• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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 
17 package com.android.cts.verifier.notifications;
18 
19 import static android.provider.Settings.ACTION_NOTIFICATION_LISTENER_DETAIL_SETTINGS;
20 import static android.provider.Settings.EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME;
21 
22 import android.app.NotificationManager;
23 import android.app.PendingIntent;
24 import android.app.Service;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.pm.PackageManager;
29 import android.os.Bundle;
30 import android.os.IBinder;
31 import android.os.Parcelable;
32 import android.provider.Settings.Secure;
33 import android.util.Log;
34 import android.view.LayoutInflater;
35 import android.view.View;
36 import android.view.ViewGroup;
37 import android.widget.Button;
38 import android.widget.ImageView;
39 import android.widget.LinearLayout;
40 import android.widget.ScrollView;
41 import android.widget.TextView;
42 
43 import com.android.cts.verifier.PassFailButtons;
44 import com.android.cts.verifier.R;
45 
46 import java.util.ArrayList;
47 import java.util.Arrays;
48 import java.util.Iterator;
49 import java.util.List;
50 import java.util.Objects;
51 import java.util.concurrent.LinkedBlockingQueue;
52 
53 public abstract class InteractiveVerifierActivity extends PassFailButtons.Activity
54         implements Runnable {
55     private static final String TAG = "InteractiveVerifier";
56     private static final String STATE = "state";
57     private static final String STATUS = "status";
58     private static final String SCROLLY = "scrolly";
59     private static LinkedBlockingQueue<String> sDeletedQueue = new LinkedBlockingQueue<String>();
60     protected static final String LISTENER_PATH = "com.android.cts.verifier/" +
61             "com.android.cts.verifier.notifications.MockListener";
62     protected static final int SETUP = 0;
63     protected static final int READY = 1;
64     protected static final int RETEST = 2;
65     protected static final int PASS = 3;
66     protected static final int FAIL = 4;
67     protected static final int WAIT_FOR_USER = 5;
68     protected static final int RETEST_AFTER_LONG_DELAY = 6;
69     protected static final int READY_AFTER_LONG_DELAY = 7;
70 
71     protected static final int NOTIFICATION_ID = 1001;
72 
73     // TODO remove these once b/10023397 is fixed
74     public static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
75 
76     protected InteractiveTestCase mCurrentTest;
77     protected PackageManager mPackageManager;
78     protected NotificationManager mNm;
79     protected Context mContext;
80     protected Runnable mRunner;
81     protected View mHandler;
82     protected String mPackageString;
83 
84     private LayoutInflater mInflater;
85     private LinearLayout mItemList;
86     private ScrollView mScrollView;
87     private List<InteractiveTestCase> mTestList;
88     private Iterator<InteractiveTestCase> mTestOrder;
89 
90     public static class DismissService extends Service {
91         @Override
onBind(Intent intent)92         public IBinder onBind(Intent intent) {
93             return null;
94         }
95 
96         @Override
onStart(Intent intent, int startId)97         public void onStart(Intent intent, int startId) {
98             if(intent != null) { sDeletedQueue.offer(intent.getAction()); }
99         }
100     }
101 
102     protected abstract class InteractiveTestCase {
103         protected boolean mUserVerified;
104         protected int status;
105         private View view;
106         protected long delayTime = 3000;
107         boolean buttonPressed;
108 
inflate(ViewGroup parent)109         protected abstract View inflate(ViewGroup parent);
getView(ViewGroup parent)110         View getView(ViewGroup parent) {
111             if (view == null) {
112                 view = inflate(parent);
113             }
114             view.setTag(this.getClass().getSimpleName());
115             return view;
116         }
117 
118         /** @return true if the test should re-run when the test activity starts. */
autoStart()119         boolean autoStart() {
120             return false;
121         }
122 
123         /** Set status to {@link #READY} to proceed, or {@link #SETUP} to try again. */
setUp()124         protected void setUp() { status = READY; next(); };
125 
126         /** Set status to {@link #PASS} or @{link #FAIL} to proceed, or {@link #READY} to retry. */
test()127         protected void test() { status = FAIL; next(); };
128 
129         /** Do not modify status. */
tearDown()130         protected void tearDown() { next(); };
131 
setFailed()132         protected void setFailed() {
133             status = FAIL;
134             logFail();
135         }
136 
logFail()137         protected void logFail() {
138             logFail(null);
139         }
140 
logFail(String message)141         protected void logFail(String message) {
142             logWithStack("failed " + this.getClass().getSimpleName() +
143                     ((message == null) ? "" : ": " + message));
144         }
145 
logFail(String message, Throwable e)146         protected void logFail(String message, Throwable e) {
147             Log.e(TAG, "failed " + this.getClass().getSimpleName() +
148                     ((message == null) ? "" : ": " + message), e);
149         }
150 
151         // If this test contains a button that launches another activity, override this
152         // method to provide the intent to launch.
getIntent()153         protected Intent getIntent() {
154             return null;
155         }
156     }
157 
getTitleResource()158     protected abstract int getTitleResource();
getInstructionsResource()159     protected abstract int getInstructionsResource();
160 
onCreate(Bundle savedState)161     protected void onCreate(Bundle savedState) {
162         super.onCreate(savedState);
163         int savedStateIndex = (savedState == null) ? 0 : savedState.getInt(STATE, 0);
164         int savedStatus = (savedState == null) ? SETUP : savedState.getInt(STATUS, SETUP);
165         int scrollY = (savedState == null) ? 0 : savedState.getInt(SCROLLY, 0);
166         Log.i(TAG, "restored state(" + savedStateIndex + "}, status(" + savedStatus + ")");
167         mContext = this;
168         mRunner = this;
169         mNm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
170         mPackageManager = getPackageManager();
171         mInflater = getLayoutInflater();
172         View view = mInflater.inflate(R.layout.nls_main, null);
173         mScrollView = view.findViewById(R.id.nls_test_scroller);
174         mItemList = view.findViewById(R.id.nls_test_items);
175         mHandler = mItemList;
176         mTestList = new ArrayList<>();
177         mTestList.addAll(createTestItems());
178         for (InteractiveTestCase test: mTestList) {
179             mItemList.addView(test.getView(mItemList));
180         }
181         mTestOrder = mTestList.iterator();
182         for (int i = 0; i < savedStateIndex; i++) {
183             mCurrentTest = mTestOrder.next();
184             mCurrentTest.status = PASS;
185             markItem(mCurrentTest);
186         }
187         mCurrentTest = mTestOrder.next();
188 
189         mScrollView.post(() -> mScrollView.smoothScrollTo(0, scrollY));
190 
191         mCurrentTest.status = savedStatus;
192 
193         setContentView(view);
194         setPassFailButtonClickListeners();
195         getPassButton().setEnabled(false);
196 
197         setInfoResources(getTitleResource(), getInstructionsResource(), -1);
198     }
199 
200     @Override
onSaveInstanceState(Bundle outState)201     protected void onSaveInstanceState (Bundle outState) {
202         final int stateIndex = mTestList.indexOf(mCurrentTest);
203         outState.putInt(STATE, stateIndex);
204         final int status = mCurrentTest == null ? SETUP : mCurrentTest.status;
205         outState.putInt(STATUS, status);
206         outState.putInt(SCROLLY, mScrollView.getScrollY());
207         Log.i(TAG, "saved state(" + stateIndex + "), status(" + status + ")");
208     }
209 
210     @Override
onResume()211     protected void onResume() {
212         super.onResume();
213         //To avoid NPE during onResume,before start to iterate next test order
214         if (mCurrentTest != null && mCurrentTest.status != SETUP && mCurrentTest.autoStart()) {
215             Log.i(TAG, "auto starting: " + mCurrentTest.getClass().getSimpleName());
216             mCurrentTest.status = READY;
217         }
218         next();
219     }
220 
221     // Interface Utilities
222 
setButtonsEnabled(View view, boolean enabled)223     protected final void setButtonsEnabled(View view, boolean enabled) {
224         if (view instanceof Button) {
225             view.setEnabled(enabled);
226         } else if (view instanceof ViewGroup) {
227             ViewGroup viewGroup = (ViewGroup) view;
228             for (int i = 0; i < viewGroup.getChildCount(); i++) {
229                 View child = viewGroup.getChildAt(i);
230                 setButtonsEnabled(child, enabled);
231             }
232         }
233     }
234 
markItem(InteractiveTestCase test)235     protected void markItem(InteractiveTestCase test) {
236         if (test == null) { return; }
237         View item = test.view;
238         ImageView status = item.findViewById(R.id.nls_status);
239         switch (test.status) {
240             case WAIT_FOR_USER:
241                 status.setImageResource(R.drawable.fs_warning);
242                 break;
243 
244             case SETUP:
245             case READY:
246             case RETEST:
247                 status.setImageResource(R.drawable.fs_clock);
248                 break;
249 
250             case FAIL:
251                 status.setImageResource(R.drawable.fs_error);
252                 setButtonsEnabled(test.view, false);
253                 break;
254 
255             case PASS:
256                 status.setImageResource(R.drawable.fs_good);
257                 setButtonsEnabled(test.view, false);
258                 break;
259 
260         }
261         status.invalidate();
262     }
263 
createNlsSettingsItem(ViewGroup parent, int messageId)264     protected View createNlsSettingsItem(ViewGroup parent, int messageId) {
265         return createUserItem(parent, R.string.nls_start_settings, messageId);
266     }
267 
createRetryItem(ViewGroup parent, int messageId, Object... messageFormatArgs)268     protected View createRetryItem(ViewGroup parent, int messageId, Object... messageFormatArgs) {
269         return createUserItem(parent, R.string.attention_ready, messageId, messageFormatArgs);
270     }
271 
createUserItem(ViewGroup parent, int actionId, int messageId, Object... messageFormatArgs)272     protected View createUserItem(ViewGroup parent, int actionId, int messageId,
273             Object... messageFormatArgs) {
274         View item = mInflater.inflate(R.layout.nls_item, parent, false);
275         TextView instructions = item.findViewById(R.id.nls_instructions);
276         instructions.setText(getString(messageId, messageFormatArgs));
277         Button button = item.findViewById(R.id.nls_action_button);
278         button.setText(actionId);
279         button.setTag(actionId);
280         return item;
281     }
282 
createAutoItem(ViewGroup parent, int stringId)283     protected ViewGroup createAutoItem(ViewGroup parent, int stringId) {
284         ViewGroup item = (ViewGroup) mInflater.inflate(R.layout.nls_item, parent, false);
285         TextView instructions = item.findViewById(R.id.nls_instructions);
286         instructions.setText(stringId);
287         View button = item.findViewById(R.id.nls_action_button);
288         button.setVisibility(View.GONE);
289         return item;
290     }
291 
createPassFailItem(ViewGroup parent, int stringId)292     protected View createPassFailItem(ViewGroup parent, int stringId) {
293         View item = mInflater.inflate(R.layout.iva_pass_fail_item, parent, false);
294         TextView instructions = item.findViewById(R.id.nls_instructions);
295         instructions.setText(stringId);
296         return item;
297     }
298 
createUserAndPassFailItem(ViewGroup parent, int actionId, int stringId)299     protected View createUserAndPassFailItem(ViewGroup parent, int actionId, int stringId) {
300         View item = mInflater.inflate(R.layout.iva_pass_fail_item, parent, false);
301         TextView instructions = item.findViewById(R.id.nls_instructions);
302         instructions.setText(stringId);
303         Button button = item.findViewById(R.id.nls_action_button);
304         button.setVisibility(View.VISIBLE);
305         button.setText(actionId);
306         button.setTag(actionId);
307         return item;
308     }
309 
310     // Test management
311 
createTestItems()312     abstract protected List<InteractiveTestCase> createTestItems();
313 
run()314     public void run() {
315         if (mCurrentTest == null) { return; }
316         markItem(mCurrentTest);
317         switch (mCurrentTest.status) {
318             case SETUP:
319                 Log.i(TAG, "running setup for: " + mCurrentTest.getClass().getSimpleName());
320                 mCurrentTest.setUp();
321                 if (mCurrentTest.status == READY_AFTER_LONG_DELAY) {
322                     delay(mCurrentTest.delayTime);
323                 } else {
324                     delay();
325                 }
326                 break;
327 
328             case WAIT_FOR_USER:
329                 Log.i(TAG, "waiting for user: " + mCurrentTest.getClass().getSimpleName());
330                 break;
331 
332             case READY_AFTER_LONG_DELAY:
333             case RETEST_AFTER_LONG_DELAY:
334             case READY:
335             case RETEST:
336                 Log.i(TAG, "running test for: " + mCurrentTest.getClass().getSimpleName());
337                 try {
338                     mCurrentTest.test();
339                     if (mCurrentTest.status == RETEST_AFTER_LONG_DELAY) {
340                         delay(mCurrentTest.delayTime);
341                     } else {
342                         delay();
343                     }
344                 } catch (Throwable t) {
345                     mCurrentTest.status = FAIL;
346                     markItem(mCurrentTest);
347                     Log.e(TAG, "FAIL: " + mCurrentTest.getClass().getSimpleName(), t);
348                     mCurrentTest.tearDown();
349                     mCurrentTest = null;
350                     delay();
351                 }
352 
353                 break;
354 
355             case FAIL:
356                 Log.i(TAG, "FAIL: " + mCurrentTest.getClass().getSimpleName());
357                 mCurrentTest.tearDown();
358                 mCurrentTest = null;
359                 delay();
360                 break;
361 
362             case PASS:
363                 Log.i(TAG, "pass for: " + mCurrentTest.getClass().getSimpleName());
364                 mCurrentTest.tearDown();
365                 if (mTestOrder.hasNext()) {
366                     mCurrentTest = mTestOrder.next();
367                     Log.i(TAG, "next test is: " + mCurrentTest.getClass().getSimpleName());
368                     next();
369                 } else {
370                     Log.i(TAG, "no more tests");
371                     mCurrentTest = null;
372                     getPassButton().setEnabled(true);
373                     mNm.cancelAll();
374                 }
375                 break;
376         }
377         markItem(mCurrentTest);
378     }
379 
380     /**
381      * Return to the state machine to progress through the tests.
382      */
next()383     protected void next() {
384         mHandler.removeCallbacks(mRunner);
385         mHandler.post(mRunner);
386     }
387 
388     /**
389      * Wait for things to settle before returning to the state machine.
390      */
delay()391     protected void delay() {
392         delay(3000);
393     }
394 
sleep(long time)395     protected void sleep(long time) {
396         try {
397             Thread.sleep(time);
398         } catch (InterruptedException e) {
399             e.printStackTrace();
400         }
401     }
402 
403     /**
404      * Wait for some time.
405      */
delay(long waitTime)406     protected void delay(long waitTime) {
407         mHandler.removeCallbacks(mRunner);
408         mHandler.postDelayed(mRunner, waitTime);
409     }
410 
411     // UI callbacks
412 
actionPressed(View v)413     public void actionPressed(View v) {
414         Object tag = v.getTag();
415         if (tag instanceof Integer) {
416             int id = ((Integer) tag).intValue();
417             if (mCurrentTest != null && mCurrentTest.getIntent() != null) {
418                 startActivity(mCurrentTest.getIntent());
419             } else if (id == R.string.attention_ready) {
420                 if (mCurrentTest != null) {
421                     mCurrentTest.status = READY;
422                     next();
423                 }
424             }
425             if (mCurrentTest != null) {
426                 mCurrentTest.mUserVerified = true;
427                 mCurrentTest.buttonPressed = true;
428             }
429         }
430     }
431 
actionPassed(View v)432     public void actionPassed(View v) {
433         if (mCurrentTest != null) {
434             mCurrentTest.mUserVerified = true;
435             mCurrentTest.status = PASS;
436             next();
437         }
438     }
439 
actionFailed(View v)440     public void actionFailed(View v) {
441         if (mCurrentTest != null) {
442             mCurrentTest.setFailed();
443         }
444     }
445 
446     // Utilities
447 
makeIntent(int code, String tag)448     protected PendingIntent makeIntent(int code, String tag) {
449         Intent intent = new Intent(tag);
450         intent.setComponent(new ComponentName(mContext, DismissService.class));
451         PendingIntent pi = PendingIntent.getService(mContext, code, intent,
452                 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
453         return pi;
454     }
455 
makeBroadcastIntent(int code, String tag)456     protected PendingIntent makeBroadcastIntent(int code, String tag) {
457         Intent intent = new Intent(tag);
458         intent.setComponent(new ComponentName(mContext, ActionTriggeredReceiver.class));
459         PendingIntent pi = PendingIntent.getBroadcast(mContext, code, intent,
460                 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
461         return pi;
462     }
463 
checkEquals(long[] expected, long[] actual, String message)464     protected boolean checkEquals(long[] expected, long[] actual, String message) {
465         if (Arrays.equals(expected, actual)) {
466             return true;
467         }
468         logWithStack(String.format(message, Arrays.toString(expected), Arrays.toString(actual)));
469         return false;
470     }
471 
checkEquals(Object[] expected, Object[] actual, String message)472     protected boolean checkEquals(Object[] expected, Object[] actual, String message) {
473         if (Arrays.equals(expected, actual)) {
474             return true;
475         }
476         logWithStack(String.format(message, Arrays.toString(expected), Arrays.toString(actual)));
477         return false;
478     }
479 
checkEquals(Parcelable expected, Parcelable actual, String message)480     protected boolean checkEquals(Parcelable expected, Parcelable actual, String message) {
481         if (Objects.equals(expected, actual)) {
482             return true;
483         }
484         logWithStack(String.format(message, expected, actual));
485         return false;
486     }
487 
checkEquals(boolean expected, boolean actual, String message)488     protected boolean checkEquals(boolean expected, boolean actual, String message) {
489         if (expected == actual) {
490             return true;
491         }
492         logWithStack(String.format(message, expected, actual));
493         return false;
494     }
495 
checkEquals(long expected, long actual, String message)496     protected boolean checkEquals(long expected, long actual, String message) {
497         if (expected == actual) {
498             return true;
499         }
500         logWithStack(String.format(message, expected, actual));
501         return false;
502     }
503 
checkEquals(CharSequence expected, CharSequence actual, String message)504     protected boolean checkEquals(CharSequence expected, CharSequence actual, String message) {
505         if (expected.equals(actual)) {
506             return true;
507         }
508         logWithStack(String.format(message, expected, actual));
509         return false;
510     }
511 
checkFlagSet(int expected, int actual, String message)512     protected boolean checkFlagSet(int expected, int actual, String message) {
513         if ((expected & actual) != 0) {
514             return true;
515         }
516         logWithStack(String.format(message, expected, actual));
517         return false;
518     };
519 
logWithStack(String message)520     protected void logWithStack(String message) {
521         Throwable stackTrace = new Throwable();
522         stackTrace.fillInStackTrace();
523         Log.e(TAG, message, stackTrace);
524     }
525 
526     // Common Tests: useful for the side-effects they generate
527 
528     protected class IsEnabledTest extends InteractiveTestCase {
529         @Override
inflate(ViewGroup parent)530         protected View inflate(ViewGroup parent) {
531             return createNlsSettingsItem(parent, R.string.nls_enable_service);
532         }
533 
534         @Override
autoStart()535         boolean autoStart() {
536             return true;
537         }
538 
539         @Override
test()540         protected void test() {
541             mNm.cancelAll();
542 
543             if (getIntent().resolveActivity(mPackageManager) == null) {
544                 logFail("no settings activity");
545                 status = FAIL;
546             } else {
547                 String listeners = Secure.getString(getContentResolver(),
548                         ENABLED_NOTIFICATION_LISTENERS);
549                 if (listeners != null && listeners.contains(LISTENER_PATH)) {
550                     status = PASS;
551                 } else {
552                     status = WAIT_FOR_USER;
553                 }
554                 next();
555             }
556         }
557 
558         @Override
tearDown()559         protected void tearDown() {
560             // wait for the service to start
561             delay();
562         }
563 
564         @Override
getIntent()565         protected Intent getIntent() {
566             Intent settings = new Intent(ACTION_NOTIFICATION_LISTENER_DETAIL_SETTINGS);
567             settings.putExtra(EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME,
568                     MockListener.COMPONENT_NAME.flattenToString());
569             return settings;
570         }
571     }
572 
573     protected class ServiceStartedTest extends InteractiveTestCase {
574         @Override
inflate(ViewGroup parent)575         protected View inflate(ViewGroup parent) {
576             return createAutoItem(parent, R.string.nls_service_started);
577         }
578 
579         @Override
test()580         protected void test() {
581             if (MockListener.getInstance() != null && MockListener.getInstance().isConnected) {
582                 status = PASS;
583                 next();
584             } else {
585                 logFail();
586                 status = RETEST;
587                 delay();
588             }
589         }
590     }
591 }
592