Tests for the VR mode listener.
Bug=27977967
Change-Id: Id57e9112b01b1c66e0a9f48773fc234ead67ef46
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index b8e714f..b93ab4c 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -45,6 +45,8 @@
android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus"
android:required="false" />
+ <uses-feature android:name="android.software.vr.mode"
+ android:required="false" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
@@ -1206,14 +1208,53 @@
</activity>
<service android:name=".notifications.MockListener"
- android:exported="true"
- android:label="@string/nls_service_name"
- android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+ android:exported="true"
+ android:label="@string/nls_service_name"
+ android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
+ <activity android:name=".vr.VrListenerVerifierActivity"
+ android:label="@string/vr_tests">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.cts.intent.category.MANUAL_TEST" />
+ </intent-filter>
+ <meta-data android:name="test_category" android:value="@string/test_category_vr" />
+ </activity>
+
+ <activity android:name=".vr.MockVrActivity"
+ android:label="@string/vr_tests"
+ android:exported="false"
+ android:process=":TestVrActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name=".vr.MockVrActivity2"
+ android:label="@string/vr_tests"
+ android:exported="false"
+ android:process=":TestVrActivity2">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <service android:name=".vr.MockVrListenerService"
+ android:exported="true"
+ android:enabled="true"
+ android:label="@string/vr_service_name"
+ android:permission="android.permission.BIND_VR_LISTENER_SERVICE">
+ <intent-filter>
+ <action android:name="android.service.vr.VrListenerService" />
+ </intent-filter>
+ </service>
+
<service android:name=".notifications.MockConditionProvider"
android:exported="true"
android:label="@string/cp_service_name"
diff --git a/apps/CtsVerifier/res/layout/vr_item.xml b/apps/CtsVerifier/res/layout/vr_item.xml
new file mode 100644
index 0000000..b938747
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/vr_item.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" >
+
+ <ImageView
+ android:id="@+id/vr_status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:layout_marginTop="10dip"
+ android:contentDescription="@string/pass_button_text"
+ android:padding="10dip"
+ android:src="@drawable/fs_indeterminate" />
+
+ <TextView
+ android:id="@+id/vr_instructions"
+ style="@style/InstructionsSmallFont"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentTop="true"
+ android:layout_toRightOf="@id/vr_status"
+ android:text="@string/vr_enable_service" />
+
+ <Button
+ android:id="@+id/vr_action_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_below="@id/vr_instructions"
+ android:layout_marginLeft="20dip"
+ android:layout_marginRight="20dip"
+ android:layout_toRightOf="@id/vr_status"
+ android:onClick="actionPressed"
+ android:text="@string/vr_start_settings" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/vr_main.xml b/apps/CtsVerifier/res/layout/vr_main.xml
new file mode 100644
index 0000000..9f6b31d
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/vr_main.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:padding="10dip"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ScrollView
+ android:id="@+id/vr_test_scroller"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:padding="10dip" >
+
+ <LinearLayout
+ android:id="@+id/vr_test_items"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+ </LinearLayout>
+ </ScrollView>
+
+ <include
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="0"
+ layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 94e7bd4..a0834a5 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -1217,6 +1217,23 @@
and disabled, and that once enabled the service is able to receive notifications and
dismiss them.
</string>
+ <string name="vr_tests">VR Tests</string>
+ <string name="test_category_vr">VR</string>
+ <string name="vr_test_title">VR Listener Test</string>
+ <string name="vr_service_name">VR Listener for CTS Verifier</string>
+ <string name="vr_info">This test checks that a VrListenerService can be enabled and disabled, and
+ and that it receives the correct lifecycle callbacks when entering and exiting VR mode.
+ </string>
+ <string name="vr_start_settings">Launch Settings</string>
+ <string name="vr_start_vr_activity">Launch VR mode activity</string>
+ <string name="vr_start_double_vr_activity">Launch Two VR mode activities</string>
+ <string name="vr_start_vr_activity_desc">Click the button to launch the VR mode activity.</string>
+ <string name="vr_start_vr_double_activity_desc">Click the button to launch two consecutive VR mode activities.</string>
+ <string name="vr_check_disabled">Check that the CTS VR helper service is disabled by default.</string>
+ <string name="vr_enable_service">Please enable \"VR Listener for CTS Verifier\"
+ under Apps > Gear Icon > Special Access > VR Helper Services and return here.</string>
+ <string name="vr_disable_service">Please disable \"VR Listener for CTS Verifier\"
+ under Apps > Gear Icon > Special Access > VR Helper Services and return here.</string>
<string name="nls_enable_service">Please enable \"Notification Listener for CTS Verifier\"
under Security > Notification Access and return here.</string>
<string name="nls_disable_service">Please disable \"Notification Listener for CTS Verifier\"
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/vr/MockVrActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/vr/MockVrActivity.java
new file mode 100644
index 0000000..bccd8ef
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/vr/MockVrActivity.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.verifier.vr;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+
+public class MockVrActivity extends Activity {
+ private static final String TAG = "MockVrActivity";
+ static final int EVENT_DELAY_MS = 1000;
+ private boolean mDoSecondIntent;
+ private Handler mHandler;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ Log.i(TAG, "onCreate called.");
+ super.onCreate(savedInstanceState);
+ try {
+ setVrModeEnabled(true, new ComponentName(this, MockVrListenerService.class));
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Could not set VR mode: " + e);
+ }
+ mDoSecondIntent = getIntent().getBooleanExtra(
+ VrListenerVerifierActivity.EXTRA_LAUNCH_SECOND_INTENT, false);
+ mHandler = new Handler();
+ }
+
+ @Override
+ protected void onResume() {
+ Log.i(TAG, "onResume called.");
+
+ super.onResume();
+ if (mDoSecondIntent) {
+ mDoSecondIntent = false;
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ MockVrActivity.this.startActivity(new Intent(MockVrActivity.this,
+ MockVrActivity2.class));
+ }
+ }, EVENT_DELAY_MS);
+ } else {
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ MockVrActivity.this.finish();
+ }
+ }, EVENT_DELAY_MS);
+ }
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ Log.i(TAG, "onWindowFocusChanged called with " + hasFocus);
+ super.onWindowFocusChanged(hasFocus);
+ }
+
+ @Override
+ protected void onPause() {
+ Log.i(TAG, "onPause called.");
+ super.onPause();
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/vr/MockVrActivity2.java b/apps/CtsVerifier/src/com/android/cts/verifier/vr/MockVrActivity2.java
new file mode 100644
index 0000000..66b5630
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/vr/MockVrActivity2.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.verifier.vr;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+
+public class MockVrActivity2 extends Activity {
+ private static final String TAG = "MockVrActivity2";
+ private Handler mHandler;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ Log.i(TAG, "onCreate called.");
+ super.onCreate(savedInstanceState);
+ try {
+ setVrModeEnabled(true, new ComponentName(this, MockVrListenerService.class));
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Could not set VR mode: " + e);
+ }
+ mHandler = new Handler();
+ }
+
+ @Override
+ protected void onResume() {
+ Log.i(TAG, "onResume called.");
+
+ super.onResume();
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ MockVrActivity2.this.finish();
+ }
+ }, MockVrActivity.EVENT_DELAY_MS);
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ Log.i(TAG, "onWindowFocusChanged called with " + hasFocus);
+ super.onWindowFocusChanged(hasFocus);
+ }
+
+ @Override
+ protected void onPause() {
+ Log.i(TAG, "onPause called.");
+ super.onPause();
+ }
+
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/vr/MockVrListenerService.java b/apps/CtsVerifier/src/com/android/cts/verifier/vr/MockVrListenerService.java
new file mode 100644
index 0000000..5c460a1
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/vr/MockVrListenerService.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.verifier.vr;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.IBinder;
+import android.service.vr.VrListenerService;
+import android.util.Log;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class MockVrListenerService extends VrListenerService {
+ private static final String TAG = "MockVrListener";
+ private static final AtomicInteger sNumBound = new AtomicInteger();
+
+ private static final ArrayBlockingQueue<Event> sEventQueue = new ArrayBlockingQueue<>(4096);
+
+ public static ArrayBlockingQueue<Event> getPendingEvents() {
+ return sEventQueue;
+ }
+
+ public static int getNumBoundMockVrListeners() {
+ return sNumBound.get();
+ }
+
+ public enum EventType{
+ ONBIND,
+ ONREBIND,
+ ONUNBIND,
+ ONCREATE,
+ ONDESTROY,
+ ONCURRENTVRMODEACTIVITYCHANGED
+ }
+
+ public static class Event {
+ public final VrListenerService instance;
+ public final EventType type;
+ public final Object arg1;
+
+ private Event(VrListenerService i, EventType t, Object o) {
+ instance = i;
+ type = t;
+ arg1 = o;
+ }
+
+ public static Event build(VrListenerService instance, EventType type, Object argument1) {
+ return new Event(instance, type, argument1);
+ }
+
+ public static Event build(VrListenerService instance, EventType type) {
+ return new Event(instance, type, null);
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.i(TAG, "onBind called");
+ sNumBound.getAndIncrement();
+ try {
+ sEventQueue.put(Event.build(this, EventType.ONBIND, intent));
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Service thread interrupted: " + e);
+ }
+ return super.onBind(intent);
+ }
+
+ @Override
+ public void onRebind(Intent intent) {
+ Log.i(TAG, "onRebind called");
+ try {
+ sEventQueue.put(Event.build(this, EventType.ONREBIND, intent));
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Service thread interrupted: " + e);
+ }
+ super.onRebind(intent);
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ Log.i(TAG, "onUnbind called");
+ sNumBound.getAndDecrement();
+ try {
+ sEventQueue.put(Event.build(this, EventType.ONUNBIND, intent));
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Service thread interrupted: " + e);
+ }
+ return super.onUnbind(intent);
+ }
+
+ @Override
+ public void onCreate() {
+ Log.i(TAG, "onCreate called");
+ try {
+ sEventQueue.put(Event.build(this, EventType.ONCREATE));
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Service thread interrupted: " + e);
+ }
+ super.onCreate();
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.i(TAG, "onDestroy called");
+ try {
+ sEventQueue.put(Event.build(this, EventType.ONDESTROY));
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Service thread interrupted: " + e);
+ }
+ super.onDestroy();
+ }
+
+ @Override
+ public void onCurrentVrActivityChanged(ComponentName component) {
+ Log.i(TAG, "onCurrentVrActivityChanged called with: " + component);
+ try {
+ sEventQueue.put(Event.build(this, EventType.ONCURRENTVRMODEACTIVITYCHANGED, component));
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Service thread interrupted: " + e);
+ }
+ }
+
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/vr/VrListenerVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/vr/VrListenerVerifierActivity.java
new file mode 100644
index 0000000..c31adb4
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/vr/VrListenerVerifierActivity.java
@@ -0,0 +1,626 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.verifier.vr;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class VrListenerVerifierActivity extends PassFailButtons.Activity {
+
+ private static final String TAG = "VrListenerActivity";
+ public static final String ENABLED_VR_LISTENERS = "enabled_vr_listeners";
+ private static final String STATE = "state";
+ private static final int POLL_DELAY_MS = 2000;
+ static final String EXTRA_LAUNCH_SECOND_INTENT = "do2intents";
+
+ private LayoutInflater mInflater;
+ private InteractiveTestCase[] mTests;
+ private ViewGroup mTestViews;
+ private int mCurrentIdx;
+ private Handler mMainHandler;
+ private Handler mTestHandler;
+ private HandlerThread mTestThread;
+
+ public enum Status {
+ SETUP,
+ RUNNING,
+ PASS,
+ FAIL,
+ WAIT_FOR_USER;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedState) {
+ super.onCreate(savedState);
+ mCurrentIdx = (savedState == null) ? 0 : savedState.getInt(STATE, 0);
+
+ mTestThread = new HandlerThread("VrTestThread");
+ mTestThread.start();
+ mTestHandler = new Handler(mTestThread.getLooper());
+ mInflater = getLayoutInflater();
+ View v = mInflater.inflate(R.layout.vr_main, null);
+ setContentView(v);
+ setPassFailButtonClickListeners();
+ getPassButton().setEnabled(false);
+ setInfoResources(R.string.vr_test_title, R.string.vr_info, -1);
+
+ mTestViews = (ViewGroup) v.findViewById(R.id.vr_test_items);
+ mTests = new InteractiveTestCase[] {
+ new IsDefaultDisabledTest(),
+ new UserEnableTest(),
+ new VrModeSwitchTest(),
+ new VrModeMultiSwitchTest(),
+ new UserDisableTest(),
+ };
+
+ for (InteractiveTestCase test : mTests) {
+ test.setStatus((savedState == null) ? Status.SETUP :
+ Status.values()[savedState.getInt(test.getClass().getSimpleName(), 0)]);
+ mTestViews.addView(test.getView(mTestViews));
+ }
+
+ updateUiState();
+
+ mMainHandler = new Handler();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mTestThread != null) {
+ mTestThread.quit();
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ outState.putInt(STATE, mCurrentIdx);
+ for (InteractiveTestCase i : mTests) {
+ outState.putInt(i.getClass().getSimpleName(), i.getStatus().ordinal());
+ }
+ super.onSaveInstanceState(outState);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ runNext();
+ }
+
+ private void updateUiState() {
+ boolean allPassed = true;
+ for (InteractiveTestCase t : mTests) {
+ t.updateViews();
+ if (t.getStatus() != Status.PASS) {
+ allPassed = false;
+ }
+ }
+
+ if (allPassed) {
+ getPassButton().setEnabled(true);
+ }
+ }
+
+ protected void logWithStack(String message) {
+ logWithStack(message, null);
+ }
+
+ protected void logWithStack(String message, Throwable stackTrace) {
+ if (stackTrace == null) {
+ stackTrace = new Throwable();
+ stackTrace.fillInStackTrace();
+ }
+ Log.e(TAG, message, stackTrace);
+ }
+
+ private void selectNext() {
+ mCurrentIdx++;
+ if (mCurrentIdx >= mTests.length) {
+ done();
+ return;
+ }
+ final InteractiveTestCase current = mTests[mCurrentIdx];
+ current.markWaiting();
+ }
+
+ private void runNext() {
+ if (mCurrentIdx >= mTests.length) {
+ done();
+ return;
+ }
+ final InteractiveTestCase current = mTests[mCurrentIdx];
+ mTestHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Log.i(TAG, "Starting test: " + current.getClass().getSimpleName());
+ boolean passed = true;
+ try {
+ current.setUp();
+ current.test();
+ } catch (Throwable e) {
+ logWithStack("Failed " + current.getClass().getSimpleName() + " with: ", e);
+ setFailed(current);
+ passed = false;
+ } finally {
+ try {
+ current.tearDown();
+ } catch (Throwable e) {
+ logWithStack("Failed tearDown of " + current.getClass().getSimpleName() +
+ " with: ", e);
+ setFailed(current);
+ passed = false;
+ }
+ }
+ if (passed) {
+ current.markPassed();
+ mMainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ selectNext();
+ }
+ });
+ }
+ Log.i(TAG, "Done test: " + current.getClass().getSimpleName());
+ }
+ });
+ }
+
+ private void done() {
+ updateUiState();
+ Log.i(TAG, "Completed run!");
+ }
+
+
+ private void setFailed(final InteractiveTestCase current) {
+ mMainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ getPassButton().setEnabled(false);
+ current.markFailed();
+ }
+ });
+ }
+
+ protected View createUserInteractionTestView(ViewGroup parent, int stringId, int messageId) {
+ View v = mInflater.inflate(R.layout.vr_item, parent, false);
+ TextView instructions = (TextView) v.findViewById(R.id.vr_instructions);
+ instructions.setText(getString(messageId));
+ Button b = (Button) v.findViewById(R.id.vr_action_button);
+ b.setText(stringId);
+ b.setTag(stringId);
+ return v;
+ }
+
+ protected View createAutoTestView(ViewGroup parent, int messageId) {
+ View v = mInflater.inflate(R.layout.vr_item, parent, false);
+ TextView instructions = (TextView) v.findViewById(R.id.vr_instructions);
+ instructions.setText(getString(messageId));
+ Button b = (Button) v.findViewById(R.id.vr_action_button);
+ b.setVisibility(View.GONE);
+ return v;
+ }
+
+ protected abstract class InteractiveTestCase {
+ protected static final String TAG = "InteractiveTest";
+ private Status status;
+ private View view;
+
+ abstract View inflate(ViewGroup parent);
+
+ View getView(ViewGroup parent) {
+ if (view == null) {
+ view = inflate(parent);
+ }
+ return view;
+ }
+
+ abstract void test() throws Throwable;
+
+ void setUp() throws Throwable {
+ // Noop
+ }
+
+ void tearDown() throws Throwable {
+ // Noop
+ }
+
+ Status getStatus() {
+ return status;
+ }
+
+ void setStatus(Status s) {
+ status = s;
+ }
+
+ void markFailed() {
+ Log.i(TAG, "FAILED test: " + this.getClass().getSimpleName());
+ mMainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ InteractiveTestCase.this.setStatus(Status.FAIL);
+ updateViews();
+ }
+ });
+ }
+
+ void markPassed() {
+ Log.i(TAG, "PASSED test: " + this.getClass().getSimpleName());
+ mMainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ InteractiveTestCase.this.setStatus(Status.PASS);
+ updateViews();
+ }
+ });
+ }
+
+ void markFocused() {
+ mMainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ InteractiveTestCase.this.setStatus(Status.SETUP);
+ updateViews();
+ }
+ });
+ }
+
+ void markWaiting() {
+ mMainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ InteractiveTestCase.this.setStatus(Status.WAIT_FOR_USER);
+ updateViews();
+ }
+ });
+ }
+
+ void markRunning() {
+ mMainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ InteractiveTestCase.this.setStatus(Status.RUNNING);
+ updateViews();
+ }
+ });
+ }
+
+ private void updateViews() {
+ View item = view;
+ ImageView statusView = (ImageView) item.findViewById(R.id.vr_status);
+ View button = item.findViewById(R.id.vr_action_button);
+ switch (status) {
+ case WAIT_FOR_USER:
+ statusView.setImageResource(R.drawable.fs_warning);
+ button.setEnabled(true);
+ break;
+ case SETUP:
+ statusView.setImageResource(R.drawable.fs_indeterminate);
+ button.setEnabled(false);
+ break;
+ case RUNNING:
+ statusView.setImageResource(R.drawable.fs_clock);
+ break;
+ case FAIL:
+ statusView.setImageResource(R.drawable.fs_error);
+ break;
+ case PASS:
+ statusView.setImageResource(R.drawable.fs_good);
+ button.setClickable(false);
+ button.setEnabled(false);
+ break;
+ }
+ statusView.invalidate();
+ }
+ }
+
+ private static void assertTrue(String message, boolean b) {
+ if (!b) {
+ throw new IllegalStateException(message);
+ }
+ }
+
+ private static <E> void assertIn(String message, E elem, E[] c) {
+ if (!Arrays.asList(c).contains(elem)) {
+ throw new IllegalStateException(message);
+ }
+ }
+
+ private static void assertEventIn(String message, MockVrListenerService.Event elem,
+ MockVrListenerService.EventType[] c) {
+ if (!Arrays.asList(c).contains(elem.type)) {
+ throw new IllegalStateException(message);
+ }
+ }
+
+ protected void launchVrListenerSettings() {
+ VrListenerVerifierActivity.this.startActivity(
+ new Intent(Settings.ACTION_VR_LISTENER_SETTINGS));
+ }
+
+ protected void launchVrActivity() {
+ VrListenerVerifierActivity.this.startActivity(
+ new Intent(VrListenerVerifierActivity.this, MockVrActivity.class));
+ }
+
+ protected void launchDoubleVrActivity() {
+ VrListenerVerifierActivity.this.startActivity(
+ new Intent(VrListenerVerifierActivity.this, MockVrActivity.class).
+ putExtra(EXTRA_LAUNCH_SECOND_INTENT, true));
+ }
+
+ public void actionPressed(View v) {
+ Object tag = v.getTag();
+ if (tag instanceof Integer) {
+ int id = ((Integer) tag).intValue();
+ if (id == R.string.vr_start_settings) {
+ launchVrListenerSettings();
+ } else if (id == R.string.vr_start_vr_activity) {
+ launchVrActivity();
+ } else if (id == R.string.vr_start_double_vr_activity) {
+ launchDoubleVrActivity();
+ }
+ }
+ }
+
+ private class IsDefaultDisabledTest extends InteractiveTestCase {
+
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoTestView(parent, R.string.vr_check_disabled);
+ }
+
+ @Override
+ void setUp() {
+ markFocused();
+ }
+
+ @Override
+ void test() {
+ assertTrue("VR listeners should not be bound by default.",
+ MockVrListenerService.getNumBoundMockVrListeners() == 0);
+ }
+ }
+
+ private class UserEnableTest extends InteractiveTestCase {
+
+ @Override
+ View inflate(ViewGroup parent) {
+ return createUserInteractionTestView(parent, R.string.vr_start_settings,
+ R.string.vr_enable_service);
+ }
+
+ @Override
+ void setUp() {
+ markWaiting();
+ }
+
+ @Override
+ void test() {
+ String helpers = Settings.Secure.getString(getContentResolver(), ENABLED_VR_LISTENERS);
+ ComponentName c = new ComponentName(VrListenerVerifierActivity.this,
+ MockVrListenerService.class);
+ if (MockVrListenerService.getPendingEvents().size() > 0) {
+ MockVrListenerService.getPendingEvents().clear();
+ throw new IllegalStateException("VrListenerService bound before entering VR mode!");
+ }
+ assertTrue("Settings must now contain " + c.flattenToString(),
+ helpers != null && helpers.contains(c.flattenToString()));
+ }
+ }
+
+ private class VrModeSwitchTest extends InteractiveTestCase {
+
+ @Override
+ View inflate(ViewGroup parent) {
+ return createUserInteractionTestView(parent, R.string.vr_start_vr_activity,
+ R.string.vr_start_vr_activity_desc);
+ }
+
+ @Override
+ void setUp() {
+ markWaiting();
+ }
+
+ @Override
+ void test() throws Throwable {
+ ArrayBlockingQueue<MockVrListenerService.Event> q =
+ MockVrListenerService.getPendingEvents();
+ MockVrListenerService.Event e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
+ assertTrue("Timed out before receive onCreate or onBind event from VrListenerService.",
+ e != null);
+ assertEventIn("First listener service event must be onCreate or onBind, but was " +
+ e.type, e, new MockVrListenerService.EventType[]{
+ MockVrListenerService.EventType.ONCREATE,
+ MockVrListenerService.EventType.ONBIND
+ });
+ if (e.type == MockVrListenerService.EventType.ONCREATE) {
+ e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
+ assertTrue("Timed out before receive onBind event from VrListenerService.",
+ e != null);
+ assertEventIn("Second listener service event must be onBind, but was " +
+ e.type, e, new MockVrListenerService.EventType[]{
+ MockVrListenerService.EventType.ONBIND
+ });
+ }
+
+ e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
+ assertTrue("Timed out before receive onCurrentVrModeActivityChanged event " +
+ "from VrListenerService.", e != null);
+ assertTrue("Listener service must receive onCurrentVrModeActivityChanged, but was " +
+ e.type,
+ e.type == MockVrListenerService.EventType.ONCURRENTVRMODEACTIVITYCHANGED);
+ ComponentName expected = new ComponentName(VrListenerVerifierActivity.this,
+ MockVrActivity.class);
+ assertTrue("Activity component must be " + expected + ", but was: " + e.arg1,
+ Objects.equals(expected, e.arg1));
+
+ e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
+ assertTrue("Timed out before receive unbind event from VrListenerService.", e != null);
+ assertEventIn("Listener service must receive onUnbind, but was " +
+ e.type, e, new MockVrListenerService.EventType[]{
+ MockVrListenerService.EventType.ONUNBIND
+ });
+
+ // Consume onDestroy
+ e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
+ assertTrue("Timed out before receive onDestroy event from VrListenerService.",
+ e != null);
+ assertEventIn("Listener service must receive onDestroy, but was " +
+ e.type, e, new MockVrListenerService.EventType[]{
+ MockVrListenerService.EventType.ONDESTROY
+ });
+
+ markRunning();
+
+ e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
+ if (e != null) {
+ throw new IllegalStateException("Spurious event received after onDestroy: "
+ + e.type);
+ }
+ }
+ }
+
+ private class VrModeMultiSwitchTest extends InteractiveTestCase {
+
+ @Override
+ View inflate(ViewGroup parent) {
+ return createUserInteractionTestView(parent, R.string.vr_start_double_vr_activity,
+ R.string.vr_start_vr_double_activity_desc);
+ }
+
+ @Override
+ void setUp() {
+ markWaiting();
+ }
+
+ @Override
+ void test() throws Throwable {
+ ArrayBlockingQueue<MockVrListenerService.Event> q =
+ MockVrListenerService.getPendingEvents();
+ MockVrListenerService.Event e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
+ assertTrue("Timed out before receive event from VrListenerService.", e != null);
+ assertEventIn("First listener service event must be onCreate or onBind, but was " +
+ e.type, e, new MockVrListenerService.EventType[]{
+ MockVrListenerService.EventType.ONCREATE,
+ MockVrListenerService.EventType.ONBIND
+ });
+ if (e.type == MockVrListenerService.EventType.ONCREATE) {
+ e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
+ assertTrue("Timed out before receive event from VrListenerService.", e != null);
+ assertEventIn("Second listener service event must be onBind, but was " +
+ e.type, e, new MockVrListenerService.EventType[]{
+ MockVrListenerService.EventType.ONBIND
+ });
+ }
+
+ e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
+ assertTrue("Timed out before receive event from VrListenerService.", e != null);
+ assertTrue("Listener service must receive onCurrentVrModeActivityChanged, but received "
+ + e.type, e.type ==
+ MockVrListenerService.EventType.ONCURRENTVRMODEACTIVITYCHANGED);
+ ComponentName expected = new ComponentName(VrListenerVerifierActivity.this,
+ MockVrActivity.class);
+ assertTrue("Activity component must be " + expected + ", but was: " + e.arg1,
+ Objects.equals(expected, e.arg1));
+
+ e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
+ assertTrue("Timed out before receive event from VrListenerService.", e != null);
+ assertTrue("Listener service must receive onCurrentVrModeActivityChanged, but received "
+ + e.type, e.type ==
+ MockVrListenerService.EventType.ONCURRENTVRMODEACTIVITYCHANGED);
+ ComponentName expected2 = new ComponentName(VrListenerVerifierActivity.this,
+ MockVrActivity2.class);
+ assertTrue("Activity component must be " + expected2 + ", but was: " + e.arg1,
+ Objects.equals(expected2, e.arg1));
+
+ e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
+ assertTrue("Timed out before receive event from VrListenerService.", e != null);
+ assertTrue("Listener service must receive onCurrentVrModeActivityChanged, but received "
+ + e.type, e.type ==
+ MockVrListenerService.EventType.ONCURRENTVRMODEACTIVITYCHANGED);
+ assertTrue("Activity component must be " + expected + ", but was: " + e.arg1,
+ Objects.equals(expected, e.arg1));
+
+ e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
+ assertTrue("Timed out before receive event from VrListenerService.", e != null);
+ assertEventIn("Listener service must receive onUnbind, but was " +
+ e.type, e, new MockVrListenerService.EventType[]{
+ MockVrListenerService.EventType.ONUNBIND
+ });
+
+ // Consume onDestroy
+ e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
+ assertTrue("Timed out before receive onDestroy event from VrListenerService.",
+ e != null);
+ assertEventIn("Listener service must receive onDestroy, but was " +
+ e.type, e, new MockVrListenerService.EventType[]{
+ MockVrListenerService.EventType.ONDESTROY
+ });
+
+ markRunning();
+
+ e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
+ if (e != null) {
+ throw new IllegalStateException("Spurious event received after onDestroy: "
+ + e.type);
+ }
+ }
+ }
+
+ private class UserDisableTest extends InteractiveTestCase {
+
+ @Override
+ View inflate(ViewGroup parent) {
+ return createUserInteractionTestView(parent, R.string.vr_start_settings,
+ R.string.vr_disable_service);
+ }
+
+ @Override
+ void setUp() {
+ markWaiting();
+ }
+
+ @Override
+ void test() {
+ String helpers = Settings.Secure.getString(getContentResolver(), ENABLED_VR_LISTENERS);
+ ComponentName c = new ComponentName(VrListenerVerifierActivity.this,
+ MockVrListenerService.class);
+ assertTrue("Settings must no longer contain " + c.flattenToString(),
+ helpers == null || !(helpers.contains(c.flattenToString())));
+ }
+ }
+
+}