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.app.Notification.VISIBILITY_PRIVATE;
20 import static android.app.Notification.VISIBILITY_PUBLIC;
21 import static android.app.NotificationManager.IMPORTANCE_LOW;
22 import static android.app.NotificationManager.IMPORTANCE_MAX;
23 import static android.app.NotificationManager.IMPORTANCE_NONE;
24 import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
25 import static android.provider.Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS;
26 import static android.provider.Settings.EXTRA_APP_PACKAGE;
27 import static android.provider.Settings.EXTRA_CHANNEL_ID;
28 
29 import static com.android.cts.verifier.notifications.MockListener.JSON_FLAGS;
30 import static com.android.cts.verifier.notifications.MockListener.JSON_ICON;
31 import static com.android.cts.verifier.notifications.MockListener.JSON_ID;
32 import static com.android.cts.verifier.notifications.MockListener.JSON_LAST_AUDIBLY_ALERTED;
33 import static com.android.cts.verifier.notifications.MockListener.JSON_PACKAGE;
34 import static com.android.cts.verifier.notifications.MockListener.JSON_REASON;
35 import static com.android.cts.verifier.notifications.MockListener.JSON_STATS;
36 import static com.android.cts.verifier.notifications.MockListener.JSON_TAG;
37 import static com.android.cts.verifier.notifications.MockListener.JSON_WHEN;
38 import static com.android.cts.verifier.notifications.MockListener.REASON_LISTENER_CANCEL;
39 
40 import android.annotation.SuppressLint;
41 import android.app.KeyguardManager;
42 import android.app.Notification;
43 import android.app.NotificationChannel;
44 import android.app.NotificationChannelGroup;
45 import android.app.Person;
46 import android.content.Context;
47 import android.content.Intent;
48 import android.content.SharedPreferences;
49 import android.content.pm.PackageManager;
50 import android.content.pm.ShortcutInfo;
51 import android.content.pm.ShortcutManager;
52 import android.graphics.drawable.Icon;
53 import android.os.Bundle;
54 import android.os.SystemClock;
55 import android.provider.Settings;
56 import android.provider.Settings.Secure;
57 import android.service.notification.NotificationListenerService;
58 import android.service.notification.StatusBarNotification;
59 import android.util.ArraySet;
60 import android.util.Log;
61 import android.view.View;
62 import android.view.ViewGroup;
63 import android.widget.Button;
64 import android.widget.RemoteViews;
65 
66 import androidx.core.app.NotificationCompat;
67 
68 import com.android.cts.verifier.R;
69 
70 import org.json.JSONException;
71 import org.json.JSONObject;
72 
73 import java.util.ArrayList;
74 import java.util.Arrays;
75 import java.util.HashSet;
76 import java.util.List;
77 import java.util.Set;
78 import java.util.UUID;
79 
80 public class NotificationListenerVerifierActivity extends InteractiveVerifierActivity
81         implements Runnable {
82     static final String TAG = "NoListenerVerifier";
83     private static final String NOTIFICATION_CHANNEL_ID = TAG;
84     private static final String NOISY_NOTIFICATION_CHANNEL_ID = TAG + "noisy";
85     protected static final String PREFS = "listener_prefs";
86     final int NUM_NOTIFICATIONS_SENT = 3; // # notifications sent by sendNotifications()
87 
88     private String mTag1;
89     private String mTag2;
90     private String mTag3;
91     private String mTag4;
92     private int mIcon1;
93     private int mIcon2;
94     private int mIcon3;
95     private int mIcon4;
96     private int mId1;
97     private int mId2;
98     private int mId3;
99     private int mId4;
100     private long mWhen1;
101     private long mWhen2;
102     private long mWhen3;
103     private long mWhen4;
104     private int mFlag1;
105     private int mFlag2;
106     private int mFlag3;
107 
108     @Override
getTitleResource()109     protected int getTitleResource() {
110         return R.string.nls_test;
111     }
112 
113     @Override
getInstructionsResource()114     protected int getInstructionsResource() {
115         return R.string.nls_info;
116     }
117 
118     // Test Setup
119 
120     @Override
createTestItems()121     protected List<InteractiveTestCase> createTestItems() {
122         boolean isAutomotive = getPackageManager().hasSystemFeature(
123                 PackageManager.FEATURE_AUTOMOTIVE);
124         List<InteractiveTestCase> tests = new ArrayList<>(17);
125         tests.add(new IsEnabledTest());
126         tests.add(new ServiceStartedTest());
127         tests.add(new NotificationReceivedTest());
128         if (!isAutomotive) {
129             tests.add(new SendUserToChangeFilter());
130             tests.add(new AskIfFilterChanged());
131             tests.add(new NotificationTypeFilterTest());
132             tests.add(new ResetChangeFilter());
133         }
134         tests.add(new LongMessageTest());
135         tests.add(new DataIntactTest());
136         tests.add(new AudiblyAlertedTest());
137         tests.add(new DismissOneTest());
138         tests.add(new DismissOneWithReasonTest());
139         tests.add(new DismissOneWithStatsTest());
140         tests.add(new DismissAllTest());
141         tests.add(new SnoozeNotificationForTimeTest());
142         tests.add(new SnoozeNotificationForTimeCancelTest());
143         tests.add(new GetSnoozedNotificationTest());
144         tests.add(new EnableHintsTest());
145         if (!isAutomotive) {
146             tests.add(new LockscreenVisibilityTest());
147         }
148         tests.add(new ReceiveAppBlockNoticeTest());
149         tests.add(new ReceiveAppUnblockNoticeTest());
150         if (!isAutomotive) {
151             tests.add(new ReceiveChannelBlockNoticeTest());
152             tests.add(new ReceiveGroupBlockNoticeTest());
153         }
154         tests.add(new RequestUnbindTest());
155         tests.add(new RequestBindTest());
156         tests.add(new MessageBundleTest());
157         tests.add(new ConversationOrderingTest());
158         tests.add(new EnableHintsTest());
159         tests.add(new IsDisabledTest());
160         tests.add(new ServiceStoppedTest());
161         tests.add(new NotificationNotReceivedTest());
162         if (!isAutomotive) {
163             tests.add(new RestoreLockscreenVisibilityTest());
164             tests.add(new AddScreenLockTest());
165             tests.add(new SecureActionOnLockScreenTest());
166             tests.add(new RemoveScreenLockTest());
167         }
168         return tests;
169     }
170 
createChannels()171     private void createChannels() {
172         NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
173                 NOTIFICATION_CHANNEL_ID, IMPORTANCE_LOW);
174         NotificationChannel noisyChannel = new NotificationChannel(NOISY_NOTIFICATION_CHANNEL_ID,
175                 NOISY_NOTIFICATION_CHANNEL_ID, IMPORTANCE_MAX);
176         noisyChannel.setVibrationPattern(new long[]{100, 0, 100});
177         mNm.createNotificationChannel(channel);
178         mNm.createNotificationChannel(noisyChannel);
179     }
180 
deleteChannels()181     private void deleteChannels() {
182         mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
183         mNm.deleteNotificationChannel(NOISY_NOTIFICATION_CHANNEL_ID);
184     }
185 
186     @SuppressLint("NewApi")
sendNotifications()187     private void sendNotifications() {
188         mTag1 = UUID.randomUUID().toString();
189         Log.d(TAG, "Sending #1: " + mTag1);
190         mTag2 = UUID.randomUUID().toString();
191         Log.d(TAG, "Sending #2: " + mTag2);
192         mTag3 = UUID.randomUUID().toString();
193         Log.d(TAG, "Sending #3: " + mTag3);
194 
195         mWhen1 = System.currentTimeMillis() + 1;
196         mWhen2 = System.currentTimeMillis() + 2;
197         mWhen3 = System.currentTimeMillis() + 3;
198 
199         mIcon1 = R.drawable.ic_stat_alice;
200         mIcon2 = R.drawable.ic_stat_bob;
201         mIcon3 = R.drawable.ic_stat_charlie;
202 
203         mId1 = NOTIFICATION_ID + 1;
204         mId2 = NOTIFICATION_ID + 2;
205         mId3 = NOTIFICATION_ID + 3;
206 
207         mPackageString = "com.android.cts.verifier";
208 
209         Notification n1 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
210                 .setContentTitle("ClearTest 1")
211                 .setContentText(mTag1)
212                 .setSmallIcon(mIcon1)
213                 .setWhen(mWhen1)
214                 .setDeleteIntent(makeIntent(1, mTag1))
215                 .setOnlyAlertOnce(true)
216                 .build();
217         mNm.notify(mTag1, mId1, n1);
218         mFlag1 = Notification.FLAG_ONLY_ALERT_ONCE;
219 
220         Notification n2 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
221                 .setContentTitle("ClearTest 2")
222                 .setContentText(mTag2)
223                 .setSmallIcon(mIcon2)
224                 .setWhen(mWhen2)
225                 .setDeleteIntent(makeIntent(2, mTag2))
226                 .setAutoCancel(true)
227                 .build();
228         mNm.notify(mTag2, mId2, n2);
229         mFlag2 = Notification.FLAG_AUTO_CANCEL;
230 
231         Notification n3 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
232                 .setContentTitle("ClearTest 3")
233                 .setContentText(mTag3)
234                 .setSmallIcon(mIcon3)
235                 .setWhen(mWhen3)
236                 .setDeleteIntent(makeIntent(3, mTag3))
237                 .setAutoCancel(true)
238                 .setOnlyAlertOnce(true)
239                 .build();
240         mNm.notify(mTag3, mId3, n3);
241         mFlag3 = Notification.FLAG_ONLY_ALERT_ONCE | Notification.FLAG_AUTO_CANCEL;
242     }
243 
sendNoisyNotification()244     private void sendNoisyNotification() {
245         mTag4 = UUID.randomUUID().toString();
246         Log.d(TAG, "Sending noisy notif: " + mTag4);
247 
248         mWhen4 = System.currentTimeMillis() + 4;
249         mIcon4 = R.drawable.ic_stat_charlie;
250         mId4 = NOTIFICATION_ID + 4;
251         mPackageString = "com.android.cts.verifier";
252 
253         Notification n1 = new Notification.Builder(mContext, NOISY_NOTIFICATION_CHANNEL_ID)
254                 .setContentTitle("NoisyTest 1")
255                 .setContentText(mTag4)
256                 .setSmallIcon(mIcon4)
257                 .setWhen(mWhen4)
258                 .setDeleteIntent(makeIntent(4, mTag4))
259                 .setCategory(Notification.CATEGORY_REMINDER)
260                 .build();
261         mNm.notify(mTag4, mId4, n1);
262     }
263 
264     // Tests
265     private class NotificationReceivedTest extends InteractiveTestCase {
266         @Override
inflate(ViewGroup parent)267         protected View inflate(ViewGroup parent) {
268             return createAutoItem(parent, R.string.nls_note_received);
269 
270         }
271 
272         @Override
setUp()273         protected void setUp() {
274             createChannels();
275             sendNotifications();
276             status = READY;
277         }
278 
279         @Override
tearDown()280         protected void tearDown() {
281             mNm.cancelAll();
282             MockListener.getInstance().resetData();
283             deleteChannels();
284         }
285 
286         @Override
test()287         protected void test() {
288             if (MockListener.getInstance().getPosted(mTag1) != null) {
289                 status = PASS;
290             } else {
291                 logFail();
292                 status = FAIL;
293             }
294         }
295     }
296 
297     private class LongMessageTest extends InteractiveTestCase {
298         private ViewGroup mParent;
299         @Override
inflate(ViewGroup parent)300         protected View inflate(ViewGroup parent) {
301             mParent = createAutoItem(parent, R.string.nls_anr);
302             return mParent;
303         }
304 
305         @Override
setUp()306         protected void setUp() {
307             createChannels();
308             StringBuilder sb = new StringBuilder();
309             for (int i = 0; i < 20000; i++) {
310                 sb.append("\u2009\u200a" + "\u200E\u200F" + "stuff");
311             }
312             Notification.Builder builder = new Notification.Builder(
313                     mContext, NOTIFICATION_CHANNEL_ID)
314                     .setSmallIcon(R.drawable.ic_stat_alice)
315                     .setContentTitle("This is an long notification")
316                     .setContentText("Innocuous content")
317                     .setStyle(new Notification.MessagingStyle("Fake person")
318                             .addMessage("hey how is it goin", 0, "Person 1")
319                             .addMessage("hey", 0, "Person 1")
320                             .addMessage("u there", 0, "Person 1")
321                             .addMessage("how you like tHIS", 0, "Person 1")
322                             .addMessage(sb.toString(), 0, "Person 1")
323                     );
324             mTag1 = UUID.randomUUID().toString();
325             mId1 = NOTIFICATION_ID + 1;
326             mPackageString = "com.android.cts.verifier";
327             mNm.notify(mTag1, mId1, builder.build());
328             status = READY;
329         }
330 
331         @Override
tearDown()332         protected void tearDown() {
333             mNm.cancelAll();
334             MockListener.getInstance().resetData();
335             deleteChannels();
336         }
337 
338         @Override
test()339         protected void test() {
340             StatusBarNotification sbn = MockListener.getInstance().getPosted(mTag1);
341             if (sbn == null) {
342                 logFail();
343                 status = FAIL;
344             } else {
345                 ViewGroup parent = mParent.findViewById(R.id.feedback);
346                 parent.setVisibility(View.VISIBLE);
347                 final Notification.Builder recoveredBuilder = Notification.Builder.recoverBuilder(
348                         NotificationListenerVerifierActivity.this,
349                         sbn.getNotification());
350                 RemoteViews rv = recoveredBuilder.createContentView();
351                 View v = rv.apply(NotificationListenerVerifierActivity.this, parent);
352                 parent.addView(v);
353             }
354             if (MockListener.getInstance().getPosted(mTag1) != null) {
355                 status = PASS;
356             } else {
357                 logFail();
358                 status = FAIL;
359             }
360         }
361     }
362 
363     /**
364      * Creates a notification channel. Sends the user to settings to block the channel. Waits
365      * to receive the broadcast that the channel was blocked, and confirms that the broadcast
366      * contains the correct extras.
367      */
368     protected class ReceiveChannelBlockNoticeTest extends InteractiveTestCase {
369         private String mChannelId;
370         private int mRetries = 2;
371         private View mView;
372         @Override
inflate(ViewGroup parent)373         protected View inflate(ViewGroup parent) {
374             mView = createNlsSettingsItem(parent, R.string.nls_block_channel);
375             Button button = mView.findViewById(R.id.nls_action_button);
376             button.setEnabled(false);
377             return mView;
378         }
379 
380         @Override
setUp()381         protected void setUp() {
382             mChannelId = UUID.randomUUID().toString();
383             NotificationChannel channel = new NotificationChannel(
384                     mChannelId, "ReceiveChannelBlockNoticeTest", IMPORTANCE_LOW);
385             mNm.createNotificationChannel(channel);
386             status = READY;
387             Button button = mView.findViewById(R.id.nls_action_button);
388             button.setEnabled(true);
389         }
390 
391         @Override
autoStart()392         boolean autoStart() {
393             return true;
394         }
395 
396         @Override
test()397         protected void test() {
398             NotificationChannel channel = mNm.getNotificationChannel(mChannelId);
399             SharedPreferences prefs = mContext.getSharedPreferences(
400                     NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
401 
402             if (channel.getImportance() == IMPORTANCE_NONE) {
403                 if (prefs.contains(mChannelId) && prefs.getBoolean(mChannelId, false)) {
404                     status = PASS;
405                 } else {
406                     if (mRetries > 0) {
407                         mRetries--;
408                         status = RETEST;
409                     } else {
410                         status = FAIL;
411                     }
412                 }
413             } else {
414                 // user hasn't jumped to settings to block the channel yet
415                 status = WAIT_FOR_USER;
416             }
417 
418             next();
419         }
420 
tearDown()421         protected void tearDown() {
422             MockListener.getInstance().resetData();
423             mNm.deleteNotificationChannel(mChannelId);
424             SharedPreferences prefs = mContext.getSharedPreferences(
425                     NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
426             SharedPreferences.Editor editor = prefs.edit();
427             editor.remove(mChannelId);
428         }
429 
430         @Override
getIntent()431         protected Intent getIntent() {
432          return new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
433                  .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName())
434                  .putExtra(EXTRA_CHANNEL_ID, mChannelId);
435         }
436     }
437 
438     /**
439      * Creates a notification channel group. Sends the user to settings to block the group. Waits
440      * to receive the broadcast that the group was blocked, and confirms that the broadcast contains
441      * the correct extras.
442      */
443     protected class ReceiveGroupBlockNoticeTest extends InteractiveTestCase {
444         private String mGroupId;
445         private int mRetries = 2;
446         private View mView;
447         @Override
inflate(ViewGroup parent)448         protected View inflate(ViewGroup parent) {
449             mView = createNlsSettingsItem(parent, R.string.nls_block_group);
450             Button button = mView.findViewById(R.id.nls_action_button);
451             button.setEnabled(false);
452             return mView;
453         }
454 
455         @Override
setUp()456         protected void setUp() {
457             mGroupId = UUID.randomUUID().toString();
458             NotificationChannelGroup group
459                     = new NotificationChannelGroup(mGroupId, "ReceiveChannelGroupBlockNoticeTest");
460             mNm.createNotificationChannelGroup(group);
461             NotificationChannel channel = new NotificationChannel(
462                     mGroupId, "ReceiveChannelBlockNoticeTest", IMPORTANCE_LOW);
463             channel.setGroup(mGroupId);
464             mNm.createNotificationChannel(channel);
465             status = READY;
466             Button button = mView.findViewById(R.id.nls_action_button);
467             button.setEnabled(true);
468         }
469 
470         @Override
autoStart()471         boolean autoStart() {
472             return true;
473         }
474 
475         @Override
test()476         protected void test() {
477             NotificationChannelGroup group = mNm.getNotificationChannelGroup(mGroupId);
478             SharedPreferences prefs = mContext.getSharedPreferences(
479                     NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
480 
481             if (group.isBlocked()) {
482                 if (prefs.contains(mGroupId) && prefs.getBoolean(mGroupId, false)) {
483                     status = PASS;
484                 } else {
485                     if (mRetries > 0) {
486                         mRetries--;
487                         status = RETEST;
488                     } else {
489                         status = FAIL;
490                     }
491                 }
492             } else {
493                 // user hasn't jumped to settings to block the group yet
494                 status = WAIT_FOR_USER;
495             }
496 
497             next();
498         }
499 
tearDown()500         protected void tearDown() {
501             MockListener.getInstance().resetData();
502             mNm.deleteNotificationChannelGroup(mGroupId);
503             SharedPreferences prefs = mContext.getSharedPreferences(
504                     NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
505             SharedPreferences.Editor editor = prefs.edit();
506             editor.remove(mGroupId);
507         }
508 
509         @Override
getIntent()510         protected Intent getIntent() {
511             return new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
512                     .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
513                     .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName());
514         }
515     }
516 
517     /**
518      * Creates a notification channel. Sends the user to settings to disallow the channel from
519      * showing on the lockscreen. Sends a notification, checks the lockscreen setting in the
520      * ranking object.
521      */
522     protected class LockscreenVisibilityTest extends InteractiveTestCase {
523         private int mRetries = 3;
524         private View mView;
525         @Override
inflate(ViewGroup parent)526         protected View inflate(ViewGroup parent) {
527             mView = createNlsSettingsItem(parent, R.string.nls_visibility);
528             Button button = mView.findViewById(R.id.nls_action_button);
529             button.setEnabled(false);
530             return mView;
531         }
532 
533         @Override
setUp()534         protected void setUp() {
535             createChannels();
536             status = READY;
537             Button button = mView.findViewById(R.id.nls_action_button);
538             button.setEnabled(true);
539         }
540 
541         @Override
autoStart()542         boolean autoStart() {
543             return true;
544         }
545 
546         @Override
test()547         protected void test() {
548             NotificationChannel channel = mNm.getNotificationChannel(NOTIFICATION_CHANNEL_ID);
549             if (channel.getLockscreenVisibility() == VISIBILITY_PRIVATE) {
550                 if (mRetries == 3) {
551                     sendNotifications();
552                 }
553 
554                 NotificationListenerService.Ranking rank =
555                         new NotificationListenerService.Ranking();
556                 StatusBarNotification sbn = MockListener.getInstance().getPosted(mTag1);
557                 if (sbn != null) {
558                     MockListener.getInstance().getCurrentRanking().getRanking(sbn.getKey(), rank);
559                     if (rank.getLockscreenVisibilityOverride() == VISIBILITY_PRIVATE) {
560                         status = PASS;
561                     } else {
562                         logFail("Actual visibility:" + rank.getLockscreenVisibilityOverride());
563                         status = FAIL;
564                     }
565                 } else {
566                     if (mRetries > 0) {
567                         mRetries--;
568                         status = RETEST;
569                     } else {
570                         logFail("Notification wasn't posted");
571                         status = FAIL;
572                     }
573                 }
574 
575             } else {
576                 // user hasn't jumped to settings  yet
577                 status = WAIT_FOR_USER;
578             }
579 
580             next();
581         }
582 
tearDown()583         protected void tearDown() {
584             MockListener.getInstance().resetData();
585             deleteChannels();
586         }
587 
588         @Override
getIntent()589         protected Intent getIntent() {
590             return new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
591                     .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName())
592                     .putExtra(EXTRA_CHANNEL_ID, NOTIFICATION_CHANNEL_ID);
593         }
594     }
595 
596     /**
597      * Sends the user to settings to block the app. Waits to receive the broadcast that the app was
598      * blocked, and confirms that the broadcast contains the correct extras.
599      */
600     protected class ReceiveAppBlockNoticeTest extends InteractiveTestCase {
601         private int mRetries = 2;
602         private View mView;
603         @Override
inflate(ViewGroup parent)604         protected View inflate(ViewGroup parent) {
605             mView = createNlsSettingsItem(parent, R.string.nls_block_app);
606             Button button = mView.findViewById(R.id.nls_action_button);
607             button.setEnabled(false);
608             return mView;
609         }
610 
611         @Override
setUp()612         protected void setUp() {
613             status = READY;
614             Button button = mView.findViewById(R.id.nls_action_button);
615             button.setEnabled(true);
616         }
617 
618         @Override
autoStart()619         boolean autoStart() {
620             return true;
621         }
622 
623         @Override
test()624         protected void test() {
625             SharedPreferences prefs = mContext.getSharedPreferences(
626                     NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
627 
628             if (!mNm.areNotificationsEnabled()) {
629                 Log.d(TAG, "Got broadcast " + prefs.contains(mContext.getPackageName()));
630                 Log.d(TAG, "Broadcast contains correct data? " +
631                         prefs.getBoolean(mContext.getPackageName(), false));
632                 if (prefs.contains(mContext.getPackageName())
633                         && prefs.getBoolean(mContext.getPackageName(), false)) {
634                     status = PASS;
635                 } else {
636                     if (mRetries > 0) {
637                         mRetries--;
638                         status = RETEST;
639                     } else {
640                         status = FAIL;
641                     }
642                 }
643             } else {
644                 Log.d(TAG, "Notifications still enabled");
645                 // user hasn't jumped to settings to block the app yet
646                 status = WAIT_FOR_USER;
647             }
648 
649             next();
650         }
651 
tearDown()652         protected void tearDown() {
653             MockListener.getInstance().resetData();
654             SharedPreferences prefs = mContext.getSharedPreferences(
655                     NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
656             SharedPreferences.Editor editor = prefs.edit();
657             editor.remove(mContext.getPackageName());
658         }
659 
660         @Override
getIntent()661         protected Intent getIntent() {
662             return new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
663                     .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
664                     .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName());
665         }
666     }
667 
668     /**
669      * Sends the user to settings to unblock the app. Waits to receive the broadcast that the app
670      * was unblocked, and confirms that the broadcast contains the correct extras.
671      */
672     protected class ReceiveAppUnblockNoticeTest extends InteractiveTestCase {
673         private int mRetries = 2;
674         private View mView;
675         @Override
inflate(ViewGroup parent)676         protected View inflate(ViewGroup parent) {
677             mView = createNlsSettingsItem(parent, R.string.nls_unblock_app);
678             Button button = mView.findViewById(R.id.nls_action_button);
679             button.setEnabled(false);
680             return mView;
681         }
682 
683         @Override
setUp()684         protected void setUp() {
685             status = READY;
686             Button button = mView.findViewById(R.id.nls_action_button);
687             button.setEnabled(true);
688         }
689 
690         @Override
autoStart()691         boolean autoStart() {
692             return true;
693         }
694 
695         @Override
test()696         protected void test() {
697             SharedPreferences prefs = mContext.getSharedPreferences(
698                     NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
699 
700             if (mNm.areNotificationsEnabled()) {
701                 if (prefs.contains(mContext.getPackageName())
702                         && !prefs.getBoolean(mContext.getPackageName(), true)) {
703                     status = PASS;
704                 } else {
705                     if (mRetries > 0) {
706                         mRetries--;
707                         status = RETEST;
708                     } else {
709                         status = FAIL;
710                     }
711                 }
712             } else {
713                 // user hasn't jumped to settings to block the app yet
714                 status = WAIT_FOR_USER;
715             }
716 
717             next();
718         }
719 
tearDown()720         protected void tearDown() {
721             MockListener.getInstance().resetData();
722             SharedPreferences prefs = mContext.getSharedPreferences(
723                     NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
724             SharedPreferences.Editor editor = prefs.edit();
725             editor.remove(mContext.getPackageName());
726         }
727 
728         @Override
getIntent()729         protected Intent getIntent() {
730             return new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
731                     .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
732                     .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName());
733         }
734     }
735 
736     private class DataIntactTest extends InteractiveTestCase {
737         @Override
inflate(ViewGroup parent)738         protected View inflate(ViewGroup parent) {
739             return createAutoItem(parent, R.string.nls_payload_intact);
740         }
741 
742         @Override
setUp()743         protected void setUp() {
744             createChannels();
745             sendNotifications();
746             status = READY;
747         }
748 
749         @Override
test()750         protected void test() {
751             List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted());
752 
753             Set<String> found = new HashSet<String>();
754             if (result.size() == 0) {
755                 status = FAIL;
756                 return;
757             }
758             boolean pass = true;
759             for (JSONObject payload : result) {
760                 try {
761                     pass &= checkEquals(mPackageString,
762                             payload.getString(JSON_PACKAGE),
763                             "data integrity test: notification package (%s, %s)");
764                     String tag = payload.getString(JSON_TAG);
765                     if (mTag1.equals(tag)) {
766                         found.add(mTag1);
767                         pass &= checkEquals(mIcon1, payload.getInt(JSON_ICON),
768                                 "data integrity test: notification icon (%d, %d)");
769                         pass &= checkFlagSet(mFlag1, payload.getInt(JSON_FLAGS),
770                                 "data integrity test: notification flags (%d, %d)");
771                         pass &= checkEquals(mId1, payload.getInt(JSON_ID),
772                                 "data integrity test: notification ID (%d, %d)");
773                         pass &= checkEquals(mWhen1, payload.getLong(JSON_WHEN),
774                                 "data integrity test: notification when (%d, %d)");
775                     } else if (mTag2.equals(tag)) {
776                         found.add(mTag2);
777                         pass &= checkEquals(mIcon2, payload.getInt(JSON_ICON),
778                                 "data integrity test: notification icon (%d, %d)");
779                         pass &= checkFlagSet(mFlag2, payload.getInt(JSON_FLAGS),
780                                 "data integrity test: notification flags (%d, %d)");
781                         pass &= checkEquals(mId2, payload.getInt(JSON_ID),
782                                 "data integrity test: notification ID (%d, %d)");
783                         pass &= checkEquals(mWhen2, payload.getLong(JSON_WHEN),
784                                 "data integrity test: notification when (%d, %d)");
785                     } else if (mTag3.equals(tag)) {
786                         found.add(mTag3);
787                         pass &= checkEquals(mIcon3, payload.getInt(JSON_ICON),
788                                 "data integrity test: notification icon (%d, %d)");
789                         pass &= checkFlagSet(mFlag3, payload.getInt(JSON_FLAGS),
790                                 "data integrity test: notification flags (%d, %d)");
791                         pass &= checkEquals(mId3, payload.getInt(JSON_ID),
792                                 "data integrity test: notification ID (%d, %d)");
793                         pass &= checkEquals(mWhen3, payload.getLong(JSON_WHEN),
794                                 "data integrity test: notification when (%d, %d)");
795                     }
796                 } catch (JSONException e) {
797                     pass = false;
798                     Log.e(TAG, "failed to unpack data from mocklistener", e);
799                 }
800             }
801 
802             pass &= found.size() >= 3;
803             status = pass ? PASS : FAIL;
804         }
805 
806         @Override
tearDown()807         protected void tearDown() {
808             mNm.cancelAll();
809             MockListener.getInstance().resetData();
810             deleteChannels();
811         }
812     }
813 
814     private class AudiblyAlertedTest extends InteractiveTestCase {
815         @Override
inflate(ViewGroup parent)816         protected View inflate(ViewGroup parent) {
817             return createAutoItem(parent, R.string.nls_audibly_alerted);
818         }
819 
820         @Override
setUp()821         protected void setUp() {
822             createChannels();
823             sendNotifications();
824             sendNoisyNotification();
825             status = READY;
826         }
827 
828         @Override
test()829         protected void test() {
830             List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted());
831 
832             Set<String> found = new HashSet<>();
833             if (result.size() == 0) {
834                 status = FAIL;
835                 return;
836             }
837             boolean pass = true;
838             for (JSONObject payload : result) {
839                 try {
840                     String tag = payload.getString(JSON_TAG);
841                     if (mTag4.equals(tag)) {
842                         found.add(mTag4);
843                         boolean lastAudiblyAlertedSet
844                                 = payload.getLong(JSON_LAST_AUDIBLY_ALERTED) > -1;
845                         if (!lastAudiblyAlertedSet) {
846                             logWithStack(
847                                     "noisy notification test: getLastAudiblyAlertedMillis not set");
848                         }
849                         pass &= lastAudiblyAlertedSet;
850                     } else if (payload.getString(JSON_PACKAGE).equals(mPackageString)) {
851                         found.add(tag);
852                         boolean lastAudiblyAlertedSet
853                                 = payload.getLong(JSON_LAST_AUDIBLY_ALERTED) > 0;
854                         if (lastAudiblyAlertedSet) {
855                             logWithStack(
856                                     "noisy notification test: getLastAudiblyAlertedMillis set "
857                                             + "incorrectly");
858                         }
859                         pass &= !lastAudiblyAlertedSet;
860                     }
861                 } catch (JSONException e) {
862                     pass = false;
863                     Log.e(TAG, "failed to unpack data from mocklistener", e);
864                 }
865             }
866 
867             pass &= found.size() >= 4;
868             status = pass ? PASS : FAIL;
869         }
870 
871         @Override
tearDown()872         protected void tearDown() {
873             mNm.cancelAll();
874             MockListener.getInstance().resetData();
875             deleteChannels();
876         }
877     }
878 
879     private class DismissOneTest extends InteractiveTestCase {
880         @Override
inflate(ViewGroup parent)881         protected View inflate(ViewGroup parent) {
882             return createAutoItem(parent, R.string.nls_clear_one);
883         }
884 
885         @Override
setUp()886         protected void setUp() {
887             createChannels();
888             sendNotifications();
889             status = READY;
890         }
891 
892         @Override
test()893         protected void test() {
894             if (status == READY) {
895                 MockListener.getInstance().cancelNotification(
896                         MockListener.getInstance().getKeyForTag(mTag1));
897                 status = RETEST;
898             } else {
899                 List<String> result = new ArrayList<>(MockListener.getInstance().mRemoved);
900                 if (result.size() != 0
901                         && result.contains(mTag1)
902                         && !result.contains(mTag2)
903                         && !result.contains(mTag3)) {
904                     status = PASS;
905                 } else {
906                     logFail();
907                     status = FAIL;
908                 }
909             }
910         }
911 
912         @Override
tearDown()913         protected void tearDown() {
914             mNm.cancelAll();
915             deleteChannels();
916             MockListener.getInstance().resetData();
917         }
918     }
919 
920     private class DismissOneWithReasonTest extends InteractiveTestCase {
921         int mRetries = 3;
922 
923         @Override
inflate(ViewGroup parent)924         protected View inflate(ViewGroup parent) {
925             return createAutoItem(parent, R.string.nls_clear_one_reason);
926         }
927 
928         @Override
setUp()929         protected void setUp() {
930             createChannels();
931             sendNotifications();
932             status = READY;
933         }
934 
935         @Override
test()936         protected void test() {
937             if (status == READY) {
938                 MockListener.getInstance().cancelNotification(
939                         MockListener.getInstance().getKeyForTag(mTag1));
940                 status = RETEST;
941             } else {
942                 List<JSONObject> result =
943                         new ArrayList<>(MockListener.getInstance().mRemovedReason.values());
944                 boolean pass = false;
945                 for (JSONObject payload : result) {
946                     try {
947                         pass |= (checkEquals(mTag1,
948                                 payload.getString(JSON_TAG),
949                                 "data dismissal test: notification tag (%s, %s)")
950                                 && checkEquals(REASON_LISTENER_CANCEL,
951                                 payload.getInt(JSON_REASON),
952                                 "data dismissal test: reason (%d, %d)"));
953                         if(pass) {
954                             break;
955                         }
956                     } catch (JSONException e) {
957                         e.printStackTrace();
958                     }
959                 }
960                 if (pass) {
961                     status = PASS;
962                 } else {
963                     if (--mRetries > 0) {
964                         sleep(100);
965                         status = RETEST;
966                     } else {
967                         status = FAIL;
968                     }
969                 }
970             }
971         }
972 
973         @Override
tearDown()974         protected void tearDown() {
975             mNm.cancelAll();
976             deleteChannels();
977             MockListener.getInstance().resetData();
978         }
979     }
980 
981     private class DismissOneWithStatsTest extends InteractiveTestCase {
982         int mRetries = 3;
983 
984         @Override
inflate(ViewGroup parent)985         protected View inflate(ViewGroup parent) {
986             return createAutoItem(parent, R.string.nls_clear_one_stats);
987         }
988 
989         @Override
setUp()990         protected void setUp() {
991             createChannels();
992             sendNotifications();
993             status = READY;
994         }
995 
996         @Override
test()997         protected void test() {
998             if (status == READY) {
999                 MockListener.getInstance().cancelNotification(
1000                         MockListener.getInstance().getKeyForTag(mTag1));
1001                 status = RETEST;
1002             } else {
1003                 List<JSONObject> result =
1004                         new ArrayList<>(MockListener.getInstance().mRemovedReason.values());
1005                 boolean pass = true;
1006                 for (JSONObject payload : result) {
1007                     try {
1008                         pass &= (payload.getBoolean(JSON_STATS) == false);
1009                     } catch (JSONException e) {
1010                         e.printStackTrace();
1011                         pass = false;
1012                     }
1013                 }
1014                 if (pass) {
1015                     status = PASS;
1016                 } else {
1017                     if (--mRetries > 0) {
1018                         sleep(100);
1019                         status = RETEST;
1020                     } else {
1021                         logFail("Notification listener got populated stats object.");
1022                         status = FAIL;
1023                     }
1024                 }
1025             }
1026         }
1027 
1028         @Override
tearDown()1029         protected void tearDown() {
1030             mNm.cancelAll();
1031             deleteChannels();
1032             MockListener.getInstance().resetData();
1033         }
1034     }
1035 
1036     private class DismissAllTest extends InteractiveTestCase {
1037         @Override
inflate(ViewGroup parent)1038         protected View inflate(ViewGroup parent) {
1039             return createAutoItem(parent, R.string.nls_clear_all);
1040         }
1041 
1042         @Override
setUp()1043         protected void setUp() {
1044             createChannels();
1045             sendNotifications();
1046             status = READY;
1047         }
1048 
1049         @Override
test()1050         protected void test() {
1051             if (status == READY) {
1052                 MockListener.getInstance().cancelAllNotifications();
1053                 status = RETEST;
1054             } else {
1055                 List<String> result = new ArrayList<>(MockListener.getInstance().mRemoved);
1056                 if (result.size() != 0
1057                         && result.contains(mTag1)
1058                         && result.contains(mTag2)
1059                         && result.contains(mTag3)) {
1060                     status = PASS;
1061                 } else {
1062                     logFail();
1063                     status = FAIL;
1064                 }
1065             }
1066         }
1067 
1068         @Override
tearDown()1069         protected void tearDown() {
1070             mNm.cancelAll();
1071             deleteChannels();
1072             MockListener.getInstance().resetData();
1073         }
1074     }
1075 
1076     private class IsDisabledTest extends InteractiveTestCase {
1077         @Override
inflate(ViewGroup parent)1078         protected View inflate(ViewGroup parent) {
1079             return createNlsSettingsItem(parent, R.string.nls_disable_service);
1080         }
1081 
1082         @Override
autoStart()1083         boolean autoStart() {
1084             return true;
1085         }
1086 
1087         @Override
test()1088         protected void test() {
1089             String listeners = Secure.getString(getContentResolver(),
1090                     ENABLED_NOTIFICATION_LISTENERS);
1091             if (listeners == null || !listeners.contains(LISTENER_PATH)) {
1092                 status = PASS;
1093             } else {
1094                 status = WAIT_FOR_USER;
1095             }
1096         }
1097 
1098         @Override
tearDown()1099         protected void tearDown() {
1100             MockListener.getInstance().resetData();
1101         }
1102 
1103         @Override
getIntent()1104         protected Intent getIntent() {
1105             return new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS);
1106         }
1107     }
1108 
1109     private class ServiceStoppedTest extends InteractiveTestCase {
1110         int mRetries = 3;
1111         @Override
inflate(ViewGroup parent)1112         protected View inflate(ViewGroup parent) {
1113             return createAutoItem(parent, R.string.nls_service_stopped);
1114         }
1115 
1116         @Override
test()1117         protected void test() {
1118             if (mNm.getEffectsSuppressor() == null && (MockListener.getInstance() == null
1119                     || !MockListener.getInstance().isConnected)) {
1120                 status = PASS;
1121             } else {
1122                 if (--mRetries > 0) {
1123                     sleep(100);
1124                     status = RETEST;
1125                 } else {
1126                     status = FAIL;
1127                 }
1128             }
1129         }
1130 
1131         @Override
getIntent()1132         protected Intent getIntent() {
1133             return new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS);
1134         }
1135     }
1136 
1137     private class NotificationNotReceivedTest extends InteractiveTestCase {
1138         @Override
inflate(ViewGroup parent)1139         protected View inflate(ViewGroup parent) {
1140             return createAutoItem(parent, R.string.nls_note_missed);
1141 
1142         }
1143 
1144         @Override
setUp()1145         protected void setUp() {
1146             createChannels();
1147             sendNotifications();
1148             status = READY;
1149         }
1150 
1151         @Override
test()1152         protected void test() {
1153             if (MockListener.getInstance() == null) {
1154                 status = PASS;
1155             } else {
1156                 if (MockListener.getInstance().mPosted.size() == 0) {
1157                     status = PASS;
1158                 } else {
1159                     logFail();
1160                     status = FAIL;
1161                 }
1162             }
1163             next();
1164         }
1165 
1166         @Override
tearDown()1167         protected void tearDown() {
1168             mNm.cancelAll();
1169             deleteChannels();
1170             if (MockListener.getInstance() != null) {
1171                 MockListener.getInstance().resetData();
1172             }
1173         }
1174     }
1175 
1176     private class RequestUnbindTest extends InteractiveTestCase {
1177         int mRetries = 5;
1178 
1179         @Override
inflate(ViewGroup parent)1180         protected View inflate(ViewGroup parent) {
1181             return createAutoItem(parent, R.string.nls_snooze);
1182 
1183         }
1184 
1185         @Override
setUp()1186         protected void setUp() {
1187             status = READY;
1188             MockListener.getInstance().requestListenerHints(
1189                     MockListener.HINT_HOST_DISABLE_CALL_EFFECTS);
1190         }
1191 
1192         @Override
test()1193         protected void test() {
1194             if (status == READY) {
1195                 MockListener.getInstance().requestUnbind();
1196                 status = RETEST;
1197             } else {
1198                 if (mNm.getEffectsSuppressor() == null && !MockListener.getInstance().isConnected) {
1199                     status = PASS;
1200                 } else {
1201                     if (--mRetries > 0) {
1202                         status = RETEST;
1203                     } else {
1204                         logFail();
1205                         status = FAIL;
1206                     }
1207                 }
1208                 next();
1209             }
1210         }
1211     }
1212 
1213     private class RequestBindTest extends InteractiveTestCase {
1214         int mRetries = 5;
1215 
1216         @Override
inflate(ViewGroup parent)1217         protected View inflate(ViewGroup parent) {
1218             return createAutoItem(parent, R.string.nls_unsnooze);
1219 
1220         }
1221 
1222         @Override
test()1223         protected void test() {
1224             if (status == READY) {
1225                 MockListener.requestRebind(MockListener.COMPONENT_NAME);
1226                 status = RETEST;
1227             } else {
1228                 if (MockListener.getInstance().isConnected) {
1229                     status = PASS;
1230                     next();
1231                 } else {
1232                     if (--mRetries > 0) {
1233                         status = RETEST;
1234                         next();
1235                     } else {
1236                         logFail();
1237                         status = FAIL;
1238                     }
1239                 }
1240             }
1241         }
1242     }
1243 
1244     private class EnableHintsTest extends InteractiveTestCase {
1245         @Override
inflate(ViewGroup parent)1246         protected View inflate(ViewGroup parent) {
1247             return createAutoItem(parent, R.string.nls_hints);
1248 
1249         }
1250 
1251         @Override
test()1252         protected void test() {
1253             if (status == READY) {
1254                 MockListener.getInstance().requestListenerHints(
1255                         MockListener.HINT_HOST_DISABLE_CALL_EFFECTS);
1256                 status = RETEST;
1257             } else {
1258                 int result = MockListener.getInstance().getCurrentListenerHints();
1259                 if ((result & MockListener.HINT_HOST_DISABLE_CALL_EFFECTS)
1260                         == MockListener.HINT_HOST_DISABLE_CALL_EFFECTS) {
1261                     status = PASS;
1262                     next();
1263                 } else {
1264                     logFail();
1265                     status = FAIL;
1266                 }
1267             }
1268         }
1269     }
1270 
1271     private class SnoozeNotificationForTimeTest extends InteractiveTestCase {
1272         final static int READY_TO_SNOOZE = 0;
1273         final static int SNOOZED = 1;
1274         final static int READY_TO_CHECK_FOR_UNSNOOZE = 2;
1275         int state = -1;
1276         long snoozeTime = 3000;
1277 
1278         @Override
inflate(ViewGroup parent)1279         protected View inflate(ViewGroup parent) {
1280             return createAutoItem(parent, R.string.nls_snooze_one_time);
1281         }
1282 
1283         @Override
setUp()1284         protected void setUp() {
1285             createChannels();
1286             sendNotifications();
1287             status = READY;
1288             state = READY_TO_SNOOZE;
1289             delay();
1290         }
1291 
1292         @Override
test()1293         protected void test() {
1294             status = RETEST;
1295             if (state == READY_TO_SNOOZE) {
1296                 MockListener.getInstance().snoozeNotification(
1297                         MockListener.getInstance().getKeyForTag(mTag1), snoozeTime);
1298                 state = SNOOZED;
1299             } else if (state == SNOOZED) {
1300                 List<JSONObject> result =
1301                         new ArrayList<>(MockListener.getInstance().mRemovedReason.values());
1302                 boolean pass = false;
1303                 for (JSONObject payload : result) {
1304                     try {
1305                         pass |= (checkEquals(mTag1,
1306                                 payload.getString(JSON_TAG),
1307                                 "data dismissal test: notification tag (%s, %s)")
1308                                 && checkEquals(MockListener.REASON_SNOOZED,
1309                                 payload.getInt(JSON_REASON),
1310                                 "data dismissal test: reason (%d, %d)"));
1311                         if (pass) {
1312                             break;
1313                         }
1314                     } catch (JSONException e) {
1315                         e.printStackTrace();
1316                     }
1317                 }
1318                 if (!pass) {
1319                     logFail();
1320                     status = FAIL;
1321                     next();
1322                     return;
1323                 } else {
1324                     state = READY_TO_CHECK_FOR_UNSNOOZE;
1325                 }
1326             } else {
1327                 if (MockListener.getInstance().getPosted(mTag1) != null) {
1328                     status = PASS;
1329                 } else {
1330                     logFail();
1331                     status = FAIL;
1332                 }
1333             }
1334         }
1335 
1336         @Override
tearDown()1337         protected void tearDown() {
1338             mNm.cancelAll();
1339             deleteChannels();
1340             MockListener.getInstance().resetData();
1341             delay();
1342         }
1343     }
1344 
1345     /**
1346      * Posts notifications, snoozes one of them. Verifies that it is snoozed. Cancels all
1347      * notifications and reposts them. Confirms that the original notification is still snoozed.
1348      */
1349     private class SnoozeNotificationForTimeCancelTest extends InteractiveTestCase {
1350 
1351         final static int READY_TO_SNOOZE = 0;
1352         final static int SNOOZED = 1;
1353         final static int READY_TO_CHECK_FOR_SNOOZE = 2;
1354         int state = -1;
1355         long snoozeTime = 10000;
1356         private String tag;
1357 
1358         @Override
inflate(ViewGroup parent)1359         protected View inflate(ViewGroup parent) {
1360             return createAutoItem(parent, R.string.nls_snooze_one_time);
1361         }
1362 
1363         @Override
setUp()1364         protected void setUp() {
1365             createChannels();
1366             sendNotifications();
1367             tag = mTag1;
1368             status = READY;
1369             state = READY_TO_SNOOZE;
1370             delay();
1371         }
1372 
1373         @Override
test()1374         protected void test() {
1375             status = RETEST;
1376             if (state == READY_TO_SNOOZE) {
1377                 MockListener.getInstance().snoozeNotification(
1378                         MockListener.getInstance().getKeyForTag(tag), snoozeTime);
1379                 state = SNOOZED;
1380             } else if (state == SNOOZED) {
1381                 List<String> result = getSnoozed();
1382                 if (result.size() >= 1
1383                         && result.contains(tag)) {
1384                     // cancel and repost
1385                     sendNotifications();
1386                     state = READY_TO_CHECK_FOR_SNOOZE;
1387                 } else {
1388                     logFail();
1389                     status = FAIL;
1390                 }
1391             } else {
1392                 List<String> result = getSnoozed();
1393                 if (result.size() >= 1
1394                         && result.contains(tag)) {
1395                     status = PASS;
1396                 } else {
1397                     logFail();
1398                     status = FAIL;
1399                 }
1400             }
1401         }
1402 
getSnoozed()1403         private List<String> getSnoozed() {
1404             List<String> result = new ArrayList<>();
1405             StatusBarNotification[] snoozed = MockListener.getInstance().getSnoozedNotifications();
1406             for (StatusBarNotification sbn : snoozed) {
1407                 result.add(sbn.getTag());
1408             }
1409             return result;
1410         }
1411 
1412         @Override
tearDown()1413         protected void tearDown() {
1414             mNm.cancelAll();
1415             deleteChannels();
1416             MockListener.getInstance().resetData();
1417         }
1418     }
1419 
1420     private class GetSnoozedNotificationTest extends InteractiveTestCase {
1421         final static int READY_TO_SNOOZE = 0;
1422         final static int SNOOZED = 1;
1423         final static int READY_TO_CHECK_FOR_GET_SNOOZE = 2;
1424         int state = -1;
1425         long snoozeTime = 30000;
1426 
1427         @Override
inflate(ViewGroup parent)1428         protected View inflate(ViewGroup parent) {
1429             return createAutoItem(parent, R.string.nls_get_snoozed);
1430         }
1431 
1432         @Override
setUp()1433         protected void setUp() {
1434             createChannels();
1435             sendNotifications();
1436             status = READY;
1437             state = READY_TO_SNOOZE;
1438         }
1439 
1440         @Override
test()1441         protected void test() {
1442             status = RETEST;
1443             if (state == READY_TO_SNOOZE) {
1444                 MockListener.getInstance().snoozeNotification(
1445                         MockListener.getInstance().getKeyForTag(mTag1), snoozeTime);
1446                 MockListener.getInstance().snoozeNotification(
1447                         MockListener.getInstance().getKeyForTag(mTag2), snoozeTime);
1448                 state = SNOOZED;
1449             } else if (state == SNOOZED) {
1450                 List<JSONObject> result =
1451                         new ArrayList<>(MockListener.getInstance().mRemovedReason.values());
1452                 if (result.size() == 0) {
1453                     status = FAIL;
1454                     return;
1455                 }
1456                 boolean pass = false;
1457                 for (JSONObject payload : result) {
1458                     try {
1459                         pass |= (checkEquals(mTag1,
1460                                 payload.getString(JSON_TAG),
1461                                 "data dismissal test: notification tag (%s, %s)")
1462                                 && checkEquals(MockListener.REASON_SNOOZED,
1463                                 payload.getInt(JSON_REASON),
1464                                 "data dismissal test: reason (%d, %d)"));
1465                         if (pass) {
1466                             break;
1467                         }
1468                     } catch (JSONException e) {
1469                         e.printStackTrace();
1470                     }
1471                 }
1472                 if (!pass) {
1473                     logFail();
1474                     status = FAIL;
1475                 } else {
1476                     state = READY_TO_CHECK_FOR_GET_SNOOZE;
1477                 }
1478             } else {
1479                 List<String> result = new ArrayList<>();
1480                 StatusBarNotification[] snoozed =
1481                         MockListener.getInstance().getSnoozedNotifications();
1482                 for (StatusBarNotification sbn : snoozed) {
1483                     result.add(sbn.getTag());
1484                 }
1485                 if (result.size() >= 2
1486                         && result.contains(mTag1)
1487                         && result.contains(mTag2)) {
1488                     status = PASS;
1489                 } else {
1490                     logFail();
1491                     status = FAIL;
1492                 }
1493             }
1494         }
1495 
1496         @Override
tearDown()1497         protected void tearDown() {
1498             mNm.cancelAll();
1499             deleteChannels();
1500             MockListener.getInstance().resetData();
1501             delay();
1502         }
1503     }
1504 
1505     /** Tests that the extras {@link Bundle} in a MessagingStyle#Message is preserved. */
1506     private class MessageBundleTest extends InteractiveTestCase {
1507         private final String extrasKey1 = "extras_key_1";
1508         private final CharSequence extrasValue1 = "extras_value_1";
1509         private final String extrasKey2 = "extras_key_2";
1510         private final CharSequence extrasValue2 = "extras_value_2";
1511 
1512         @Override
inflate(ViewGroup parent)1513         protected View inflate(ViewGroup parent) {
1514             return createAutoItem(parent, R.string.msg_extras_preserved);
1515         }
1516 
1517         @Override
setUp()1518         protected void setUp() {
1519             createChannels();
1520             sendMessagingNotification();
1521             status = READY;
1522         }
1523 
1524         @Override
tearDown()1525         protected void tearDown() {
1526             mNm.cancelAll();
1527             deleteChannels();
1528             delay();
1529         }
1530 
sendMessagingNotification()1531         private void sendMessagingNotification() {
1532             mTag1 = UUID.randomUUID().toString();
1533             mNm.cancelAll();
1534             mWhen1 = System.currentTimeMillis() + 1;
1535             mIcon1 = R.drawable.ic_stat_alice;
1536             mId1 = NOTIFICATION_ID + 1;
1537 
1538             Notification.MessagingStyle.Message msg1 =
1539                     new Notification.MessagingStyle.Message("text1", 0 /* timestamp */, "sender1");
1540             msg1.getExtras().putCharSequence(extrasKey1, extrasValue1);
1541 
1542             Notification.MessagingStyle.Message msg2 =
1543                     new Notification.MessagingStyle.Message("text2", 1 /* timestamp */, "sender2");
1544             msg2.getExtras().putCharSequence(extrasKey2, extrasValue2);
1545 
1546             Notification.MessagingStyle style = new Notification.MessagingStyle("display_name");
1547             style.addMessage(msg1);
1548             style.addMessage(msg2);
1549 
1550             Notification n1 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
1551                     .setContentTitle("ClearTest 1")
1552                     .setContentText(mTag1.toString())
1553                     .setPriority(Notification.PRIORITY_LOW)
1554                     .setSmallIcon(mIcon1)
1555                     .setWhen(mWhen1)
1556                     .setDeleteIntent(makeIntent(1, mTag1))
1557                     .setOnlyAlertOnce(true)
1558                     .setStyle(style)
1559                     .build();
1560             mNm.notify(mTag1, mId1, n1);
1561             mFlag1 = Notification.FLAG_ONLY_ALERT_ONCE;
1562         }
1563 
1564         // Returns true on success.
verifyMessage( NotificationCompat.MessagingStyle.Message message, String extrasKey, CharSequence extrasValue)1565         private boolean verifyMessage(
1566                 NotificationCompat.MessagingStyle.Message message,
1567                 String extrasKey,
1568                 CharSequence extrasValue) {
1569             return message.getExtras() != null
1570                     && message.getExtras().getCharSequence(extrasKey) != null
1571                     && message.getExtras().getCharSequence(extrasKey).equals(extrasValue);
1572         }
1573 
1574         @Override
test()1575         protected void test() {
1576             List<Notification> result =
1577                     new ArrayList<>(MockListener.getInstance().mPostedNotifications);
1578             if (result.size() != 1 || result.get(0) == null) {
1579                 logFail();
1580                 status = FAIL;
1581                 next();
1582                 return;
1583             }
1584             // Can only read in MessagingStyle using the compat class.
1585             NotificationCompat.MessagingStyle readStyle =
1586                     NotificationCompat.MessagingStyle
1587                             .extractMessagingStyleFromNotification(
1588                                     result.get(0));
1589             if (readStyle == null || readStyle.getMessages().size() != 2) {
1590                 status = FAIL;
1591                 logFail();
1592                 next();
1593                 return;
1594             }
1595 
1596             if (!verifyMessage(readStyle.getMessages().get(0), extrasKey1,
1597                     extrasValue1)
1598                     || !verifyMessage(
1599                     readStyle.getMessages().get(1), extrasKey2, extrasValue2)) {
1600                 status = FAIL;
1601                 logFail();
1602                 next();
1603                 return;
1604             }
1605 
1606             status = PASS;
1607         }
1608     }
1609 
1610     /**
1611      * Tests that conversation notifications appear at the top of the shade, if the device supports
1612      * a separate conversation section
1613      */
1614     private class ConversationOrderingTest extends InteractiveTestCase {
1615         private static final String SHARE_SHORTCUT_ID = "shareShortcut";
1616         private static final String SHORTCUT_CATEGORY =
1617                 "com.android.cts.verifier.notifications.SHORTCUT_CATEGORY";
1618 
1619         @Override
setUp()1620         protected void setUp() {
1621             createChannels();
1622             createDynamicShortcut();
1623             sendNotifications();
1624             status = READY;
1625         }
1626 
1627         @Override
tearDown()1628         protected void tearDown() {
1629             mNm.cancelAll();
1630             deleteChannels();
1631             delay();
1632         }
1633 
1634         @Override
inflate(ViewGroup parent)1635         protected View inflate(ViewGroup parent) {
1636             return createPassFailItem(parent, R.string.conversation_section_ordering);
1637         }
1638 
createDynamicShortcut()1639         private void createDynamicShortcut() {
1640             Person person = new Person.Builder()
1641                     .setBot(false)
1642                     .setIcon(Icon.createWithResource(mContext, R.drawable.ic_stat_alice))
1643                     .setName("Person A")
1644                     .setImportant(true)
1645                     .build();
1646 
1647             Set<String> categorySet = new ArraySet<>();
1648             categorySet.add(SHORTCUT_CATEGORY);
1649             Intent shortcutIntent =
1650                     new Intent(mContext, BubbleActivity.class);
1651             shortcutIntent.setAction(Intent.ACTION_VIEW);
1652 
1653             ShortcutInfo shortcut = new ShortcutInfo.Builder(mContext, SHARE_SHORTCUT_ID)
1654                     .setShortLabel(SHARE_SHORTCUT_ID)
1655                     .setIcon(Icon.createWithResource(mContext, R.drawable.ic_stat_alice))
1656                     .setIntent(shortcutIntent)
1657                     .setPerson(person)
1658                     .setCategories(categorySet)
1659                     .setLongLived(true)
1660                     .build();
1661 
1662             ShortcutManager scManager =
1663                     (ShortcutManager) mContext.getSystemService(Context.SHORTCUT_SERVICE);
1664             scManager.addDynamicShortcuts(Arrays.asList(shortcut));
1665         }
1666 
sendNotifications()1667         private void sendNotifications() {
1668             mTag1 = UUID.randomUUID().toString();
1669             mId1 = NOTIFICATION_ID + 1;
1670 
1671             Person person = new Person.Builder()
1672                     .setName("Person A")
1673                     .build();
1674 
1675             Notification n1 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
1676                     .setContentTitle("ConversationOrderingTest")
1677                     .setContentText(mTag1)
1678                     .setSmallIcon(R.drawable.ic_stat_alice)
1679                     .setShortcutId(SHARE_SHORTCUT_ID)
1680                     .setStyle(new Notification.MessagingStyle(person)
1681                             .setConversationTitle("Bubble Chat")
1682                             .addMessage("Hello?",
1683                                     SystemClock.currentThreadTimeMillis() - 300000, person)
1684                             .addMessage("Is it me you're looking for?",
1685                                     SystemClock.currentThreadTimeMillis(), person)
1686                     )
1687                     .build();
1688             mNm.notify(mTag1, mId1, n1);
1689 
1690             mTag2 = UUID.randomUUID().toString();
1691             mId2 = mId1 + 1;
1692             Notification n2 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
1693                     .setContentTitle("Non-Person Notification")
1694                     .setContentText(mTag1)
1695                     .setSmallIcon(R.drawable.ic_stat_alice)
1696                     .build();
1697             mNm.notify(mTag2, mId2, n2);
1698         }
1699 
1700         @Override
autoStart()1701         boolean autoStart() {
1702             return true;
1703         }
1704 
1705         @Override
test()1706         protected void test() {
1707             status = WAIT_FOR_USER;
1708             next();
1709         }
1710     }
1711 
1712     /**
1713      * Creates a notification channel. Sends the user to settings to re-allow the channel to
1714      * show content on the lockscreen.
1715      * This asks the user to undo what they did for {@link LockscreenVisibilityTest}
1716      */
1717     protected class RestoreLockscreenVisibilityTest extends InteractiveTestCase {
1718         private View mView;
1719         @Override
inflate(ViewGroup parent)1720         protected View inflate(ViewGroup parent) {
1721             mView = createNlsSettingsItem(parent, R.string.nls_restore_visibility);
1722             Button button = mView.findViewById(R.id.nls_action_button);
1723             button.setEnabled(false);
1724             return mView;
1725         }
1726 
1727         @Override
setUp()1728         protected void setUp() {
1729             createChannels();
1730             status = READY;
1731             Button button = mView.findViewById(R.id.nls_action_button);
1732             button.setEnabled(true);
1733         }
1734 
1735         @Override
autoStart()1736         boolean autoStart() {
1737             return true;
1738         }
1739 
1740         @Override
test()1741         protected void test() {
1742             NotificationChannel channel = mNm.getNotificationChannel(NOTIFICATION_CHANNEL_ID);
1743             int visibility = channel.getLockscreenVisibility();
1744             if (visibility == VISIBILITY_PUBLIC || visibility == VISIBILITY_NO_OVERRIDE) {
1745                 status = PASS;
1746             } else {
1747                 status = WAIT_FOR_USER;
1748             }
1749 
1750             next();
1751         }
1752 
tearDown()1753         protected void tearDown() {
1754             deleteChannels();
1755         }
1756 
1757         @Override
getIntent()1758         protected Intent getIntent() {
1759             return new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
1760                     .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName())
1761                     .putExtra(EXTRA_CHANNEL_ID, NOTIFICATION_CHANNEL_ID);
1762         }
1763     }
1764 
1765     private class AddScreenLockTest extends InteractiveTestCase {
1766         private View mView;
1767         @Override
inflate(ViewGroup parent)1768         protected View inflate(ViewGroup parent) {
1769             mView = createNlsSettingsItem(parent, R.string.add_screen_lock);
1770             Button button = mView.findViewById(R.id.nls_action_button);
1771             button.setEnabled(false);
1772             return mView;
1773         }
1774 
1775         @Override
setUp()1776         protected void setUp() {
1777             status = READY;
1778             Button button = mView.findViewById(R.id.nls_action_button);
1779             button.setEnabled(true);
1780         }
1781 
1782         @Override
autoStart()1783         boolean autoStart() {
1784             return true;
1785         }
1786 
1787         @Override
test()1788         protected void test() {
1789             KeyguardManager km = getSystemService(KeyguardManager.class);
1790             if (km.isDeviceSecure()) {
1791                 status = PASS;
1792             } else {
1793                 status = WAIT_FOR_USER;
1794             }
1795 
1796             next();
1797         }
1798 
1799         @Override
getIntent()1800         protected Intent getIntent() {
1801             return new Intent(Settings.ACTION_SECURITY_SETTINGS)
1802                     .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
1803         }
1804     }
1805 
1806     private class SecureActionOnLockScreenTest extends InteractiveTestCase {
1807         @Override
setUp()1808         protected void setUp() {
1809             createChannels();
1810             ActionTriggeredReceiver.sendNotification(mContext, true);
1811             status = READY;
1812         }
1813 
1814         @Override
tearDown()1815         protected void tearDown() {
1816             mNm.cancelAll();
1817             deleteChannels();
1818             delay();
1819         }
1820 
1821         @Override
inflate(ViewGroup parent)1822         protected View inflate(ViewGroup parent) {
1823             return createPassFailItem(parent, R.string.secure_action_lockscreen);
1824         }
1825 
1826         @Override
autoStart()1827         boolean autoStart() {
1828             return true;
1829         }
1830 
1831         @Override
test()1832         protected void test() {
1833             status = WAIT_FOR_USER;
1834             next();
1835         }
1836     }
1837 
1838     private class RemoveScreenLockTest extends InteractiveTestCase {
1839         private View mView;
1840         @Override
inflate(ViewGroup parent)1841         protected View inflate(ViewGroup parent) {
1842             mView = createNlsSettingsItem(parent, R.string.remove_screen_lock);
1843             Button button = mView.findViewById(R.id.nls_action_button);
1844             button.setEnabled(false);
1845             return mView;
1846         }
1847 
1848         @Override
setUp()1849         protected void setUp() {
1850             status = READY;
1851             Button button = mView.findViewById(R.id.nls_action_button);
1852             button.setEnabled(true);
1853         }
1854 
1855         @Override
autoStart()1856         boolean autoStart() {
1857             return true;
1858         }
1859 
1860         @Override
test()1861         protected void test() {
1862             KeyguardManager km = getSystemService(KeyguardManager.class);
1863             if (!km.isDeviceSecure()) {
1864                 status = PASS;
1865             } else {
1866                 status = WAIT_FOR_USER;
1867             }
1868 
1869             next();
1870         }
1871 
1872         @Override
getIntent()1873         protected Intent getIntent() {
1874             return new Intent(Settings.ACTION_SECURITY_SETTINGS)
1875                     .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
1876         }
1877     }
1878 
1879     /**
1880      * Sends the user to settings filter out silent notifications for this notification listener.
1881      * Sends silent and not silent notifs and makes sure only the non silent is received
1882      */
1883     private class NotificationTypeFilterTest extends InteractiveTestCase {
1884         int mRetries = 3;
1885         @Override
inflate(ViewGroup parent)1886         protected View inflate(ViewGroup parent) {
1887             return createAutoItem(parent, R.string.nls_filter_test);
1888 
1889         }
1890 
1891         @Override
setUp()1892         protected void setUp() {
1893             createChannels();
1894             sendNotifications();
1895             sendNoisyNotification();
1896             status = READY;
1897         }
1898 
1899         @Override
tearDown()1900         protected void tearDown() {
1901             mNm.cancelAll();
1902             MockListener.getInstance().resetData();
1903             deleteChannels();
1904         }
1905 
1906         @Override
test()1907         protected void test() {
1908             if (MockListener.getInstance().getPosted(mTag4) == null) {
1909                 Log.d(TAG, "Could not find " + mTag4);
1910                 if (--mRetries > 0) {
1911                     sleep(100);
1912                     status = RETEST;
1913                 } else {
1914                     status = FAIL;
1915                 }
1916             } else if (MockListener.getInstance().getPosted(mTag2) != null) {
1917                 logFail("Found" + mTag2);
1918                 status = FAIL;
1919             } else {
1920                 status = PASS;
1921             }
1922         }
1923     }
1924 
1925     protected class SendUserToChangeFilter extends InteractiveTestCase {
1926         @Override
inflate(ViewGroup parent)1927         protected View inflate(ViewGroup parent) {
1928             return createUserItem(
1929                     parent, R.string.cp_start_settings,  R.string.nls_change_type_filter);
1930         }
1931 
1932         @Override
setUp()1933         protected void setUp() {
1934             // note: it's expected that the '0' type will be ignored since we've specified a
1935             // type in the manifest
1936             ArrayList<String> pkgs = new ArrayList<>();
1937             pkgs.add("com.android.settings");
1938             MockListener.getInstance().migrateNotificationFilter(0, pkgs);
1939         }
1940 
1941         @Override
autoStart()1942         boolean autoStart() {
1943             return true;
1944         }
1945 
1946         @Override
test()1947         protected void test() {
1948             if (getIntent().resolveActivity(mPackageManager) == null) {
1949                 logFail("no settings activity");
1950                 status = FAIL;
1951             } else {
1952                 if (buttonPressed) {
1953                     status = PASS;
1954                 } else {
1955                     status = RETEST_AFTER_LONG_DELAY;
1956                 }
1957                 next();
1958             }
1959         }
1960 
tearDown()1961         protected void tearDown() {
1962             // wait for the service to start
1963             delay();
1964         }
1965 
1966         @Override
getIntent()1967         protected Intent getIntent() {
1968             Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_DETAIL_SETTINGS);
1969             intent.putExtra(Settings.EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME,
1970                     MockListener.COMPONENT_NAME.flattenToString());
1971             return intent;
1972         }
1973     }
1974 
1975     protected class ResetChangeFilter extends SendUserToChangeFilter {
1976         @Override
inflate(ViewGroup parent)1977         protected View inflate(ViewGroup parent) {
1978             return createUserItem(
1979                     parent, R.string.cp_start_settings,  R.string.nls_reset_type_filter);
1980         }
1981     }
1982 
1983     protected class AskIfFilterChanged extends InteractiveTestCase {
1984         @Override
inflate(ViewGroup parent)1985         protected View inflate(ViewGroup parent) {
1986             return createPassFailItem(parent, R.string.nls_original_filter_verification);
1987         }
1988 
1989         @Override
autoStart()1990         boolean autoStart() {
1991             return true;
1992         }
1993 
1994         @Override
test()1995         protected void test() {
1996             status = WAIT_FOR_USER;
1997             next();
1998         }
1999     }
2000 }
2001