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