• 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.NotificationManager.IMPORTANCE_LOW;
20 import static android.app.NotificationManager.IMPORTANCE_MAX;
21 import static android.app.NotificationManager.IMPORTANCE_NONE;
22 import static android.provider.Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS;
23 import static android.provider.Settings.EXTRA_APP_PACKAGE;
24 import static android.provider.Settings.EXTRA_CHANNEL_ID;
25 
26 import static com.android.cts.verifier.notifications.MockListener.JSON_FLAGS;
27 import static com.android.cts.verifier.notifications.MockListener.JSON_ICON;
28 import static com.android.cts.verifier.notifications.MockListener.JSON_ID;
29 import static com.android.cts.verifier.notifications.MockListener.JSON_LAST_AUDIBLY_ALERTED;
30 import static com.android.cts.verifier.notifications.MockListener.JSON_PACKAGE;
31 import static com.android.cts.verifier.notifications.MockListener.JSON_REASON;
32 import static com.android.cts.verifier.notifications.MockListener.JSON_STATS;
33 import static com.android.cts.verifier.notifications.MockListener.JSON_TAG;
34 import static com.android.cts.verifier.notifications.MockListener.JSON_WHEN;
35 import static com.android.cts.verifier.notifications.MockListener.REASON_LISTENER_CANCEL;
36 
37 import android.annotation.SuppressLint;
38 import android.app.ActivityManager;
39 import android.app.Notification;
40 import android.app.NotificationChannel;
41 import android.app.NotificationChannelGroup;
42 import android.content.Context;
43 import android.content.Intent;
44 import android.content.SharedPreferences;
45 import android.os.Bundle;
46 import android.provider.Settings;
47 import android.provider.Settings.Secure;
48 import android.service.notification.StatusBarNotification;
49 import android.util.Log;
50 import android.view.View;
51 import android.view.ViewGroup;
52 import android.widget.Button;
53 
54 import androidx.core.app.NotificationCompat;
55 
56 import com.android.cts.verifier.R;
57 
58 import org.json.JSONException;
59 import org.json.JSONObject;
60 
61 import java.util.ArrayList;
62 import java.util.HashSet;
63 import java.util.List;
64 import java.util.Set;
65 import java.util.UUID;
66 
67 public class NotificationListenerVerifierActivity extends InteractiveVerifierActivity
68         implements Runnable {
69     private static final String TAG = "NoListenerVerifier";
70     private static final String NOTIFICATION_CHANNEL_ID = TAG;
71     private static final String NOISY_NOTIFICATION_CHANNEL_ID = TAG + "Noisy";
72     protected static final String PREFS = "listener_prefs";
73     final int NUM_NOTIFICATIONS_SENT = 3; // # notifications sent by sendNotifications()
74 
75     private String mTag1;
76     private String mTag2;
77     private String mTag3;
78     private String mTag4;
79     private int mIcon1;
80     private int mIcon2;
81     private int mIcon3;
82     private int mIcon4;
83     private int mId1;
84     private int mId2;
85     private int mId3;
86     private int mId4;
87     private long mWhen1;
88     private long mWhen2;
89     private long mWhen3;
90     private long mWhen4;
91     private int mFlag1;
92     private int mFlag2;
93     private int mFlag3;
94 
95     @Override
getTitleResource()96     protected int getTitleResource() {
97         return R.string.nls_test;
98     }
99 
100     @Override
getInstructionsResource()101     protected int getInstructionsResource() {
102         return R.string.nls_info;
103     }
104 
105     // Test Setup
106 
107     @Override
createTestItems()108     protected List<InteractiveTestCase> createTestItems() {
109         List<InteractiveTestCase> tests = new ArrayList<>(17);
110         ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
111         if (am.isLowRamDevice()) {
112             tests.add(new CannotBeEnabledTest());
113             tests.add(new ServiceStoppedTest());
114             tests.add(new NotificationNotReceivedTest());
115         } else {
116             tests.add(new IsEnabledTest());
117             tests.add(new ServiceStartedTest());
118             tests.add(new NotificationReceivedTest());
119             tests.add(new DataIntactTest());
120             tests.add(new AudiblyAlertedTest());
121             tests.add(new DismissOneTest());
122             tests.add(new DismissOneWithReasonTest());
123             tests.add(new DismissOneWithStatsTest());
124             tests.add(new DismissAllTest());
125             tests.add(new SnoozeNotificationForTimeTest());
126             tests.add(new SnoozeNotificationForTimeCancelTest());
127             tests.add(new GetSnoozedNotificationTest());
128             tests.add(new EnableHintsTest());
129             tests.add(new ReceiveAppBlockNoticeTest());
130             tests.add(new ReceiveAppUnblockNoticeTest());
131             tests.add(new ReceiveChannelBlockNoticeTest());
132             tests.add(new ReceiveGroupBlockNoticeTest());
133             tests.add(new RequestUnbindTest());
134             tests.add(new RequestBindTest());
135             tests.add(new MessageBundleTest());
136             tests.add(new EnableHintsTest());
137             tests.add(new IsDisabledTest());
138             tests.add(new ServiceStoppedTest());
139             tests.add(new NotificationNotReceivedTest());
140         }
141         return tests;
142     }
143 
createChannels()144     private void createChannels() {
145         NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
146                 NOTIFICATION_CHANNEL_ID, IMPORTANCE_LOW);
147         NotificationChannel noisyChannel = new NotificationChannel(NOISY_NOTIFICATION_CHANNEL_ID,
148                 NOISY_NOTIFICATION_CHANNEL_ID, IMPORTANCE_MAX);
149         noisyChannel.setVibrationPattern(new long[]{100, 0, 100});
150         mNm.createNotificationChannel(channel);
151         mNm.createNotificationChannel(noisyChannel);
152     }
153 
deleteChannels()154     private void deleteChannels() {
155         mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
156         mNm.deleteNotificationChannel(NOISY_NOTIFICATION_CHANNEL_ID);
157     }
158 
159     @SuppressLint("NewApi")
sendNotifications()160     private void sendNotifications() {
161         mTag1 = UUID.randomUUID().toString();
162         Log.d(TAG, "Sending " + mTag1);
163         mTag2 = UUID.randomUUID().toString();
164         Log.d(TAG, "Sending " + mTag2);
165         mTag3 = UUID.randomUUID().toString();
166         Log.d(TAG, "Sending " + mTag3);
167 
168         mWhen1 = System.currentTimeMillis() + 1;
169         mWhen2 = System.currentTimeMillis() + 2;
170         mWhen3 = System.currentTimeMillis() + 3;
171 
172         mIcon1 = R.drawable.ic_stat_alice;
173         mIcon2 = R.drawable.ic_stat_bob;
174         mIcon3 = R.drawable.ic_stat_charlie;
175 
176         mId1 = NOTIFICATION_ID + 1;
177         mId2 = NOTIFICATION_ID + 2;
178         mId3 = NOTIFICATION_ID + 3;
179 
180         mPackageString = "com.android.cts.verifier";
181 
182         Notification n1 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
183                 .setContentTitle("ClearTest 1")
184                 .setContentText(mTag1)
185                 .setSmallIcon(mIcon1)
186                 .setWhen(mWhen1)
187                 .setDeleteIntent(makeIntent(1, mTag1))
188                 .setOnlyAlertOnce(true)
189                 .build();
190         mNm.notify(mTag1, mId1, n1);
191         mFlag1 = Notification.FLAG_ONLY_ALERT_ONCE;
192 
193         Notification n2 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
194                 .setContentTitle("ClearTest 2")
195                 .setContentText(mTag2)
196                 .setSmallIcon(mIcon2)
197                 .setWhen(mWhen2)
198                 .setDeleteIntent(makeIntent(2, mTag2))
199                 .setAutoCancel(true)
200                 .build();
201         mNm.notify(mTag2, mId2, n2);
202         mFlag2 = Notification.FLAG_AUTO_CANCEL;
203 
204         Notification n3 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
205                 .setContentTitle("ClearTest 3")
206                 .setContentText(mTag3)
207                 .setSmallIcon(mIcon3)
208                 .setWhen(mWhen3)
209                 .setDeleteIntent(makeIntent(3, mTag3))
210                 .setAutoCancel(true)
211                 .setOnlyAlertOnce(true)
212                 .build();
213         mNm.notify(mTag3, mId3, n3);
214         mFlag3 = Notification.FLAG_ONLY_ALERT_ONCE | Notification.FLAG_AUTO_CANCEL;
215     }
216 
sendNoisyNotification()217     private void sendNoisyNotification() {
218         mTag4 = UUID.randomUUID().toString();
219         Log.d(TAG, "Sending " + mTag4);
220 
221         mWhen4 = System.currentTimeMillis() + 4;
222         mIcon4 = R.drawable.ic_stat_charlie;
223         mId4 = NOTIFICATION_ID + 4;
224         mPackageString = "com.android.cts.verifier";
225 
226         Notification n1 = new Notification.Builder(mContext, NOISY_NOTIFICATION_CHANNEL_ID)
227                 .setContentTitle("NoisyTest 1")
228                 .setContentText(mTag4)
229                 .setSmallIcon(mIcon4)
230                 .setWhen(mWhen4)
231                 .setDeleteIntent(makeIntent(4, mTag4))
232                 .setCategory(Notification.CATEGORY_REMINDER)
233                 .build();
234         mNm.notify(mTag4, mId4, n1);
235     }
236 
237     // Tests
238     private class NotificationReceivedTest extends InteractiveTestCase {
239         @Override
inflate(ViewGroup parent)240         protected View inflate(ViewGroup parent) {
241             return createAutoItem(parent, R.string.nls_note_received);
242 
243         }
244 
245         @Override
setUp()246         protected void setUp() {
247             createChannels();
248             sendNotifications();
249             status = READY;
250         }
251 
252         @Override
tearDown()253         protected void tearDown() {
254             mNm.cancelAll();
255             MockListener.getInstance().resetData();
256             deleteChannels();
257         }
258 
259         @Override
test()260         protected void test() {
261             List<String> result = new ArrayList<>(MockListener.getInstance().mPosted);
262             if (result.size() > 0 && result.contains(mTag1)) {
263                 status = PASS;
264             } else {
265                 logFail();
266                 status = FAIL;
267             }
268         }
269     }
270 
271     /**
272      * Creates a notification channel. Sends the user to settings to block the channel. Waits
273      * to receive the broadcast that the channel was blocked, and confirms that the broadcast
274      * contains the correct extras.
275      */
276     protected class ReceiveChannelBlockNoticeTest extends InteractiveTestCase {
277         private String mChannelId;
278         private int mRetries = 2;
279         private View mView;
280         @Override
inflate(ViewGroup parent)281         protected View inflate(ViewGroup parent) {
282             mView = createNlsSettingsItem(parent, R.string.nls_block_channel);
283             Button button = mView.findViewById(R.id.nls_action_button);
284             button.setEnabled(false);
285             return mView;
286         }
287 
288         @Override
setUp()289         protected void setUp() {
290             mChannelId = UUID.randomUUID().toString();
291             NotificationChannel channel = new NotificationChannel(
292                     mChannelId, "ReceiveChannelBlockNoticeTest", IMPORTANCE_LOW);
293             mNm.createNotificationChannel(channel);
294             status = READY;
295             Button button = mView.findViewById(R.id.nls_action_button);
296             button.setEnabled(true);
297         }
298 
299         @Override
autoStart()300         boolean autoStart() {
301             return true;
302         }
303 
304         @Override
test()305         protected void test() {
306             NotificationChannel channel = mNm.getNotificationChannel(mChannelId);
307             SharedPreferences prefs = mContext.getSharedPreferences(
308                     NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
309 
310             if (channel.getImportance() == IMPORTANCE_NONE) {
311                 if (prefs.contains(mChannelId) && prefs.getBoolean(mChannelId, false)) {
312                     status = PASS;
313                 } else {
314                     if (mRetries > 0) {
315                         mRetries--;
316                         status = RETEST;
317                     } else {
318                         status = FAIL;
319                     }
320                 }
321             } else {
322                 // user hasn't jumped to settings to block the channel yet
323                 status = WAIT_FOR_USER;
324             }
325 
326             next();
327         }
328 
tearDown()329         protected void tearDown() {
330             MockListener.getInstance().resetData();
331             mNm.deleteNotificationChannel(mChannelId);
332             SharedPreferences prefs = mContext.getSharedPreferences(
333                     NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
334             SharedPreferences.Editor editor = prefs.edit();
335             editor.remove(mChannelId);
336         }
337 
338         @Override
getIntent()339         protected Intent getIntent() {
340          return new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
341                  .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName())
342                  .putExtra(EXTRA_CHANNEL_ID, mChannelId);
343         }
344     }
345 
346     /**
347      * Creates a notification channel group. Sends the user to settings to block the group. Waits
348      * to receive the broadcast that the group was blocked, and confirms that the broadcast contains
349      * the correct extras.
350      */
351     protected class ReceiveGroupBlockNoticeTest extends InteractiveTestCase {
352         private String mGroupId;
353         private int mRetries = 2;
354         private View mView;
355         @Override
inflate(ViewGroup parent)356         protected View inflate(ViewGroup parent) {
357             mView = createNlsSettingsItem(parent, R.string.nls_block_group);
358             Button button = mView.findViewById(R.id.nls_action_button);
359             button.setEnabled(false);
360             return mView;
361         }
362 
363         @Override
setUp()364         protected void setUp() {
365             mGroupId = UUID.randomUUID().toString();
366             NotificationChannelGroup group
367                     = new NotificationChannelGroup(mGroupId, "ReceiveChannelGroupBlockNoticeTest");
368             mNm.createNotificationChannelGroup(group);
369             NotificationChannel channel = new NotificationChannel(
370                     mGroupId, "ReceiveChannelBlockNoticeTest", IMPORTANCE_LOW);
371             channel.setGroup(mGroupId);
372             mNm.createNotificationChannel(channel);
373             status = READY;
374             Button button = mView.findViewById(R.id.nls_action_button);
375             button.setEnabled(true);
376         }
377 
378         @Override
autoStart()379         boolean autoStart() {
380             return true;
381         }
382 
383         @Override
test()384         protected void test() {
385             NotificationChannelGroup group = mNm.getNotificationChannelGroup(mGroupId);
386             SharedPreferences prefs = mContext.getSharedPreferences(
387                     NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
388 
389             if (group.isBlocked()) {
390                 if (prefs.contains(mGroupId) && prefs.getBoolean(mGroupId, false)) {
391                     status = PASS;
392                 } else {
393                     if (mRetries > 0) {
394                         mRetries--;
395                         status = RETEST;
396                     } else {
397                         status = FAIL;
398                     }
399                 }
400             } else {
401                 // user hasn't jumped to settings to block the group yet
402                 status = WAIT_FOR_USER;
403             }
404 
405             next();
406         }
407 
tearDown()408         protected void tearDown() {
409             MockListener.getInstance().resetData();
410             mNm.deleteNotificationChannelGroup(mGroupId);
411             SharedPreferences prefs = mContext.getSharedPreferences(
412                     NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
413             SharedPreferences.Editor editor = prefs.edit();
414             editor.remove(mGroupId);
415         }
416 
417         @Override
getIntent()418         protected Intent getIntent() {
419             return new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
420                     .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
421                     .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName());
422         }
423     }
424 
425     /**
426      * Sends the user to settings to block the app. Waits to receive the broadcast that the app was
427      * blocked, and confirms that the broadcast contains the correct extras.
428      */
429     protected class ReceiveAppBlockNoticeTest extends InteractiveTestCase {
430         private int mRetries = 2;
431         private View mView;
432         @Override
inflate(ViewGroup parent)433         protected View inflate(ViewGroup parent) {
434             mView = createNlsSettingsItem(parent, R.string.nls_block_app);
435             Button button = mView.findViewById(R.id.nls_action_button);
436             button.setEnabled(false);
437             return mView;
438         }
439 
440         @Override
setUp()441         protected void setUp() {
442             status = READY;
443             Button button = mView.findViewById(R.id.nls_action_button);
444             button.setEnabled(true);
445         }
446 
447         @Override
autoStart()448         boolean autoStart() {
449             return true;
450         }
451 
452         @Override
test()453         protected void test() {
454             SharedPreferences prefs = mContext.getSharedPreferences(
455                     NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
456 
457             if (!mNm.areNotificationsEnabled()) {
458                 Log.d(TAG, "Got broadcast " + prefs.contains(mContext.getPackageName()));
459                 Log.d(TAG, "Broadcast contains correct data? " +
460                         prefs.getBoolean(mContext.getPackageName(), false));
461                 if (prefs.contains(mContext.getPackageName())
462                         && prefs.getBoolean(mContext.getPackageName(), false)) {
463                     status = PASS;
464                 } else {
465                     if (mRetries > 0) {
466                         mRetries--;
467                         status = RETEST;
468                     } else {
469                         status = FAIL;
470                     }
471                 }
472             } else {
473                 Log.d(TAG, "Notifications still enabled");
474                 // user hasn't jumped to settings to block the app yet
475                 status = WAIT_FOR_USER;
476             }
477 
478             next();
479         }
480 
tearDown()481         protected void tearDown() {
482             MockListener.getInstance().resetData();
483             SharedPreferences prefs = mContext.getSharedPreferences(
484                     NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
485             SharedPreferences.Editor editor = prefs.edit();
486             editor.remove(mContext.getPackageName());
487         }
488 
489         @Override
getIntent()490         protected Intent getIntent() {
491             return new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
492                     .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
493                     .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName());
494         }
495     }
496 
497     /**
498      * Sends the user to settings to unblock the app. Waits to receive the broadcast that the app
499      * was unblocked, and confirms that the broadcast contains the correct extras.
500      */
501     protected class ReceiveAppUnblockNoticeTest extends InteractiveTestCase {
502         private int mRetries = 2;
503         private View mView;
504         @Override
inflate(ViewGroup parent)505         protected View inflate(ViewGroup parent) {
506             mView = createNlsSettingsItem(parent, R.string.nls_unblock_app);
507             Button button = mView.findViewById(R.id.nls_action_button);
508             button.setEnabled(false);
509             return mView;
510         }
511 
512         @Override
setUp()513         protected void setUp() {
514             status = READY;
515             Button button = mView.findViewById(R.id.nls_action_button);
516             button.setEnabled(true);
517         }
518 
519         @Override
autoStart()520         boolean autoStart() {
521             return true;
522         }
523 
524         @Override
test()525         protected void test() {
526             SharedPreferences prefs = mContext.getSharedPreferences(
527                     NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
528 
529             if (mNm.areNotificationsEnabled()) {
530                 if (prefs.contains(mContext.getPackageName())
531                         && !prefs.getBoolean(mContext.getPackageName(), true)) {
532                     status = PASS;
533                 } else {
534                     if (mRetries > 0) {
535                         mRetries--;
536                         status = RETEST;
537                     } else {
538                         status = FAIL;
539                     }
540                 }
541             } else {
542                 // user hasn't jumped to settings to block the app yet
543                 status = WAIT_FOR_USER;
544             }
545 
546             next();
547         }
548 
tearDown()549         protected void tearDown() {
550             MockListener.getInstance().resetData();
551             SharedPreferences prefs = mContext.getSharedPreferences(
552                     NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
553             SharedPreferences.Editor editor = prefs.edit();
554             editor.remove(mContext.getPackageName());
555         }
556 
557         @Override
getIntent()558         protected Intent getIntent() {
559             return new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
560                     .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
561                     .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName());
562         }
563     }
564 
565     private class DataIntactTest extends InteractiveTestCase {
566         @Override
inflate(ViewGroup parent)567         protected View inflate(ViewGroup parent) {
568             return createAutoItem(parent, R.string.nls_payload_intact);
569         }
570 
571         @Override
setUp()572         protected void setUp() {
573             createChannels();
574             sendNotifications();
575             status = READY;
576         }
577 
578         @Override
test()579         protected void test() {
580             List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted());
581 
582             Set<String> found = new HashSet<String>();
583             if (result.size() == 0) {
584                 status = FAIL;
585                 return;
586             }
587             boolean pass = true;
588             for (JSONObject payload : result) {
589                 try {
590                     pass &= checkEquals(mPackageString,
591                             payload.getString(JSON_PACKAGE),
592                             "data integrity test: notification package (%s, %s)");
593                     String tag = payload.getString(JSON_TAG);
594                     if (mTag1.equals(tag)) {
595                         found.add(mTag1);
596                         pass &= checkEquals(mIcon1, payload.getInt(JSON_ICON),
597                                 "data integrity test: notification icon (%d, %d)");
598                         pass &= checkFlagSet(mFlag1, payload.getInt(JSON_FLAGS),
599                                 "data integrity test: notification flags (%d, %d)");
600                         pass &= checkEquals(mId1, payload.getInt(JSON_ID),
601                                 "data integrity test: notification ID (%d, %d)");
602                         pass &= checkEquals(mWhen1, payload.getLong(JSON_WHEN),
603                                 "data integrity test: notification when (%d, %d)");
604                     } else if (mTag2.equals(tag)) {
605                         found.add(mTag2);
606                         pass &= checkEquals(mIcon2, payload.getInt(JSON_ICON),
607                                 "data integrity test: notification icon (%d, %d)");
608                         pass &= checkFlagSet(mFlag2, payload.getInt(JSON_FLAGS),
609                                 "data integrity test: notification flags (%d, %d)");
610                         pass &= checkEquals(mId2, payload.getInt(JSON_ID),
611                                 "data integrity test: notification ID (%d, %d)");
612                         pass &= checkEquals(mWhen2, payload.getLong(JSON_WHEN),
613                                 "data integrity test: notification when (%d, %d)");
614                     } else if (mTag3.equals(tag)) {
615                         found.add(mTag3);
616                         pass &= checkEquals(mIcon3, payload.getInt(JSON_ICON),
617                                 "data integrity test: notification icon (%d, %d)");
618                         pass &= checkFlagSet(mFlag3, payload.getInt(JSON_FLAGS),
619                                 "data integrity test: notification flags (%d, %d)");
620                         pass &= checkEquals(mId3, payload.getInt(JSON_ID),
621                                 "data integrity test: notification ID (%d, %d)");
622                         pass &= checkEquals(mWhen3, payload.getLong(JSON_WHEN),
623                                 "data integrity test: notification when (%d, %d)");
624                     }
625                 } catch (JSONException e) {
626                     pass = false;
627                     Log.e(TAG, "failed to unpack data from mocklistener", e);
628                 }
629             }
630 
631             pass &= found.size() >= 3;
632             status = pass ? PASS : FAIL;
633         }
634 
635         @Override
tearDown()636         protected void tearDown() {
637             mNm.cancelAll();
638             MockListener.getInstance().resetData();
639             deleteChannels();
640         }
641     }
642 
643     private class AudiblyAlertedTest extends InteractiveTestCase {
644         @Override
inflate(ViewGroup parent)645         protected View inflate(ViewGroup parent) {
646             return createAutoItem(parent, R.string.nls_audibly_alerted);
647         }
648 
649         @Override
setUp()650         protected void setUp() {
651             createChannels();
652             sendNotifications();
653             sendNoisyNotification();
654             status = READY;
655         }
656 
657         @Override
test()658         protected void test() {
659             List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted());
660 
661             Set<String> found = new HashSet<>();
662             if (result.size() == 0) {
663                 status = FAIL;
664                 return;
665             }
666             boolean pass = true;
667             for (JSONObject payload : result) {
668                 try {
669                     String tag = payload.getString(JSON_TAG);
670                     if (mTag4.equals(tag)) {
671                         found.add(mTag4);
672                         boolean lastAudiblyAlertedSet
673                                 = payload.getLong(JSON_LAST_AUDIBLY_ALERTED) > -1;
674                         if (!lastAudiblyAlertedSet) {
675                             logWithStack(
676                                     "noisy notification test: getLastAudiblyAlertedMillis not set");
677                         }
678                         pass &= lastAudiblyAlertedSet;
679                     } else if (payload.getString(JSON_PACKAGE).equals(mPackageString)) {
680                         found.add(tag);
681                         boolean lastAudiblyAlertedSet
682                                 = payload.getLong(JSON_LAST_AUDIBLY_ALERTED) > -1;
683                         if (lastAudiblyAlertedSet) {
684                             logWithStack(
685                                     "noisy notification test: getLastAudiblyAlertedMillis set "
686                                             + "incorrectly");
687                         }
688                         pass &= !lastAudiblyAlertedSet;
689                     }
690                 } catch (JSONException e) {
691                     pass = false;
692                     Log.e(TAG, "failed to unpack data from mocklistener", e);
693                 }
694             }
695 
696             pass &= found.size() >= 4;
697             status = pass ? PASS : FAIL;
698         }
699 
700         @Override
tearDown()701         protected void tearDown() {
702             mNm.cancelAll();
703             MockListener.getInstance().resetData();
704             deleteChannels();
705         }
706     }
707 
708     private class DismissOneTest extends InteractiveTestCase {
709         @Override
inflate(ViewGroup parent)710         protected View inflate(ViewGroup parent) {
711             return createAutoItem(parent, R.string.nls_clear_one);
712         }
713 
714         @Override
setUp()715         protected void setUp() {
716             createChannels();
717             sendNotifications();
718             status = READY;
719         }
720 
721         @Override
test()722         protected void test() {
723             if (status == READY) {
724                 MockListener.getInstance().cancelNotification(
725                         MockListener.getInstance().getKeyForTag(mTag1));
726                 status = RETEST;
727             } else {
728                 List<String> result = new ArrayList<>(MockListener.getInstance().mRemoved);
729                 if (result.size() != 0
730                         && result.contains(mTag1)
731                         && !result.contains(mTag2)
732                         && !result.contains(mTag3)) {
733                     status = PASS;
734                 } else {
735                     logFail();
736                     status = FAIL;
737                 }
738             }
739         }
740 
741         @Override
tearDown()742         protected void tearDown() {
743             mNm.cancelAll();
744             deleteChannels();
745             MockListener.getInstance().resetData();
746         }
747     }
748 
749     private class DismissOneWithReasonTest extends InteractiveTestCase {
750         int mRetries = 3;
751 
752         @Override
inflate(ViewGroup parent)753         protected View inflate(ViewGroup parent) {
754             return createAutoItem(parent, R.string.nls_clear_one_reason);
755         }
756 
757         @Override
setUp()758         protected void setUp() {
759             createChannels();
760             sendNotifications();
761             status = READY;
762         }
763 
764         @Override
test()765         protected void test() {
766             if (status == READY) {
767                 MockListener.getInstance().cancelNotification(
768                         MockListener.getInstance().getKeyForTag(mTag1));
769                 status = RETEST;
770             } else {
771                 List<JSONObject> result =
772                         new ArrayList<>(MockListener.getInstance().mRemovedReason.values());
773                 boolean pass = false;
774                 for (JSONObject payload : result) {
775                     try {
776                         pass |= (checkEquals(mTag1,
777                                 payload.getString(JSON_TAG),
778                                 "data dismissal test: notification tag (%s, %s)")
779                                 && checkEquals(REASON_LISTENER_CANCEL,
780                                 payload.getInt(JSON_REASON),
781                                 "data dismissal test: reason (%d, %d)"));
782                         if(pass) {
783                             break;
784                         }
785                     } catch (JSONException e) {
786                         e.printStackTrace();
787                     }
788                 }
789                 if (pass) {
790                     status = PASS;
791                 } else {
792                     if (--mRetries > 0) {
793                         sleep(100);
794                         status = RETEST;
795                     } else {
796                         status = FAIL;
797                     }
798                 }
799             }
800         }
801 
802         @Override
tearDown()803         protected void tearDown() {
804             mNm.cancelAll();
805             deleteChannels();
806             MockListener.getInstance().resetData();
807         }
808     }
809 
810     private class DismissOneWithStatsTest extends InteractiveTestCase {
811         int mRetries = 3;
812 
813         @Override
inflate(ViewGroup parent)814         protected View inflate(ViewGroup parent) {
815             return createAutoItem(parent, R.string.nls_clear_one_stats);
816         }
817 
818         @Override
setUp()819         protected void setUp() {
820             createChannels();
821             sendNotifications();
822             status = READY;
823         }
824 
825         @Override
test()826         protected void test() {
827             if (status == READY) {
828                 MockListener.getInstance().cancelNotification(
829                         MockListener.getInstance().getKeyForTag(mTag1));
830                 status = RETEST;
831             } else {
832                 List<JSONObject> result =
833                         new ArrayList<>(MockListener.getInstance().mRemovedReason.values());
834                 boolean pass = true;
835                 for (JSONObject payload : result) {
836                     try {
837                         pass &= (payload.getBoolean(JSON_STATS) == false);
838                     } catch (JSONException e) {
839                         e.printStackTrace();
840                         pass = false;
841                     }
842                 }
843                 if (pass) {
844                     status = PASS;
845                 } else {
846                     if (--mRetries > 0) {
847                         sleep(100);
848                         status = RETEST;
849                     } else {
850                         logFail("Notification listener got populated stats object.");
851                         status = FAIL;
852                     }
853                 }
854             }
855         }
856 
857         @Override
tearDown()858         protected void tearDown() {
859             mNm.cancelAll();
860             deleteChannels();
861             MockListener.getInstance().resetData();
862         }
863     }
864 
865     private class DismissAllTest extends InteractiveTestCase {
866         @Override
inflate(ViewGroup parent)867         protected View inflate(ViewGroup parent) {
868             return createAutoItem(parent, R.string.nls_clear_all);
869         }
870 
871         @Override
setUp()872         protected void setUp() {
873             createChannels();
874             sendNotifications();
875             status = READY;
876         }
877 
878         @Override
test()879         protected void test() {
880             if (status == READY) {
881                 MockListener.getInstance().cancelAllNotifications();
882                 status = RETEST;
883             } else {
884                 List<String> result = new ArrayList<>(MockListener.getInstance().mRemoved);
885                 if (result.size() != 0
886                         && result.contains(mTag1)
887                         && result.contains(mTag2)
888                         && result.contains(mTag3)) {
889                     status = PASS;
890                 } else {
891                     logFail();
892                     status = FAIL;
893                 }
894             }
895         }
896 
897         @Override
tearDown()898         protected void tearDown() {
899             mNm.cancelAll();
900             deleteChannels();
901             MockListener.getInstance().resetData();
902         }
903     }
904 
905     private class IsDisabledTest extends InteractiveTestCase {
906         @Override
inflate(ViewGroup parent)907         protected View inflate(ViewGroup parent) {
908             return createNlsSettingsItem(parent, R.string.nls_disable_service);
909         }
910 
911         @Override
autoStart()912         boolean autoStart() {
913             return true;
914         }
915 
916         @Override
test()917         protected void test() {
918             String listeners = Secure.getString(getContentResolver(),
919                     ENABLED_NOTIFICATION_LISTENERS);
920             if (listeners == null || !listeners.contains(LISTENER_PATH)) {
921                 status = PASS;
922             } else {
923                 status = WAIT_FOR_USER;
924             }
925         }
926 
927         @Override
tearDown()928         protected void tearDown() {
929             MockListener.getInstance().resetData();
930         }
931 
932         @Override
getIntent()933         protected Intent getIntent() {
934             return new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS);
935         }
936     }
937 
938     private class ServiceStoppedTest extends InteractiveTestCase {
939         int mRetries = 3;
940         @Override
inflate(ViewGroup parent)941         protected View inflate(ViewGroup parent) {
942             return createAutoItem(parent, R.string.nls_service_stopped);
943         }
944 
945         @Override
test()946         protected void test() {
947             if (mNm.getEffectsSuppressor() == null && (MockListener.getInstance() == null
948                     || !MockListener.getInstance().isConnected)) {
949                 status = PASS;
950             } else {
951                 if (--mRetries > 0) {
952                     sleep(100);
953                     status = RETEST;
954                 } else {
955                     status = FAIL;
956                 }
957             }
958         }
959 
960         @Override
getIntent()961         protected Intent getIntent() {
962             return new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS);
963         }
964     }
965 
966     private class NotificationNotReceivedTest extends InteractiveTestCase {
967         @Override
inflate(ViewGroup parent)968         protected View inflate(ViewGroup parent) {
969             return createAutoItem(parent, R.string.nls_note_missed);
970 
971         }
972 
973         @Override
setUp()974         protected void setUp() {
975             createChannels();
976             sendNotifications();
977             status = READY;
978         }
979 
980         @Override
test()981         protected void test() {
982             if (MockListener.getInstance() == null) {
983                 status = PASS;
984             } else {
985                 List<String> result = new ArrayList<>(MockListener.getInstance().mPosted);
986                 if (result.size() == 0) {
987                     status = PASS;
988                 } else {
989                     logFail();
990                     status = FAIL;
991                 }
992             }
993             next();
994         }
995 
996         @Override
tearDown()997         protected void tearDown() {
998             mNm.cancelAll();
999             deleteChannels();
1000             if (MockListener.getInstance() != null) {
1001                 MockListener.getInstance().resetData();
1002             }
1003         }
1004     }
1005 
1006     private class RequestUnbindTest extends InteractiveTestCase {
1007         int mRetries = 5;
1008 
1009         @Override
inflate(ViewGroup parent)1010         protected View inflate(ViewGroup parent) {
1011             return createAutoItem(parent, R.string.nls_snooze);
1012 
1013         }
1014 
1015         @Override
setUp()1016         protected void setUp() {
1017             status = READY;
1018             MockListener.getInstance().requestListenerHints(
1019                     MockListener.HINT_HOST_DISABLE_CALL_EFFECTS);
1020         }
1021 
1022         @Override
test()1023         protected void test() {
1024             if (status == READY) {
1025                 MockListener.getInstance().requestUnbind();
1026                 status = RETEST;
1027             } else {
1028                 if (mNm.getEffectsSuppressor() == null && !MockListener.getInstance().isConnected) {
1029                     status = PASS;
1030                 } else {
1031                     if (--mRetries > 0) {
1032                         status = RETEST;
1033                     } else {
1034                         logFail();
1035                         status = FAIL;
1036                     }
1037                 }
1038                 next();
1039             }
1040         }
1041     }
1042 
1043     private class RequestBindTest extends InteractiveTestCase {
1044         int mRetries = 5;
1045 
1046         @Override
inflate(ViewGroup parent)1047         protected View inflate(ViewGroup parent) {
1048             return createAutoItem(parent, R.string.nls_unsnooze);
1049 
1050         }
1051 
1052         @Override
test()1053         protected void test() {
1054             if (status == READY) {
1055                 MockListener.requestRebind(MockListener.COMPONENT_NAME);
1056                 status = RETEST;
1057             } else {
1058                 if (MockListener.getInstance().isConnected) {
1059                     status = PASS;
1060                     next();
1061                 } else {
1062                     if (--mRetries > 0) {
1063                         status = RETEST;
1064                         next();
1065                     } else {
1066                         logFail();
1067                         status = FAIL;
1068                     }
1069                 }
1070             }
1071         }
1072     }
1073 
1074     private class EnableHintsTest extends InteractiveTestCase {
1075         @Override
inflate(ViewGroup parent)1076         protected View inflate(ViewGroup parent) {
1077             return createAutoItem(parent, R.string.nls_hints);
1078 
1079         }
1080 
1081         @Override
test()1082         protected void test() {
1083             if (status == READY) {
1084                 MockListener.getInstance().requestListenerHints(
1085                         MockListener.HINT_HOST_DISABLE_CALL_EFFECTS);
1086                 status = RETEST;
1087             } else {
1088                 int result = MockListener.getInstance().getCurrentListenerHints();
1089                 if ((result & MockListener.HINT_HOST_DISABLE_CALL_EFFECTS)
1090                         == MockListener.HINT_HOST_DISABLE_CALL_EFFECTS) {
1091                     status = PASS;
1092                     next();
1093                 } else {
1094                     logFail();
1095                     status = FAIL;
1096                 }
1097             }
1098         }
1099     }
1100 
1101     private class SnoozeNotificationForTimeTest extends InteractiveTestCase {
1102         final static int READY_TO_SNOOZE = 0;
1103         final static int SNOOZED = 1;
1104         final static int READY_TO_CHECK_FOR_UNSNOOZE = 2;
1105         int state = -1;
1106         long snoozeTime = 3000;
1107 
1108         @Override
inflate(ViewGroup parent)1109         protected View inflate(ViewGroup parent) {
1110             return createAutoItem(parent, R.string.nls_snooze_one_time);
1111         }
1112 
1113         @Override
setUp()1114         protected void setUp() {
1115             createChannels();
1116             sendNotifications();
1117             status = READY;
1118             state = READY_TO_SNOOZE;
1119             delay();
1120         }
1121 
1122         @Override
test()1123         protected void test() {
1124             status = RETEST;
1125             if (state == READY_TO_SNOOZE) {
1126                 MockListener.getInstance().snoozeNotification(
1127                         MockListener.getInstance().getKeyForTag(mTag1), snoozeTime);
1128                 state = SNOOZED;
1129             } else if (state == SNOOZED) {
1130                 List<JSONObject> result =
1131                         new ArrayList<>(MockListener.getInstance().mRemovedReason.values());
1132                 boolean pass = false;
1133                 for (JSONObject payload : result) {
1134                     try {
1135                         pass |= (checkEquals(mTag1,
1136                                 payload.getString(JSON_TAG),
1137                                 "data dismissal test: notification tag (%s, %s)")
1138                                 && checkEquals(MockListener.REASON_SNOOZED,
1139                                 payload.getInt(JSON_REASON),
1140                                 "data dismissal test: reason (%d, %d)"));
1141                         if (pass) {
1142                             break;
1143                         }
1144                     } catch (JSONException e) {
1145                         e.printStackTrace();
1146                     }
1147                 }
1148                 if (!pass) {
1149                     logFail();
1150                     status = FAIL;
1151                     next();
1152                     return;
1153                 } else {
1154                     state = READY_TO_CHECK_FOR_UNSNOOZE;
1155                 }
1156             } else {
1157                 List<String> result = new ArrayList<>(MockListener.getInstance().mPosted);
1158                 if (result.size() > 0 && result.contains(mTag1)) {
1159                     status = PASS;
1160                 } else {
1161                     logFail();
1162                     status = FAIL;
1163                 }
1164             }
1165         }
1166 
1167         @Override
tearDown()1168         protected void tearDown() {
1169             mNm.cancelAll();
1170             deleteChannels();
1171             MockListener.getInstance().resetData();
1172             delay();
1173         }
1174     }
1175 
1176     /**
1177      * Posts notifications, snoozes one of them. Verifies that it is snoozed. Cancels all
1178      * notifications and reposts them. Confirms that the original notification is still snoozed.
1179      */
1180     private class SnoozeNotificationForTimeCancelTest extends InteractiveTestCase {
1181 
1182         final static int READY_TO_SNOOZE = 0;
1183         final static int SNOOZED = 1;
1184         final static int READY_TO_CHECK_FOR_SNOOZE = 2;
1185         int state = -1;
1186         long snoozeTime = 10000;
1187         private String tag;
1188 
1189         @Override
inflate(ViewGroup parent)1190         protected View inflate(ViewGroup parent) {
1191             return createAutoItem(parent, R.string.nls_snooze_one_time);
1192         }
1193 
1194         @Override
setUp()1195         protected void setUp() {
1196             createChannels();
1197             sendNotifications();
1198             tag = mTag1;
1199             status = READY;
1200             state = READY_TO_SNOOZE;
1201             delay();
1202         }
1203 
1204         @Override
test()1205         protected void test() {
1206             status = RETEST;
1207             if (state == READY_TO_SNOOZE) {
1208                 MockListener.getInstance().snoozeNotification(
1209                         MockListener.getInstance().getKeyForTag(tag), snoozeTime);
1210                 state = SNOOZED;
1211             } else if (state == SNOOZED) {
1212                 List<String> result = getSnoozed();
1213                 if (result.size() >= 1
1214                         && result.contains(tag)) {
1215                     // cancel and repost
1216                     sendNotifications();
1217                     state = READY_TO_CHECK_FOR_SNOOZE;
1218                 } else {
1219                     logFail();
1220                     status = FAIL;
1221                 }
1222             } else {
1223                 List<String> result = getSnoozed();
1224                 if (result.size() >= 1
1225                         && result.contains(tag)) {
1226                     status = PASS;
1227                 } else {
1228                     logFail();
1229                     status = FAIL;
1230                 }
1231             }
1232         }
1233 
getSnoozed()1234         private List<String> getSnoozed() {
1235             List<String> result = new ArrayList<>();
1236             StatusBarNotification[] snoozed = MockListener.getInstance().getSnoozedNotifications();
1237             for (StatusBarNotification sbn : snoozed) {
1238                 result.add(sbn.getTag());
1239             }
1240             return result;
1241         }
1242 
1243         @Override
tearDown()1244         protected void tearDown() {
1245             mNm.cancelAll();
1246             deleteChannels();
1247             MockListener.getInstance().resetData();
1248         }
1249     }
1250 
1251     private class GetSnoozedNotificationTest extends InteractiveTestCase {
1252         final static int READY_TO_SNOOZE = 0;
1253         final static int SNOOZED = 1;
1254         final static int READY_TO_CHECK_FOR_GET_SNOOZE = 2;
1255         int state = -1;
1256         long snoozeTime = 30000;
1257 
1258         @Override
inflate(ViewGroup parent)1259         protected View inflate(ViewGroup parent) {
1260             return createAutoItem(parent, R.string.nls_get_snoozed);
1261         }
1262 
1263         @Override
setUp()1264         protected void setUp() {
1265             createChannels();
1266             sendNotifications();
1267             status = READY;
1268             state = READY_TO_SNOOZE;
1269         }
1270 
1271         @Override
test()1272         protected void test() {
1273             status = RETEST;
1274             if (state == READY_TO_SNOOZE) {
1275                 MockListener.getInstance().snoozeNotification(
1276                         MockListener.getInstance().getKeyForTag(mTag1), snoozeTime);
1277                 MockListener.getInstance().snoozeNotification(
1278                         MockListener.getInstance().getKeyForTag(mTag2), snoozeTime);
1279                 state = SNOOZED;
1280             } else if (state == SNOOZED) {
1281                 List<JSONObject> result =
1282                         new ArrayList<>(MockListener.getInstance().mRemovedReason.values());
1283                 if (result.size() == 0) {
1284                     status = FAIL;
1285                     return;
1286                 }
1287                 boolean pass = false;
1288                 for (JSONObject payload : result) {
1289                     try {
1290                         pass |= (checkEquals(mTag1,
1291                                 payload.getString(JSON_TAG),
1292                                 "data dismissal test: notification tag (%s, %s)")
1293                                 && checkEquals(MockListener.REASON_SNOOZED,
1294                                 payload.getInt(JSON_REASON),
1295                                 "data dismissal test: reason (%d, %d)"));
1296                         if (pass) {
1297                             break;
1298                         }
1299                     } catch (JSONException e) {
1300                         e.printStackTrace();
1301                     }
1302                 }
1303                 if (!pass) {
1304                     logFail();
1305                     status = FAIL;
1306                 } else {
1307                     state = READY_TO_CHECK_FOR_GET_SNOOZE;
1308                 }
1309             } else {
1310                 List<String> result = new ArrayList<>();
1311                 StatusBarNotification[] snoozed =
1312                         MockListener.getInstance().getSnoozedNotifications();
1313                 for (StatusBarNotification sbn : snoozed) {
1314                     result.add(sbn.getTag());
1315                 }
1316                 if (result.size() >= 2
1317                         && result.contains(mTag1)
1318                         && result.contains(mTag2)) {
1319                     status = PASS;
1320                 } else {
1321                     logFail();
1322                     status = FAIL;
1323                 }
1324             }
1325         }
1326 
1327         @Override
tearDown()1328         protected void tearDown() {
1329             mNm.cancelAll();
1330             deleteChannels();
1331             MockListener.getInstance().resetData();
1332             delay();
1333         }
1334     }
1335 
1336     /** Tests that the extras {@link Bundle} in a MessagingStyle#Message is preserved. */
1337     private class MessageBundleTest extends InteractiveTestCase {
1338         private final String extrasKey1 = "extras_key_1";
1339         private final CharSequence extrasValue1 = "extras_value_1";
1340         private final String extrasKey2 = "extras_key_2";
1341         private final CharSequence extrasValue2 = "extras_value_2";
1342 
1343         @Override
inflate(ViewGroup parent)1344         protected View inflate(ViewGroup parent) {
1345             return createAutoItem(parent, R.string.msg_extras_preserved);
1346         }
1347 
1348         @Override
setUp()1349         protected void setUp() {
1350             createChannels();
1351             sendMessagingNotification();
1352             status = READY;
1353         }
1354 
1355         @Override
tearDown()1356         protected void tearDown() {
1357             mNm.cancelAll();
1358             deleteChannels();
1359             delay();
1360         }
1361 
sendMessagingNotification()1362         private void sendMessagingNotification() {
1363             mTag1 = UUID.randomUUID().toString();
1364             mNm.cancelAll();
1365             mWhen1 = System.currentTimeMillis() + 1;
1366             mIcon1 = R.drawable.ic_stat_alice;
1367             mId1 = NOTIFICATION_ID + 1;
1368 
1369             Notification.MessagingStyle.Message msg1 =
1370                     new Notification.MessagingStyle.Message("text1", 0 /* timestamp */, "sender1");
1371             msg1.getExtras().putCharSequence(extrasKey1, extrasValue1);
1372 
1373             Notification.MessagingStyle.Message msg2 =
1374                     new Notification.MessagingStyle.Message("text2", 1 /* timestamp */, "sender2");
1375             msg2.getExtras().putCharSequence(extrasKey2, extrasValue2);
1376 
1377             Notification.MessagingStyle style = new Notification.MessagingStyle("display_name");
1378             style.addMessage(msg1);
1379             style.addMessage(msg2);
1380 
1381             Notification n1 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
1382                     .setContentTitle("ClearTest 1")
1383                     .setContentText(mTag1.toString())
1384                     .setPriority(Notification.PRIORITY_LOW)
1385                     .setSmallIcon(mIcon1)
1386                     .setWhen(mWhen1)
1387                     .setDeleteIntent(makeIntent(1, mTag1))
1388                     .setOnlyAlertOnce(true)
1389                     .setStyle(style)
1390                     .build();
1391             mNm.notify(mTag1, mId1, n1);
1392             mFlag1 = Notification.FLAG_ONLY_ALERT_ONCE;
1393         }
1394 
1395         // Returns true on success.
verifyMessage( NotificationCompat.MessagingStyle.Message message, String extrasKey, CharSequence extrasValue)1396         private boolean verifyMessage(
1397                 NotificationCompat.MessagingStyle.Message message,
1398                 String extrasKey,
1399                 CharSequence extrasValue) {
1400             return message.getExtras() != null
1401                     && message.getExtras().getCharSequence(extrasKey) != null
1402                     && message.getExtras().getCharSequence(extrasKey).equals(extrasValue);
1403         }
1404 
1405         @Override
test()1406         protected void test() {
1407             List<Notification> result =
1408                     new ArrayList<>(MockListener.getInstance().mPostedNotifications);
1409             if (result.size() != 1 || result.get(0) == null) {
1410                 logFail();
1411                 status = FAIL;
1412                 next();
1413                 return;
1414             }
1415             // Can only read in MessagingStyle using the compat class.
1416             NotificationCompat.MessagingStyle readStyle =
1417                     NotificationCompat.MessagingStyle
1418                             .extractMessagingStyleFromNotification(
1419                                     result.get(0));
1420             if (readStyle == null || readStyle.getMessages().size() != 2) {
1421                 status = FAIL;
1422                 logFail();
1423                 next();
1424                 return;
1425             }
1426 
1427             if (!verifyMessage(readStyle.getMessages().get(0), extrasKey1,
1428                     extrasValue1)
1429                     || !verifyMessage(
1430                     readStyle.getMessages().get(1), extrasKey2, extrasValue2)) {
1431                 status = FAIL;
1432                 logFail();
1433                 next();
1434                 return;
1435             }
1436 
1437             status = PASS;
1438         }
1439     }
1440 }
1441