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