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