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