• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.cts.verifier.vr;
17 
18 import android.content.ComponentName;
19 import android.content.Intent;
20 import android.os.Bundle;
21 import android.os.Handler;
22 import android.os.HandlerThread;
23 import android.provider.Settings;
24 import android.util.Log;
25 import android.view.LayoutInflater;
26 import android.view.View;
27 import android.view.ViewGroup;
28 import android.widget.Button;
29 import android.widget.ImageView;
30 import android.widget.TextView;
31 
32 
33 import com.android.cts.verifier.PassFailButtons;
34 import com.android.cts.verifier.R;
35 
36 import java.util.Arrays;
37 import java.util.Objects;
38 import java.util.concurrent.ArrayBlockingQueue;
39 import java.util.concurrent.TimeUnit;
40 
41 public class VrListenerVerifierActivity extends PassFailButtons.Activity {
42 
43     private static final String TAG = "VrListenerActivity";
44     public static final String ENABLED_VR_LISTENERS = "enabled_vr_listeners";
45     private static final String STATE = "state";
46     private static final int POLL_DELAY_MS = 2000;
47     static final String EXTRA_LAUNCH_SECOND_INTENT = "do2intents";
48 
49     private LayoutInflater mInflater;
50     private InteractiveTestCase[] mTests;
51     private ViewGroup mTestViews;
52     private int mCurrentIdx;
53     private Handler mMainHandler;
54     private Handler mTestHandler;
55     private HandlerThread mTestThread;
56 
57     public enum Status {
58         SETUP,
59         RUNNING,
60         PASS,
61         FAIL,
62         WAIT_FOR_USER;
63     }
64 
65     @Override
onCreate(Bundle savedState)66     protected void onCreate(Bundle savedState) {
67         super.onCreate(savedState);
68         mCurrentIdx = (savedState == null) ? 0 : savedState.getInt(STATE, 0);
69 
70         mTestThread = new HandlerThread("VrTestThread");
71         mTestThread.start();
72         mTestHandler = new Handler(mTestThread.getLooper());
73         mInflater = getLayoutInflater();
74         View v = mInflater.inflate(R.layout.vr_main, null);
75         setContentView(v);
76         setPassFailButtonClickListeners();
77         getPassButton().setEnabled(false);
78         setInfoResources(R.string.vr_test_title, R.string.vr_info, -1);
79 
80         mTestViews = (ViewGroup) v.findViewById(R.id.vr_test_items);
81         mTests = new InteractiveTestCase[] {
82                 new IsDefaultDisabledTest(),
83                 new UserEnableTest(),
84                 new VrModeSwitchTest(),
85                 new VrModeMultiSwitchTest(),
86                 new UserDisableTest(),
87         };
88 
89         for (InteractiveTestCase test : mTests) {
90             test.setStatus((savedState == null) ? Status.SETUP :
91                     Status.values()[savedState.getInt(test.getClass().getSimpleName(), 0)]);
92             mTestViews.addView(test.getView(mTestViews));
93         }
94 
95         updateUiState();
96 
97         mMainHandler = new Handler();
98     }
99 
100     @Override
onDestroy()101     public void onDestroy() {
102         super.onDestroy();
103         if (mTestThread != null) {
104             mTestThread.quit();
105         }
106     }
107 
108     @Override
onSaveInstanceState(Bundle outState)109     protected void onSaveInstanceState(Bundle outState) {
110         outState.putInt(STATE, mCurrentIdx);
111         for (InteractiveTestCase i : mTests) {
112             outState.putInt(i.getClass().getSimpleName(), i.getStatus().ordinal());
113         }
114         super.onSaveInstanceState(outState);
115     }
116 
117     @Override
onResume()118     protected void onResume() {
119         super.onResume();
120         runNext();
121     }
122 
updateUiState()123     private void updateUiState() {
124         boolean allPassed = true;
125         for (InteractiveTestCase t : mTests) {
126             t.updateViews();
127             if (t.getStatus() != Status.PASS) {
128                 allPassed = false;
129             }
130         }
131 
132         if (allPassed) {
133             getPassButton().setEnabled(true);
134         }
135     }
136 
logWithStack(String message)137     protected void logWithStack(String message) {
138         logWithStack(message, null);
139     }
140 
logWithStack(String message, Throwable stackTrace)141     protected void logWithStack(String message, Throwable stackTrace) {
142         if (stackTrace == null) {
143             stackTrace = new Throwable();
144             stackTrace.fillInStackTrace();
145         }
146         Log.e(TAG, message, stackTrace);
147     }
148 
selectNext()149     private void selectNext() {
150         mCurrentIdx++;
151         if (mCurrentIdx >= mTests.length) {
152             done();
153             return;
154         }
155         final InteractiveTestCase current = mTests[mCurrentIdx];
156         current.markWaiting();
157     }
158 
runNext()159     private void runNext() {
160         if (mCurrentIdx >= mTests.length) {
161             done();
162             return;
163         }
164         final InteractiveTestCase current = mTests[mCurrentIdx];
165         mTestHandler.post(new Runnable() {
166             @Override
167             public void run() {
168                 Log.i(TAG, "Starting test: " + current.getClass().getSimpleName());
169                 boolean passed = true;
170                 try {
171                     current.setUp();
172                     current.test();
173                 } catch (Throwable e) {
174                     logWithStack("Failed " + current.getClass().getSimpleName() + " with: ", e);
175                     setFailed(current);
176                     passed = false;
177                 } finally {
178                     try {
179                         current.tearDown();
180                     } catch (Throwable e) {
181                         logWithStack("Failed tearDown of " + current.getClass().getSimpleName() +
182                                 " with: ", e);
183                         setFailed(current);
184                         passed = false;
185                     }
186                 }
187                 if (passed) {
188                     current.markPassed();
189                     mMainHandler.post(new Runnable() {
190                         @Override
191                         public void run() {
192                             selectNext();
193                         }
194                     });
195                 }
196                 Log.i(TAG, "Done test: " + current.getClass().getSimpleName());
197             }
198         });
199     }
200 
done()201     private void done() {
202         updateUiState();
203         Log.i(TAG, "Completed run!");
204     }
205 
206 
setFailed(final InteractiveTestCase current)207     private void setFailed(final InteractiveTestCase current) {
208         mMainHandler.post(new Runnable() {
209             @Override
210             public void run() {
211                 getPassButton().setEnabled(false);
212                 current.markFailed();
213             }
214         });
215     }
216 
createUserInteractionTestView(ViewGroup parent, int stringId, int messageId)217     protected View createUserInteractionTestView(ViewGroup parent, int stringId, int messageId) {
218         View v = mInflater.inflate(R.layout.vr_item, parent, false);
219         TextView instructions = (TextView) v.findViewById(R.id.vr_instructions);
220         instructions.setText(getString(messageId));
221         Button b = (Button) v.findViewById(R.id.vr_action_button);
222         b.setText(stringId);
223         b.setTag(stringId);
224         return v;
225     }
226 
createAutoTestView(ViewGroup parent, int messageId)227     protected View createAutoTestView(ViewGroup parent, int messageId) {
228         View v = mInflater.inflate(R.layout.vr_item, parent, false);
229         TextView instructions = (TextView) v.findViewById(R.id.vr_instructions);
230         instructions.setText(getString(messageId));
231         Button b = (Button) v.findViewById(R.id.vr_action_button);
232         b.setVisibility(View.GONE);
233         return v;
234     }
235 
236     protected abstract class InteractiveTestCase {
237         protected static final String TAG = "InteractiveTest";
238         private Status status;
239         private View view;
240 
inflate(ViewGroup parent)241         abstract View inflate(ViewGroup parent);
242 
getView(ViewGroup parent)243         View getView(ViewGroup parent) {
244             if (view == null) {
245                 view = inflate(parent);
246             }
247             return view;
248         }
249 
test()250         abstract void test() throws Throwable;
251 
setUp()252         void setUp() throws Throwable {
253             // Noop
254         }
255 
tearDown()256         void tearDown() throws Throwable {
257             // Noop
258         }
259 
getStatus()260         Status getStatus() {
261             return status;
262         }
263 
setStatus(Status s)264         void setStatus(Status s) {
265             status = s;
266         }
267 
markFailed()268         void markFailed() {
269             Log.i(TAG, "FAILED test: " + this.getClass().getSimpleName());
270             mMainHandler.post(new Runnable() {
271                 @Override
272                 public void run() {
273                     InteractiveTestCase.this.setStatus(Status.FAIL);
274                     updateViews();
275                 }
276             });
277         }
278 
markPassed()279         void markPassed() {
280             Log.i(TAG, "PASSED test: " + this.getClass().getSimpleName());
281             mMainHandler.post(new Runnable() {
282                 @Override
283                 public void run() {
284                     InteractiveTestCase.this.setStatus(Status.PASS);
285                     updateViews();
286                 }
287             });
288         }
289 
markFocused()290         void markFocused() {
291             mMainHandler.post(new Runnable() {
292                 @Override
293                 public void run() {
294                     InteractiveTestCase.this.setStatus(Status.SETUP);
295                     updateViews();
296                 }
297             });
298         }
299 
markWaiting()300         void markWaiting() {
301             mMainHandler.post(new Runnable() {
302                 @Override
303                 public void run() {
304                     InteractiveTestCase.this.setStatus(Status.WAIT_FOR_USER);
305                     updateViews();
306                 }
307             });
308         }
309 
markRunning()310         void markRunning() {
311             mMainHandler.post(new Runnable() {
312                 @Override
313                 public void run() {
314                     InteractiveTestCase.this.setStatus(Status.RUNNING);
315                     updateViews();
316                 }
317             });
318         }
319 
updateViews()320         private void updateViews() {
321             View item = view;
322             ImageView statusView = (ImageView) item.findViewById(R.id.vr_status);
323             View button = item.findViewById(R.id.vr_action_button);
324             switch (status) {
325                 case WAIT_FOR_USER:
326                     statusView.setImageResource(R.drawable.fs_warning);
327                     button.setEnabled(true);
328                     break;
329                 case SETUP:
330                     statusView.setImageResource(R.drawable.fs_indeterminate);
331                     button.setEnabled(false);
332                     break;
333                 case RUNNING:
334                     statusView.setImageResource(R.drawable.fs_clock);
335                     break;
336                 case FAIL:
337                     statusView.setImageResource(R.drawable.fs_error);
338                     break;
339                 case PASS:
340                     statusView.setImageResource(R.drawable.fs_good);
341                     button.setClickable(false);
342                     button.setEnabled(false);
343                     break;
344             }
345             statusView.invalidate();
346         }
347     }
348 
assertTrue(String message, boolean b)349     private static void assertTrue(String message, boolean b) {
350         if (!b) {
351             throw new IllegalStateException(message);
352         }
353     }
354 
assertIn(String message, E elem, E[] c)355     private static <E> void assertIn(String message, E elem, E[] c) {
356         if (!Arrays.asList(c).contains(elem)) {
357             throw new IllegalStateException(message);
358         }
359     }
360 
assertEventIn(String message, MockVrListenerService.Event elem, MockVrListenerService.EventType[] c)361     private static void assertEventIn(String message, MockVrListenerService.Event elem,
362                                       MockVrListenerService.EventType[] c) {
363         if (!Arrays.asList(c).contains(elem.type)) {
364             throw new IllegalStateException(message);
365         }
366     }
367 
launchVrListenerSettings()368     protected void launchVrListenerSettings() {
369         VrListenerVerifierActivity.this.startActivity(
370                 new Intent(Settings.ACTION_VR_LISTENER_SETTINGS));
371     }
372 
launchVrActivity()373     protected void launchVrActivity() {
374         VrListenerVerifierActivity.this.startActivity(
375                 new Intent(VrListenerVerifierActivity.this, MockVrActivity.class));
376     }
377 
launchDoubleVrActivity()378     protected void launchDoubleVrActivity() {
379         VrListenerVerifierActivity.this.startActivity(
380                 new Intent(VrListenerVerifierActivity.this, MockVrActivity.class).
381                         putExtra(EXTRA_LAUNCH_SECOND_INTENT, true));
382     }
383 
actionPressed(View v)384     public void actionPressed(View v) {
385         Object tag = v.getTag();
386         if (tag instanceof Integer) {
387             int id = ((Integer) tag).intValue();
388             if (id == R.string.vr_start_settings) {
389                 launchVrListenerSettings();
390             } else if (id == R.string.vr_start_vr_activity) {
391                 launchVrActivity();
392             } else if (id == R.string.vr_start_double_vr_activity) {
393                 launchDoubleVrActivity();
394             }
395         }
396     }
397 
398     private class IsDefaultDisabledTest extends InteractiveTestCase {
399 
400         @Override
inflate(ViewGroup parent)401         View inflate(ViewGroup parent) {
402             return createAutoTestView(parent, R.string.vr_check_disabled);
403         }
404 
405         @Override
setUp()406         void setUp() {
407             markFocused();
408         }
409 
410         @Override
test()411         void test() {
412             assertTrue("VR listeners should not be bound by default.",
413                     MockVrListenerService.getNumBoundMockVrListeners() == 0);
414         }
415     }
416 
417     private class UserEnableTest extends InteractiveTestCase {
418 
419         @Override
inflate(ViewGroup parent)420         View inflate(ViewGroup parent) {
421             return createUserInteractionTestView(parent, R.string.vr_start_settings,
422                     R.string.vr_enable_service);
423         }
424 
425         @Override
setUp()426         void setUp() {
427             markWaiting();
428         }
429 
430         @Override
test()431         void test() {
432             String helpers = Settings.Secure.getString(getContentResolver(), ENABLED_VR_LISTENERS);
433             ComponentName c = new ComponentName(VrListenerVerifierActivity.this,
434                     MockVrListenerService.class);
435             if (MockVrListenerService.getPendingEvents().size() > 0) {
436                 MockVrListenerService.getPendingEvents().clear();
437                 throw new IllegalStateException("VrListenerService bound before entering VR mode!");
438             }
439             assertTrue("Settings must now contain " + c.flattenToString(),
440                     helpers != null && helpers.contains(c.flattenToString()));
441         }
442     }
443 
444     private class VrModeSwitchTest extends InteractiveTestCase {
445 
446         @Override
inflate(ViewGroup parent)447         View inflate(ViewGroup parent) {
448             return createUserInteractionTestView(parent, R.string.vr_start_vr_activity,
449                     R.string.vr_start_vr_activity_desc);
450         }
451 
452         @Override
setUp()453         void setUp() {
454             markWaiting();
455         }
456 
457         @Override
test()458         void test() throws Throwable {
459             ArrayBlockingQueue<MockVrListenerService.Event> q =
460                     MockVrListenerService.getPendingEvents();
461             MockVrListenerService.Event e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
462             assertTrue("Timed out before receive onCreate or onBind event from VrListenerService.",
463                     e != null);
464             assertEventIn("First listener service event must be onCreate or onBind, but was " +
465                     e.type, e, new MockVrListenerService.EventType[]{
466                     MockVrListenerService.EventType.ONCREATE,
467                     MockVrListenerService.EventType.ONBIND
468             });
469             if (e.type == MockVrListenerService.EventType.ONCREATE) {
470                 e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
471                 assertTrue("Timed out before receive onBind event from VrListenerService.",
472                         e != null);
473                 assertEventIn("Second listener service event must be onBind, but was " +
474                         e.type, e, new MockVrListenerService.EventType[]{
475                         MockVrListenerService.EventType.ONBIND
476                 });
477             }
478 
479             e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
480             assertTrue("Timed out before receive onCurrentVrModeActivityChanged event " +
481                     "from VrListenerService.", e != null);
482             assertTrue("Listener service must receive onCurrentVrModeActivityChanged, but was " +
483                     e.type,
484                     e.type == MockVrListenerService.EventType.ONCURRENTVRMODEACTIVITYCHANGED);
485             ComponentName expected = new ComponentName(VrListenerVerifierActivity.this,
486                     MockVrActivity.class);
487             assertTrue("Activity component must be " + expected + ", but was: " + e.arg1,
488                     Objects.equals(expected, e.arg1));
489 
490             e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
491             assertTrue("Timed out before receive unbind event from VrListenerService.", e != null);
492             assertEventIn("Listener service must receive onUnbind, but was " +
493                     e.type, e, new MockVrListenerService.EventType[]{
494                     MockVrListenerService.EventType.ONUNBIND
495             });
496 
497             // Consume onDestroy
498             e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
499             assertTrue("Timed out before receive onDestroy event from VrListenerService.",
500                     e != null);
501             assertEventIn("Listener service must receive onDestroy, but was " +
502                     e.type, e, new MockVrListenerService.EventType[]{
503                     MockVrListenerService.EventType.ONDESTROY
504             });
505 
506             markRunning();
507 
508             e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
509             if (e != null) {
510                 throw new IllegalStateException("Spurious event received after onDestroy: "
511                         + e.type);
512             }
513         }
514     }
515 
516     private class VrModeMultiSwitchTest extends InteractiveTestCase {
517 
518         @Override
inflate(ViewGroup parent)519         View inflate(ViewGroup parent) {
520             return createUserInteractionTestView(parent, R.string.vr_start_double_vr_activity,
521                     R.string.vr_start_vr_double_activity_desc);
522         }
523 
524         @Override
setUp()525         void setUp() {
526             markWaiting();
527         }
528 
529         @Override
test()530         void test() throws Throwable {
531             ArrayBlockingQueue<MockVrListenerService.Event> q =
532                     MockVrListenerService.getPendingEvents();
533             MockVrListenerService.Event e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
534             assertTrue("Timed out before receive event from VrListenerService.", e != null);
535             assertEventIn("First listener service event must be onCreate or onBind, but was " +
536                     e.type, e, new MockVrListenerService.EventType[]{
537                     MockVrListenerService.EventType.ONCREATE,
538                     MockVrListenerService.EventType.ONBIND
539             });
540             if (e.type == MockVrListenerService.EventType.ONCREATE) {
541                 e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
542                 assertTrue("Timed out before receive event from VrListenerService.", e != null);
543                 assertEventIn("Second listener service event must be onBind, but was " +
544                         e.type, e, new MockVrListenerService.EventType[]{
545                         MockVrListenerService.EventType.ONBIND
546                 });
547             }
548 
549             e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
550             assertTrue("Timed out before receive event from VrListenerService.", e != null);
551             assertTrue("Listener service must receive onCurrentVrModeActivityChanged, but received "
552                     + e.type, e.type ==
553                     MockVrListenerService.EventType.ONCURRENTVRMODEACTIVITYCHANGED);
554             ComponentName expected = new ComponentName(VrListenerVerifierActivity.this,
555                     MockVrActivity.class);
556             assertTrue("Activity component must be " + expected + ", but was: " + e.arg1,
557                     Objects.equals(expected, e.arg1));
558 
559             e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
560             assertTrue("Timed out before receive event from VrListenerService.", e != null);
561             assertTrue("Listener service must receive onCurrentVrModeActivityChanged, but received "
562                     + e.type, e.type ==
563                     MockVrListenerService.EventType.ONCURRENTVRMODEACTIVITYCHANGED);
564             ComponentName expected2 = new ComponentName(VrListenerVerifierActivity.this,
565                     MockVrActivity2.class);
566             assertTrue("Activity component must be " + expected2 + ", but was: " + e.arg1,
567                     Objects.equals(expected2, e.arg1));
568 
569             e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
570             assertTrue("Timed out before receive event from VrListenerService.", e != null);
571             assertTrue("Listener service must receive onCurrentVrModeActivityChanged, but received "
572                     + e.type, e.type ==
573                     MockVrListenerService.EventType.ONCURRENTVRMODEACTIVITYCHANGED);
574             assertTrue("Activity component must be " + expected + ", but was: " + e.arg1,
575                     Objects.equals(expected, e.arg1));
576 
577             e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
578             assertTrue("Timed out before receive event from VrListenerService.", e != null);
579             assertEventIn("Listener service must receive onUnbind, but was " +
580                     e.type, e, new MockVrListenerService.EventType[]{
581                     MockVrListenerService.EventType.ONUNBIND
582             });
583 
584             // Consume onDestroy
585             e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
586             assertTrue("Timed out before receive onDestroy event from VrListenerService.",
587                     e != null);
588             assertEventIn("Listener service must receive onDestroy, but was " +
589                     e.type, e, new MockVrListenerService.EventType[]{
590                     MockVrListenerService.EventType.ONDESTROY
591             });
592 
593             markRunning();
594 
595             e = q.poll(POLL_DELAY_MS, TimeUnit.MILLISECONDS);
596             if (e != null) {
597                 throw new IllegalStateException("Spurious event received after onDestroy: "
598                         + e.type);
599             }
600         }
601     }
602 
603     private class UserDisableTest extends InteractiveTestCase {
604 
605         @Override
inflate(ViewGroup parent)606         View inflate(ViewGroup parent) {
607             return createUserInteractionTestView(parent, R.string.vr_start_settings,
608                     R.string.vr_disable_service);
609         }
610 
611         @Override
setUp()612         void setUp() {
613             markWaiting();
614         }
615 
616         @Override
test()617         void test() {
618             String helpers = Settings.Secure.getString(getContentResolver(), ENABLED_VR_LISTENERS);
619             ComponentName c = new ComponentName(VrListenerVerifierActivity.this,
620                     MockVrListenerService.class);
621             assertTrue("Settings must no longer contain " + c.flattenToString(),
622                     helpers == null || !(helpers.contains(c.flattenToString())));
623         }
624     }
625 
626 }
627