• 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 com.android.cts.verifier.notifications.MockListener.JSON_FLAGS;
20 import static com.android.cts.verifier.notifications.MockListener.JSON_ICON;
21 import static com.android.cts.verifier.notifications.MockListener.JSON_ID;
22 import static com.android.cts.verifier.notifications.MockListener.JSON_PACKAGE;
23 import static com.android.cts.verifier.notifications.MockListener.JSON_TAG;
24 import static com.android.cts.verifier.notifications.MockListener.JSON_WHEN;
25 
26 import android.annotation.SuppressLint;
27 import android.app.Activity;
28 import android.app.Notification;
29 import android.app.NotificationManager;
30 import android.app.PendingIntent;
31 import android.app.Service;
32 import android.content.ComponentName;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.pm.PackageManager;
36 import android.os.Bundle;
37 import android.os.IBinder;
38 import android.provider.Settings.Secure;
39 import android.util.Log;
40 import android.view.LayoutInflater;
41 import android.view.View;
42 import android.view.ViewGroup;
43 import android.widget.Button;
44 import android.widget.ImageView;
45 import android.widget.TextView;
46 
47 import com.android.cts.verifier.PassFailButtons;
48 import com.android.cts.verifier.R;
49 import com.android.cts.verifier.nfc.TagVerifierActivity;
50 
51 import org.json.JSONException;
52 import org.json.JSONObject;
53 
54 import java.util.HashSet;
55 import java.util.List;
56 import java.util.Set;
57 import java.util.UUID;
58 import java.util.concurrent.LinkedBlockingQueue;
59 
60 public class NotificationListenerVerifierActivity extends PassFailButtons.Activity
61 implements Runnable {
62     private static final String TAG = TagVerifierActivity.class.getSimpleName();
63     private static final String STATE = "state";
64     private static LinkedBlockingQueue<String> sDeletedQueue = new LinkedBlockingQueue<String>();
65 
66     protected static final String LISTENER_PATH = "com.android.cts.verifier/" +
67             "com.android.cts.verifier.notifications.MockListener";
68     protected static final int SETUP = 0;
69     protected static final int PASS = 1;
70     protected static final int FAIL = 2;
71     protected static final int WAIT_FOR_USER = 3;
72     protected static final int CLEARED = 4;
73     protected static final int READY = 5;
74     protected static final int RETRY = 6;
75 
76     protected static final int NOTIFICATION_ID = 1001;
77 
78     protected int mState;
79     protected int[] mStatus;
80     protected PackageManager mPackageManager;
81     protected NotificationManager mNm;
82     protected Context mContext;
83     protected Runnable mRunner;
84     protected View mHandler;
85     protected String mPackageString;
86 
87     private LayoutInflater mInflater;
88     private ViewGroup mItemList;
89 
90     private String mTag1;
91     private String mTag2;
92     private String mTag3;
93     private int mIcon1;
94     private int mIcon2;
95     private int mIcon3;
96     private int mId1;
97     private int mId2;
98     private int mId3;
99     private long mWhen1;
100     private long mWhen2;
101     private long mWhen3;
102     private int mFlag1;
103     private int mFlag2;
104     private int mFlag3;
105 
106     public static class DismissService extends Service {
107         @Override
onBind(Intent intent)108         public IBinder onBind(Intent intent) {
109             return null;
110         }
111 
112         @Override
onStart(Intent intent, int startId)113         public void onStart(Intent intent, int startId) {
114             sDeletedQueue.offer(intent.getAction());
115         }
116     }
117 
118     @Override
onCreate(Bundle savedInstanceState)119     protected void onCreate(Bundle savedInstanceState) {
120         onCreate(savedInstanceState, R.layout.nls_main);
121         setInfoResources(R.string.nls_test, R.string.nls_info, -1);
122     }
123 
onCreate(Bundle savedInstanceState, int layoutId)124     protected void onCreate(Bundle savedInstanceState, int layoutId) {
125         super.onCreate(savedInstanceState);
126 
127         if (savedInstanceState != null) {
128             mState = savedInstanceState.getInt(STATE, 0);
129         }
130         mContext = this;
131         mRunner = this;
132         mNm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
133         mPackageManager = getPackageManager();
134         mInflater = getLayoutInflater();
135         View view = mInflater.inflate(layoutId, null);
136         mItemList = (ViewGroup) view.findViewById(R.id.nls_test_items);
137         mHandler = mItemList;
138         createTestItems();
139         mStatus = new int[mItemList.getChildCount()];
140         setContentView(view);
141 
142         setPassFailButtonClickListeners();
143         getPassButton().setEnabled(false);
144     }
145 
146     @Override
onSaveInstanceState(Bundle outState)147     protected void onSaveInstanceState (Bundle outState) {
148         outState.putInt(STATE, mState);
149     }
150 
151     @Override
onResume()152     protected void onResume() {
153         super.onResume();
154         next();
155     }
156 
157     // Interface Utilities
158 
createTestItems()159     protected void createTestItems() {
160         createNlsSettingsItem(R.string.nls_enable_service);
161         createAutoItem(R.string.nls_service_started);
162         createAutoItem(R.string.nls_note_received);
163         createAutoItem(R.string.nls_payload_intact);
164         createAutoItem(R.string.nls_clear_one);
165         createAutoItem(R.string.nls_clear_all);
166         createNlsSettingsItem(R.string.nls_disable_service);
167         createAutoItem(R.string.nls_service_stopped);
168         createAutoItem(R.string.nls_note_missed);
169     }
170 
setItemState(int index, boolean passed)171     protected void setItemState(int index, boolean passed) {
172         ViewGroup item = (ViewGroup) mItemList.getChildAt(index);
173         ImageView status = (ImageView) item.findViewById(R.id.nls_status);
174         status.setImageResource(passed ? R.drawable.fs_good : R.drawable.fs_error);
175         View button = item.findViewById(R.id.nls_action_button);
176         button.setClickable(false);
177         button.setEnabled(false);
178         status.invalidate();
179     }
180 
markItemWaiting(int index)181     protected void markItemWaiting(int index) {
182         ViewGroup item = (ViewGroup) mItemList.getChildAt(index);
183         ImageView status = (ImageView) item.findViewById(R.id.nls_status);
184         status.setImageResource(R.drawable.fs_warning);
185         status.invalidate();
186     }
187 
createNlsSettingsItem(int messageId)188     protected View createNlsSettingsItem(int messageId) {
189         return createUserItem(messageId, R.string.nls_start_settings);
190     }
191 
createRetryItem(int messageId)192     protected View createRetryItem(int messageId) {
193         return createUserItem(messageId, R.string.attention_ready);
194     }
195 
createUserItem(int messageId, int actionId)196     protected View createUserItem(int messageId, int actionId) {
197         View item = mInflater.inflate(R.layout.nls_item, mItemList, false);
198         TextView instructions = (TextView) item.findViewById(R.id.nls_instructions);
199         instructions.setText(messageId);
200         Button button = (Button) item.findViewById(R.id.nls_action_button);
201         button.setText(actionId);
202         mItemList.addView(item);
203         button.setTag(actionId);
204         return item;
205     }
206 
createAutoItem(int stringId)207     protected View createAutoItem(int stringId) {
208         View item = mInflater.inflate(R.layout.nls_item, mItemList, false);
209         TextView instructions = (TextView) item.findViewById(R.id.nls_instructions);
210         instructions.setText(stringId);
211         View button = item.findViewById(R.id.nls_action_button);
212         button.setVisibility(View.GONE);
213         mItemList.addView(item);
214         return item;
215     }
216 
217     // Test management
218 
run()219     public void run() {
220         while (mState < mStatus.length && mStatus[mState] != WAIT_FOR_USER) {
221             if (mStatus[mState] == PASS) {
222                 setItemState(mState, true);
223                 mState++;
224             } else if (mStatus[mState] == FAIL) {
225                 setItemState(mState, false);
226                 return;
227             } else {
228                 break;
229             }
230         }
231 
232         if (mState < mStatus.length && mStatus[mState] == WAIT_FOR_USER) {
233             markItemWaiting(mState);
234         }
235 
236         updateStateMachine();
237     }
238 
updateStateMachine()239     protected void updateStateMachine() {
240         switch (mState) {
241             case 0:
242                 testIsEnabled(mState);
243                 break;
244             case 1:
245                 testIsStarted(mState);
246                 break;
247             case 2:
248                 testNotificationRecieved(mState);
249                 break;
250             case 3:
251                 testDataIntact(mState);
252                 break;
253             case 4:
254                 testDismissOne(mState);
255                 break;
256             case 5:
257                 testDismissAll(mState);
258                 break;
259             case 6:
260                 testIsDisabled(mState);
261                 break;
262             case 7:
263                 testIsStopped(mState);
264                 break;
265             case 8:
266                 testNotificationNotRecieved(mState);
267                 break;
268             case 9:
269                 getPassButton().setEnabled(true);
270                 mNm.cancelAll();
271                 break;
272         }
273     }
274 
launchSettings()275     public void launchSettings() {
276         startActivity(
277                 new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"));
278     }
279 
actionPressed(View v)280     public void actionPressed(View v) {
281         Object tag = v.getTag();
282         if (tag instanceof Integer) {
283             int id = ((Integer) tag).intValue();
284             if (id == R.string.nls_start_settings) {
285                 launchSettings();
286             } else if (id == R.string.attention_ready) {
287                 mStatus[mState] = READY;
288                 next();
289             }
290         }
291     }
292 
makeIntent(int code, String tag)293     protected PendingIntent makeIntent(int code, String tag) {
294         Intent intent = new Intent(tag);
295         intent.setComponent(new ComponentName(mContext, DismissService.class));
296         PendingIntent pi = PendingIntent.getService(mContext, code, intent,
297                 PendingIntent.FLAG_UPDATE_CURRENT);
298         return pi;
299     }
300 
301     @SuppressLint("NewApi")
sendNotifications()302     private void sendNotifications() {
303         mTag1 = UUID.randomUUID().toString();
304         mTag2 = UUID.randomUUID().toString();
305         mTag3 = UUID.randomUUID().toString();
306 
307         mNm.cancelAll();
308 
309         mWhen1 = System.currentTimeMillis() + 1;
310         mWhen2 = System.currentTimeMillis() + 2;
311         mWhen3 = System.currentTimeMillis() + 3;
312 
313         mIcon1 = R.drawable.fs_good;
314         mIcon2 = R.drawable.fs_error;
315         mIcon3 = R.drawable.fs_warning;
316 
317         mId1 = NOTIFICATION_ID + 1;
318         mId2 = NOTIFICATION_ID + 2;
319         mId3 = NOTIFICATION_ID + 3;
320 
321         mPackageString = "com.android.cts.verifier";
322 
323         Notification n1 = new Notification.Builder(mContext)
324         .setContentTitle("ClearTest 1")
325         .setContentText(mTag1.toString())
326         .setPriority(Notification.PRIORITY_LOW)
327         .setSmallIcon(mIcon1)
328         .setWhen(mWhen1)
329         .setDeleteIntent(makeIntent(1, mTag1))
330         .setOnlyAlertOnce(true)
331         .build();
332         mNm.notify(mTag1, mId1, n1);
333         mFlag1 = Notification.FLAG_ONLY_ALERT_ONCE;
334 
335         Notification n2 = new Notification.Builder(mContext)
336         .setContentTitle("ClearTest 2")
337         .setContentText(mTag2.toString())
338         .setPriority(Notification.PRIORITY_HIGH)
339         .setSmallIcon(mIcon2)
340         .setWhen(mWhen2)
341         .setDeleteIntent(makeIntent(2, mTag2))
342         .setAutoCancel(true)
343         .build();
344         mNm.notify(mTag2, mId2, n2);
345         mFlag2 = Notification.FLAG_AUTO_CANCEL;
346 
347         Notification n3 = new Notification.Builder(mContext)
348         .setContentTitle("ClearTest 3")
349         .setContentText(mTag3.toString())
350         .setPriority(Notification.PRIORITY_LOW)
351         .setSmallIcon(mIcon3)
352         .setWhen(mWhen3)
353         .setDeleteIntent(makeIntent(3, mTag3))
354         .setAutoCancel(true)
355         .setOnlyAlertOnce(true)
356         .build();
357         mNm.notify(mTag3, mId3, n3);
358         mFlag3 = Notification.FLAG_ONLY_ALERT_ONCE | Notification.FLAG_AUTO_CANCEL;
359     }
360 
361     /**
362      * Return to the state machine to progress through the tests.
363      */
next()364     protected void next() {
365         mHandler.removeCallbacks(mRunner);
366         mHandler.post(mRunner);
367     }
368 
369     /**
370      * Wait for things to settle before returning to the state machine.
371      */
delay()372     protected void delay() {
373         delay(2000);
374     }
375 
376     /**
377      * Wait for some time.
378      */
delay(long waitTime)379     protected void delay(long waitTime) {
380         mHandler.removeCallbacks(mRunner);
381         mHandler.postDelayed(mRunner, waitTime);
382     }
383 
checkEquals(long expected, long actual, String message)384     protected boolean checkEquals(long expected, long actual, String message) {
385         if (expected == actual) {
386             return true;
387         }
388         logWithStack(String.format(message, expected, actual));
389         return false;
390     }
391 
checkEquals(String expected, String actual, String message)392     protected boolean checkEquals(String expected, String actual, String message) {
393         if (expected.equals(actual)) {
394             return true;
395         }
396         logWithStack(String.format(message, expected, actual));
397         return false;
398     }
399 
checkFlagSet(int expected, int actual, String message)400     protected boolean checkFlagSet(int expected, int actual, String message) {
401         if ((expected & actual) != 0) {
402             return true;
403         }
404         logWithStack(String.format(message, expected, actual));
405         return false;
406     };
407 
logWithStack(String message)408     protected void logWithStack(String message) {
409         Throwable stackTrace = new Throwable();
410         stackTrace.fillInStackTrace();
411         Log.e(TAG, message, stackTrace);
412     }
413 
414     // Tests
415 
testIsEnabled(int i)416     private void testIsEnabled(int i) {
417         // no setup required
418         Intent settings = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
419         if (settings.resolveActivity(mPackageManager) == null) {
420             logWithStack("failed testIsEnabled: no settings activity");
421             mStatus[i] = FAIL;
422         } else {
423             // TODO: find out why Secure.ENABLED_NOTIFICATION_LISTENERS is hidden
424             String listeners = Secure.getString(getContentResolver(),
425                     "enabled_notification_listeners");
426             if (listeners != null && listeners.contains(LISTENER_PATH)) {
427                 mStatus[i] = PASS;
428             } else {
429                 mStatus[i] = WAIT_FOR_USER;
430             }
431         }
432         next();
433     }
434 
testIsStarted(final int i)435     private void testIsStarted(final int i) {
436         if (mStatus[i] == SETUP) {
437             mStatus[i] = READY;
438             // wait for the service to start
439             delay();
440         } else {
441             MockListener.probeListenerStatus(mContext,
442                     new MockListener.StatusCatcher() {
443                 @Override
444                 public void accept(int result) {
445                     if (result == Activity.RESULT_OK) {
446                         mStatus[i] = PASS;
447                     } else {
448                         logWithStack("failed testIsStarted: " + result);
449                         mStatus[i] = FAIL;
450                     }
451                     next();
452                 }
453             });
454         }
455     }
456 
testNotificationRecieved(final int i)457     private void testNotificationRecieved(final int i) {
458         if (mStatus[i] == SETUP) {
459             MockListener.resetListenerData(this);
460             mStatus[i] = CLEARED;
461             // wait for intent to move through the system
462             delay();
463         } else if (mStatus[i] == CLEARED) {
464             sendNotifications();
465             mStatus[i] = READY;
466             // wait for notifications to move through the system
467             delay();
468         } else {
469             MockListener.probeListenerPosted(mContext,
470                     new MockListener.StringListResultCatcher() {
471                 @Override
472                 public void accept(List<String> result) {
473                     if (result != null && result.size() > 0 && result.contains(mTag1)) {
474                         mStatus[i] = PASS;
475                     } else {
476                         logWithStack("failed testNotificationRecieved");
477                         mStatus[i] = FAIL;
478                     }
479                     next();
480                 }});
481         }
482     }
483 
testDataIntact(final int i)484     private void testDataIntact(final int i) {
485         // no setup required
486         MockListener.probeListenerPayloads(mContext,
487                 new MockListener.StringListResultCatcher() {
488             @Override
489             public void accept(List<String> result) {
490                 boolean pass = false;
491                 Set<String> found = new HashSet<String>();
492                 if (result != null && result.size() > 0) {
493                     pass = true;
494                     for(String payloadData : result) {
495                         try {
496                             JSONObject payload = new JSONObject(payloadData);
497                             pass &= checkEquals(mPackageString, payload.getString(JSON_PACKAGE),
498                                     "data integrity test fail: notification package (%s, %s)");
499                             String tag = payload.getString(JSON_TAG);
500                             if (mTag1.equals(tag)) {
501                                 found.add(mTag1);
502                                 pass &= checkEquals(mIcon1, payload.getInt(JSON_ICON),
503                                         "data integrity test fail: notification icon (%d, %d)");
504                                 pass &= checkFlagSet(mFlag1, payload.getInt(JSON_FLAGS),
505                                         "data integrity test fail: notification flags (%d, %d)");
506                                 pass &= checkEquals(mId1, payload.getInt(JSON_ID),
507                                         "data integrity test fail: notification ID (%d, %d)");
508                                 pass &= checkEquals(mWhen1, payload.getLong(JSON_WHEN),
509                                         "data integrity test fail: notification when (%d, %d)");
510                             } else if (mTag2.equals(tag)) {
511                                 found.add(mTag2);
512                                 pass &= checkEquals(mIcon2, payload.getInt(JSON_ICON),
513                                         "data integrity test fail: notification icon (%d, %d)");
514                                 pass &= checkFlagSet(mFlag2, payload.getInt(JSON_FLAGS),
515                                         "data integrity test fail: notification flags (%d, %d)");
516                                 pass &= checkEquals(mId2, payload.getInt(JSON_ID),
517                                         "data integrity test fail: notification ID (%d, %d)");
518                                 pass &= checkEquals(mWhen2, payload.getLong(JSON_WHEN),
519                                         "data integrity test fail: notification when (%d, %d)");
520                             } else if (mTag3.equals(tag)) {
521                                 found.add(mTag3);
522                                 pass &= checkEquals(mIcon3, payload.getInt(JSON_ICON),
523                                         "data integrity test fail: notification icon (%d, %d)");
524                                 pass &= checkFlagSet(mFlag3, payload.getInt(JSON_FLAGS),
525                                         "data integrity test fail: notification flags (%d, %d)");
526                                 pass &= checkEquals(mId3, payload.getInt(JSON_ID),
527                                         "data integrity test fail: notification ID (%d, %d)");
528                                 pass &= checkEquals(mWhen3, payload.getLong(JSON_WHEN),
529                                         "data integrity test fail: notification when (%d, %d)");
530                             } else {
531                                 pass = false;
532                                 logWithStack("failed on unexpected notification tag: " + tag);
533                             }
534                         } catch (JSONException e) {
535                             pass = false;
536                             Log.e(TAG, "failed to unpack data from mocklistener", e);
537                         }
538                     }
539                 }
540                 pass &= found.size() == 3;
541                 mStatus[i] = pass ? PASS : FAIL;
542                 next();
543             }});
544     }
545 
testDismissOne(final int i)546     private void testDismissOne(final int i) {
547         if (mStatus[i] == SETUP) {
548             MockListener.resetListenerData(this);
549             mStatus[i] = CLEARED;
550             // wait for intent to move through the system
551             delay();
552         } else if (mStatus[i] == CLEARED) {
553             MockListener.clearOne(mContext, mTag1, NOTIFICATION_ID + 1);
554             mStatus[i] = READY;
555             delay();
556         } else {
557             MockListener.probeListenerRemoved(mContext,
558                     new MockListener.StringListResultCatcher() {
559                 @Override
560                 public void accept(List<String> result) {
561                     if (result != null && result.size() > 0 && result.contains(mTag1)) {
562                         mStatus[i] = PASS;
563                         next();
564                     } else {
565                         if (mStatus[i] == RETRY) {
566                             logWithStack("failed testDismissOne");
567                             mStatus[i] = FAIL;
568                             next();
569                         } else {
570                             logWithStack("failed testDismissOne, once: retrying");
571                             mStatus[i] = RETRY;
572                             delay();
573                         }
574                     }
575                 }});
576         }
577     }
578 
testDismissAll(final int i)579     private void testDismissAll(final int i) {
580         if (mStatus[i] == SETUP) {
581             MockListener.resetListenerData(this);
582             mStatus[i] = CLEARED;
583             // wait for intent to move through the system
584             delay();
585         } else if (mStatus[i] == CLEARED) {
586             MockListener.clearAll(mContext);
587             mStatus[i] = READY;
588             delay();
589         } else {
590             MockListener.probeListenerRemoved(mContext,
591                     new MockListener.StringListResultCatcher() {
592                 @Override
593                 public void accept(List<String> result) {
594                     if (result != null && result.size() == 2
595                             && result.contains(mTag2) && result.contains(mTag3)) {
596                         mStatus[i] = PASS;
597                         next();
598                     } else {
599                         if (mStatus[i] == RETRY) {
600                             logWithStack("failed testDismissAll");
601                             mStatus[i] = FAIL;
602                             next();
603                         } else {
604                             logWithStack("failed testDismissAll, once: retrying");
605                             mStatus[i] = RETRY;
606                             delay();
607                         }
608                     }
609                 }
610             });
611         }
612     }
613 
testIsDisabled(int i)614     private void testIsDisabled(int i) {
615         // no setup required
616         // TODO: find out why Secure.ENABLED_NOTIFICATION_LISTENERS is hidden
617         String listeners = Secure.getString(getContentResolver(),
618                 "enabled_notification_listeners");
619         if (listeners == null || !listeners.contains(LISTENER_PATH)) {
620             mStatus[i] = PASS;
621             next();
622         } else {
623             mStatus[i] = WAIT_FOR_USER;
624             delay();
625         }
626     }
627 
testIsStopped(final int i)628     private void testIsStopped(final int i) {
629         if (mStatus[i] == SETUP) {
630             mStatus[i] = READY;
631             // wait for the service to start
632             delay();
633         } else {
634             MockListener.probeListenerStatus(mContext,
635                     new MockListener.StatusCatcher() {
636                 @Override
637                 public void accept(int result) {
638                     if (result == Activity.RESULT_OK) {
639                         logWithStack("failed testIsStopped");
640                         mStatus[i] = FAIL;
641                     } else {
642                         mStatus[i] = PASS;
643                     }
644                     next();
645                 }
646             });
647         }
648     }
649 
testNotificationNotRecieved(final int i)650     private void testNotificationNotRecieved(final int i) {
651         if (mStatus[i] == SETUP) {
652             MockListener.resetListenerData(this);
653             mStatus[i] = CLEARED;
654             // wait for intent to move through the system
655             delay();
656         } else if (mStatus[i] == CLEARED) {
657             // setup for testNotificationRecieved
658             sendNotifications();
659             mStatus[i] = READY;
660             delay();
661         } else {
662             MockListener.probeListenerPosted(mContext,
663                     new MockListener.StringListResultCatcher() {
664                 @Override
665                 public void accept(List<String> result) {
666                     if (result == null || result.size() == 0) {
667                         mStatus[i] = PASS;
668                     } else {
669                         logWithStack("failed testNotificationNotRecieved");
670                         mStatus[i] = FAIL;
671                     }
672                     next();
673                 }});
674         }
675     }
676 }
677