blob: c31adb466a94de59ec8fa75388e8f8640c66052b [file] [log] [blame]
/*
* 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())));
}
}
}