• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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 android.app.notification.current.cts;
18 
19 import static android.Manifest.permission.POST_NOTIFICATIONS;
20 import static android.Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL;
21 import static android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS;
22 import static android.app.Activity.RESULT_OK;
23 import static android.app.AppOpsManager.MODE_ALLOWED;
24 import static android.app.AppOpsManager.MODE_DEFAULT;
25 import static android.app.AppOpsManager.MODE_ERRORED;
26 import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
27 import static android.app.Notification.FLAG_USER_INITIATED_JOB;
28 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
29 import static android.app.NotificationManager.IMPORTANCE_HIGH;
30 import static android.app.NotificationManager.IMPORTANCE_LOW;
31 import static android.app.NotificationManager.IMPORTANCE_MIN;
32 import static android.app.NotificationManager.IMPORTANCE_NONE;
33 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
34 import static android.content.pm.PackageManager.FEATURE_WATCH;
35 import static android.service.notification.NotificationListenerService.META_DATA_DEFAULT_AUTOBIND;
36 
37 import static com.google.common.truth.Truth.assertThat;
38 
39 import static org.hamcrest.CoreMatchers.hasItem;
40 import static org.junit.Assert.assertThat;
41 
42 import android.Manifest;
43 import android.app.Notification;
44 import android.app.NotificationChannel;
45 import android.app.NotificationChannelGroup;
46 import android.app.NotificationManager;
47 import android.app.PendingIntent;
48 import android.app.role.RoleManager;
49 import android.app.stubs.GetResultActivity;
50 import android.app.stubs.R;
51 import android.app.stubs.shared.FutureServiceConnection;
52 import android.app.stubs.shared.NotificationHelper.SEARCH_TYPE;
53 import android.app.stubs.shared.TestNotificationListener;
54 import android.content.ComponentName;
55 import android.content.ContentResolver;
56 import android.content.Context;
57 import android.content.Intent;
58 import android.content.ServiceConnection;
59 import android.content.pm.PackageInfo;
60 import android.content.pm.PackageManager;
61 import android.content.pm.ServiceInfo;
62 import android.content.res.AssetFileDescriptor;
63 import android.graphics.Bitmap;
64 import android.graphics.drawable.Icon;
65 import android.media.AudioAttributes;
66 import android.media.session.MediaSession;
67 import android.net.Uri;
68 import android.os.Build;
69 import android.os.Handler;
70 import android.os.IBinder;
71 import android.os.Looper;
72 import android.os.Message;
73 import android.os.Messenger;
74 import android.os.UserHandle;
75 import android.permission.PermissionManager;
76 import android.permission.cts.PermissionUtils;
77 import android.platform.test.annotations.AsbSecurityTest;
78 import android.provider.Settings;
79 import android.service.notification.NotificationListenerService;
80 import android.service.notification.StatusBarNotification;
81 import android.util.ArrayMap;
82 import android.util.Log;
83 import android.widget.RemoteViews;
84 
85 import androidx.annotation.NonNull;
86 import androidx.annotation.Nullable;
87 import androidx.test.platform.app.InstrumentationRegistry;
88 import androidx.test.uiautomator.UiDevice;
89 
90 import com.android.compatibility.common.util.PollingCheck;
91 import com.android.compatibility.common.util.SystemUtil;
92 import com.android.compatibility.common.util.ThrowingSupplier;
93 import com.android.test.notificationlistener.INLSControlService;
94 import com.android.test.notificationlistener.INotificationUriAccessService;
95 
96 import com.google.common.base.Preconditions;
97 
98 import java.io.BufferedReader;
99 import java.io.IOException;
100 import java.io.InputStreamReader;
101 import java.util.ArrayList;
102 import java.util.Arrays;
103 import java.util.Collections;
104 import java.util.HashMap;
105 import java.util.List;
106 import java.util.Map;
107 import java.util.UUID;
108 import java.util.concurrent.CompletableFuture;
109 import java.util.concurrent.CompletionException;
110 import java.util.concurrent.CountDownLatch;
111 import java.util.concurrent.ExecutionException;
112 import java.util.concurrent.Executor;
113 import java.util.concurrent.Semaphore;
114 import java.util.concurrent.TimeUnit;
115 import java.util.concurrent.TimeoutException;
116 
117 /* This tests NotificationListenerService together with NotificationManager, as you need to have
118  * notifications to manipulate in order to test the listener service. */
119 public class NotificationManagerTest extends BaseNotificationManagerTest {
120     public static final String NOTIFICATIONPROVIDER = "com.android.test.notificationprovider";
121     public static final String RICH_NOTIFICATION_ACTIVITY =
122             "com.android.test.notificationprovider.RichNotificationActivity";
123     final String TAG = NotificationManagerTest.class.getSimpleName();
124     final boolean DEBUG = false;
125 
126     private static final String DELEGATE_POST_CLASS = TEST_APP + ".NotificationDelegateAndPost";
127     private static final String REVOKE_CLASS = TEST_APP + ".NotificationRevoker";
128 
129     private static final String TRAMPOLINE_APP =
130             "com.android.test.notificationtrampoline.current";
131     private static final String TRAMPOLINE_APP_API_30 =
132             "com.android.test.notificationtrampoline.api30";
133     private static final String TRAMPOLINE_APP_API_32 =
134             "com.android.test.notificationtrampoline.api32";
135     private static final ComponentName TRAMPOLINE_SERVICE =
136             new ComponentName(TRAMPOLINE_APP,
137                     "com.android.test.notificationtrampoline.NotificationTrampolineTestService");
138     private static final ComponentName TRAMPOLINE_SERVICE_API_30 =
139             new ComponentName(TRAMPOLINE_APP_API_30,
140                     "com.android.test.notificationtrampoline.NotificationTrampolineTestService");
141     private static final ComponentName TRAMPOLINE_SERVICE_API_32 =
142             new ComponentName(TRAMPOLINE_APP_API_32,
143                     "com.android.test.notificationtrampoline.NotificationTrampolineTestService");
144 
145     private static final ComponentName URI_ACCESS_SERVICE = new ComponentName(
146             "com.android.test.notificationlistener",
147             "com.android.test.notificationlistener.NotificationUriAccessService");
148 
149     private static final ComponentName NLS_CONTROL_SERVICE = new ComponentName(
150             "com.android.test.notificationlistener",
151             "com.android.test.notificationlistener.NLSControlService");
152 
153     private static final ComponentName NO_AUTOBIND_NLS = new ComponentName(
154             "com.android.test.notificationlistener",
155             "com.android.test.notificationlistener.TestNotificationListenerNoAutobind");
156 
157     private static final String STUB_PACKAGE_NAME = "android.app.stubs";
158 
159     private static final long TIMEOUT_LONG_MS = 10000;
160     private static final long TIMEOUT_MS = 4000;
161 
162     private static final long POST_TIMEOUT = 200;
163     private static final int MESSAGE_BROADCAST_NOTIFICATION = 1;
164     private static final int MESSAGE_SERVICE_NOTIFICATION = 2;
165     private static final int MESSAGE_CLICK_NOTIFICATION = 3;
166 
167     private String mId;
168     private INotificationUriAccessService mNotificationUriAccessService;
169     private INLSControlService mNLSControlService;
170     private FutureServiceConnection mTrampolineConnection;
171 
172     @Nullable
173     private List<String> mPreviousDefaultBrowser;
174 
175     @Override
setUp()176     protected void setUp() throws Exception {
177         super.setUp();
178         PermissionUtils.grantPermission(mContext.getPackageName(), POST_NOTIFICATIONS);
179         PermissionUtils.grantPermission(STUB_PACKAGE_NAME, POST_NOTIFICATIONS);
180         PermissionUtils.grantPermission(TEST_APP, POST_NOTIFICATIONS);
181         PermissionUtils.grantPermission(TRAMPOLINE_APP, POST_NOTIFICATIONS);
182         PermissionUtils.grantPermission(TRAMPOLINE_APP_API_30, POST_NOTIFICATIONS);
183         PermissionUtils.grantPermission(TRAMPOLINE_APP_API_32, POST_NOTIFICATIONS);
184         PermissionUtils.grantPermission(NOTIFICATIONPROVIDER, POST_NOTIFICATIONS);
185 
186         // This will leave a set of channels on the device with each test run.
187         mId = UUID.randomUUID().toString();
188 
189         // delay between tests so notifications aren't dropped by the rate limiter
190         try {
191             Thread.sleep(500);
192         } catch (InterruptedException e) {
193         }
194     }
195 
196     @Override
tearDown()197     protected void tearDown() throws Exception {
198         super.tearDown();
199 
200         // For trampoline tests
201         if (mTrampolineConnection != null) {
202             mContext.unbindService(mTrampolineConnection);
203             mTrampolineConnection = null;
204         }
205         if (mListener != null) {
206             mListener.removeTestPackage(TRAMPOLINE_APP_API_30);
207             mListener.removeTestPackage(TRAMPOLINE_APP);
208         }
209         if (mPreviousDefaultBrowser != null) {
210             restoreDefaultBrowser();
211         }
212 
213         // Use test API to prevent PermissionManager from killing the test process when revoking
214         // permission.
215         SystemUtil.runWithShellPermissionIdentity(
216                 () -> mContext.getSystemService(PermissionManager.class)
217                         .revokePostNotificationPermissionWithoutKillForTest(
218                                 mContext.getPackageName(),
219                                 android.os.Process.myUserHandle().getIdentifier()),
220                 REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL,
221                 REVOKE_RUNTIME_PERMISSIONS);
222         PermissionUtils.revokePermission(STUB_PACKAGE_NAME, POST_NOTIFICATIONS);
223         PermissionUtils.revokePermission(TEST_APP, POST_NOTIFICATIONS);
224         PermissionUtils.revokePermission(TRAMPOLINE_APP, POST_NOTIFICATIONS);
225         PermissionUtils.revokePermission(NOTIFICATIONPROVIDER, POST_NOTIFICATIONS);
226     }
227 
getPendingIntent()228     private PendingIntent getPendingIntent() {
229         return PendingIntent.getActivity(
230                 getContext(), 0, new Intent(getContext(), this.getClass()),
231                 PendingIntent.FLAG_MUTABLE_UNAUDITED);
232     }
233 
isGroupSummary(Notification n)234     private boolean isGroupSummary(Notification n) {
235         return n.getGroup() != null && (n.flags & Notification.FLAG_GROUP_SUMMARY) != 0;
236     }
237 
assertOnlySomeNotificationsAutogrouped(List<Integer> autoGroupedIds)238     private void assertOnlySomeNotificationsAutogrouped(List<Integer> autoGroupedIds) {
239         String expectedGroupKey = null;
240         try {
241             // Posting can take ~100 ms
242             Thread.sleep(150);
243         } catch (InterruptedException e) {
244             e.printStackTrace();
245         }
246         StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
247         for (StatusBarNotification sbn : sbns) {
248             if (isGroupSummary(sbn.getNotification())
249                     || autoGroupedIds.contains(sbn.getId())) {
250                 assertTrue(sbn.getKey() + " is unexpectedly not autogrouped",
251                         sbn.getOverrideGroupKey() != null);
252                 if (expectedGroupKey == null) {
253                     expectedGroupKey = sbn.getGroupKey();
254                 }
255                 assertEquals(expectedGroupKey, sbn.getGroupKey());
256             } else {
257                 assertTrue(sbn.isGroup());
258                 assertTrue(sbn.getKey() + " is unexpectedly autogrouped,",
259                         sbn.getOverrideGroupKey() == null);
260                 assertTrue(sbn.getKey() + " has an unusual group key",
261                         sbn.getGroupKey() != expectedGroupKey);
262             }
263         }
264     }
265 
assertAllPostedNotificationsAutogrouped()266     private void assertAllPostedNotificationsAutogrouped() {
267         String expectedGroupKey = null;
268         try {
269             // Posting can take ~100 ms
270             Thread.sleep(150);
271         } catch (InterruptedException e) {
272             e.printStackTrace();
273         }
274         StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
275         for (StatusBarNotification sbn : sbns) {
276             // all notis should be in a group determined by autogrouping
277             assertTrue(sbn.getOverrideGroupKey() != null);
278             if (expectedGroupKey == null) {
279                 expectedGroupKey = sbn.getGroupKey();
280             }
281             // all notis should be in the same group
282             assertEquals(expectedGroupKey, sbn.getGroupKey());
283         }
284     }
285 
getCancellationReason(String key)286     private int getCancellationReason(String key) {
287         for (int tries = 3; tries-- > 0; ) {
288             if (mListener.mRemoved.containsKey(key)) {
289                 return mListener.mRemoved.get(key);
290             }
291             try {
292                 Thread.sleep(1000);
293             } catch (InterruptedException ex) {
294                 // pass
295             }
296         }
297         return -1;
298     }
299 
getAssistantCancellationReason(String key)300     private int getAssistantCancellationReason(String key) {
301         for (int tries = 3; tries-- > 0; ) {
302             if (mAssistant.mRemoved.containsKey(key)) {
303                 return mAssistant.mRemoved.get(key);
304             }
305             try {
306                 Thread.sleep(1000);
307             } catch (InterruptedException ex) {
308                 // pass
309             }
310         }
311         return -1;
312     }
313 
assertNotificationCount(int expectedCount)314     private void assertNotificationCount(int expectedCount) {
315         // notification is a bit asynchronous so it may take a few ms to appear in
316         // getActiveNotifications()
317         // we will check for it for up to 400ms before giving up
318         int lastCount = 0;
319         for (int tries = 4; tries-- > 0; ) {
320             final StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
321             lastCount = sbns.length;
322             if (expectedCount == lastCount) return;
323             try {
324                 Thread.sleep(100);
325             } catch (InterruptedException ex) {
326                 // pass
327             }
328         }
329         fail("Expected " + expectedCount + " posted notifications, were " + lastCount);
330     }
331 
compareChannels(NotificationChannel expected, NotificationChannel actual)332     private void compareChannels(NotificationChannel expected, NotificationChannel actual) {
333         if (actual == null) {
334             fail("actual channel is null");
335             return;
336         }
337         if (expected == null) {
338             fail("expected channel is null");
339             return;
340         }
341         assertEquals(expected.getId(), actual.getId());
342         assertEquals(expected.getName(), actual.getName());
343         assertEquals(expected.getDescription(), actual.getDescription());
344         assertEquals(expected.shouldVibrate(), actual.shouldVibrate());
345         assertEquals(expected.shouldShowLights(), actual.shouldShowLights());
346         assertEquals(expected.getLightColor(), actual.getLightColor());
347         assertEquals(expected.getImportance(), actual.getImportance());
348         if (expected.getSound() == null) {
349             assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actual.getSound());
350             assertEquals(Notification.AUDIO_ATTRIBUTES_DEFAULT, actual.getAudioAttributes());
351         } else {
352             assertEquals(expected.getSound(), actual.getSound());
353             assertEquals(expected.getAudioAttributes(), actual.getAudioAttributes());
354         }
355         assertTrue(Arrays.equals(expected.getVibrationPattern(), actual.getVibrationPattern()));
356         assertEquals(expected.getGroup(), actual.getGroup());
357         assertEquals(expected.getConversationId(), actual.getConversationId());
358         assertEquals(expected.getParentChannelId(), actual.getParentChannelId());
359         assertEquals(expected.isDemoted(), actual.isDemoted());
360     }
361 
sendTrampolineMessage(ComponentName component, int message, int notificationId, Handler callback)362     private void sendTrampolineMessage(ComponentName component, int message,
363             int notificationId, Handler callback) throws Exception {
364         if (mTrampolineConnection == null) {
365             Intent intent = new Intent();
366             intent.setComponent(component);
367             mTrampolineConnection = new FutureServiceConnection();
368             assertTrue(
369                     mContext.bindService(intent, mTrampolineConnection, Context.BIND_AUTO_CREATE));
370         }
371         Messenger service = new Messenger(mTrampolineConnection.get(TIMEOUT_MS));
372         service.send(Message.obtain(null, message, notificationId, -1, new Messenger(callback)));
373     }
374 
setDefaultBrowser(String packageName)375     private void setDefaultBrowser(String packageName) throws Exception {
376         UserHandle user = android.os.Process.myUserHandle();
377         mPreviousDefaultBrowser = SystemUtil.callWithShellPermissionIdentity(
378                 () -> mRoleManager.getRoleHoldersAsUser(RoleManager.ROLE_BROWSER, user));
379         CompletableFuture<Boolean> set = new CompletableFuture<>();
380         SystemUtil.runWithShellPermissionIdentity(
381                 () -> mRoleManager.addRoleHolderAsUser(RoleManager.ROLE_BROWSER, packageName, 0,
382                         user, mContext.getMainExecutor(), set::complete));
383         assertTrue("Failed to set " + packageName + " as default browser",
384                 set.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
385     }
386 
restoreDefaultBrowser()387     private void restoreDefaultBrowser() throws Exception {
388         Preconditions.checkState(mPreviousDefaultBrowser != null);
389         UserHandle user = android.os.Process.myUserHandle();
390         Executor executor = mContext.getMainExecutor();
391         CompletableFuture<Boolean> restored = new CompletableFuture<>();
392         SystemUtil.runWithShellPermissionIdentity(() -> {
393             mRoleManager.clearRoleHoldersAsUser(RoleManager.ROLE_BROWSER, 0, user, executor,
394                     restored::complete);
395             for (String packageName : mPreviousDefaultBrowser) {
396                 mRoleManager.addRoleHolderAsUser(RoleManager.ROLE_BROWSER, packageName,
397                         0, user, executor, restored::complete);
398             }
399         });
400         assertTrue("Failed to restore default browser",
401                 restored.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
402     }
403 
404     /**
405      * Previous tests could have started activities within the grace period, so go home to avoid
406      * allowing background activity starts due to this exemption.
407      */
deactivateGracePeriod()408     private void deactivateGracePeriod() {
409         UiDevice.getInstance(mInstrumentation).pressHome();
410     }
411 
verifyCanUseFullScreenIntent(int appOpState, boolean canSend)412     private void verifyCanUseFullScreenIntent(int appOpState, boolean canSend) throws Exception {
413         final int previousState = PermissionUtils.getAppOp(STUB_PACKAGE_NAME,
414                 Manifest.permission.USE_FULL_SCREEN_INTENT);
415         try {
416             PermissionUtils.setAppOp(STUB_PACKAGE_NAME,
417                     Manifest.permission.USE_FULL_SCREEN_INTENT,
418                     appOpState);
419 
420             if (canSend) {
421                 assertTrue(mNotificationManager.canUseFullScreenIntent());
422             } else {
423                 assertFalse(mNotificationManager.canUseFullScreenIntent());
424             }
425 
426         } finally {
427             // Clean up by setting to app op to previous state.
428             PermissionUtils.setAppOp(STUB_PACKAGE_NAME,
429                     Manifest.permission.USE_FULL_SCREEN_INTENT,
430                     previousState);
431         }
432     }
433 
testCanSendFullScreenIntent_modeDefault_returnsIsPermissionGranted()434     public void testCanSendFullScreenIntent_modeDefault_returnsIsPermissionGranted()
435             throws Exception {
436         final boolean isPermissionGranted = PermissionUtils.isPermissionGranted(STUB_PACKAGE_NAME,
437                 Manifest.permission.USE_FULL_SCREEN_INTENT);
438         verifyCanUseFullScreenIntent(MODE_DEFAULT, /*canSend=*/ isPermissionGranted);
439     }
440 
testCanSendFullScreenIntent_modeAllowed_returnsTrue()441     public void testCanSendFullScreenIntent_modeAllowed_returnsTrue() throws Exception {
442         verifyCanUseFullScreenIntent(MODE_ALLOWED, /*canSend=*/ true);
443     }
444 
testCanSendFullScreenIntent_modeErrored_returnsFalse()445     public void testCanSendFullScreenIntent_modeErrored_returnsFalse() throws Exception {
446         verifyCanUseFullScreenIntent(MODE_ERRORED, /*canSend=*/ false);
447     }
448 
testCreateChannelGroup()449     public void testCreateChannelGroup() throws Exception {
450         final NotificationChannelGroup ncg = new NotificationChannelGroup("a group", "a label");
451         final NotificationChannel channel =
452                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
453         channel.setGroup(ncg.getId());
454         mNotificationManager.createNotificationChannelGroup(ncg);
455         final NotificationChannel ungrouped =
456                 new NotificationChannel(mId + "!", "name", IMPORTANCE_DEFAULT);
457         try {
458             mNotificationManager.createNotificationChannel(channel);
459             mNotificationManager.createNotificationChannel(ungrouped);
460 
461             List<NotificationChannelGroup> ncgs =
462                     mNotificationManager.getNotificationChannelGroups();
463             assertEquals(1, ncgs.size());
464             assertEquals(ncg.getName(), ncgs.get(0).getName());
465             assertEquals(ncg.getDescription(), ncgs.get(0).getDescription());
466             assertEquals(channel.getId(), ncgs.get(0).getChannels().get(0).getId());
467         } finally {
468             mNotificationManager.deleteNotificationChannelGroup(ncg.getId());
469         }
470     }
471 
testGetChannelGroup()472     public void testGetChannelGroup() throws Exception {
473         final NotificationChannelGroup ncg = new NotificationChannelGroup("a group", "a label");
474         ncg.setDescription("bananas");
475         final NotificationChannelGroup ncg2 = new NotificationChannelGroup("group 2", "label 2");
476         final NotificationChannel channel =
477                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
478         channel.setGroup(ncg.getId());
479 
480         mNotificationManager.createNotificationChannelGroup(ncg);
481         mNotificationManager.createNotificationChannelGroup(ncg2);
482         mNotificationManager.createNotificationChannel(channel);
483 
484         NotificationChannelGroup actual =
485                 mNotificationManager.getNotificationChannelGroup(ncg.getId());
486         assertEquals(ncg.getId(), actual.getId());
487         assertEquals(ncg.getName(), actual.getName());
488         assertEquals(ncg.getDescription(), actual.getDescription());
489         assertEquals(channel.getId(), actual.getChannels().get(0).getId());
490     }
491 
testGetChannelGroups()492     public void testGetChannelGroups() throws Exception {
493         final NotificationChannelGroup ncg = new NotificationChannelGroup("a group", "a label");
494         ncg.setDescription("bananas");
495         final NotificationChannelGroup ncg2 = new NotificationChannelGroup("group 2", "label 2");
496         final NotificationChannel channel =
497                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
498         channel.setGroup(ncg2.getId());
499 
500         mNotificationManager.createNotificationChannelGroup(ncg);
501         mNotificationManager.createNotificationChannelGroup(ncg2);
502         mNotificationManager.createNotificationChannel(channel);
503 
504         List<NotificationChannelGroup> actual =
505                 mNotificationManager.getNotificationChannelGroups();
506         assertEquals(2, actual.size());
507         for (NotificationChannelGroup group : actual) {
508             if (group.getId().equals(ncg.getId())) {
509                 assertEquals(group.getName(), ncg.getName());
510                 assertEquals(group.getDescription(), ncg.getDescription());
511                 assertEquals(0, group.getChannels().size());
512             } else if (group.getId().equals(ncg2.getId())) {
513                 assertEquals(group.getName(), ncg2.getName());
514                 assertEquals(group.getDescription(), ncg2.getDescription());
515                 assertEquals(1, group.getChannels().size());
516                 assertEquals(channel.getId(), group.getChannels().get(0).getId());
517             } else {
518                 fail("Extra group found " + group.getId());
519             }
520         }
521     }
522 
testDeleteChannelGroup()523     public void testDeleteChannelGroup() throws Exception {
524         final NotificationChannelGroup ncg = new NotificationChannelGroup("a group", "a label");
525         final NotificationChannel channel =
526                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
527         channel.setGroup(ncg.getId());
528         mNotificationManager.createNotificationChannelGroup(ncg);
529         mNotificationManager.createNotificationChannel(channel);
530 
531         mNotificationManager.deleteNotificationChannelGroup(ncg.getId());
532 
533         assertNull(mNotificationManager.getNotificationChannel(channel.getId()));
534         assertEquals(0, mNotificationManager.getNotificationChannelGroups().size());
535     }
536 
testCreateChannel()537     public void testCreateChannel() throws Exception {
538         final NotificationChannel channel =
539                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
540         channel.setDescription("bananas");
541         channel.enableVibration(true);
542         channel.setVibrationPattern(new long[]{5, 8, 2, 1});
543         channel.setSound(new Uri.Builder().scheme("test").build(),
544                 new AudioAttributes.Builder().setUsage(
545                         AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED).build());
546         channel.enableLights(true);
547         channel.setBypassDnd(true);
548         channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
549         mNotificationManager.createNotificationChannel(channel);
550         final NotificationChannel createdChannel =
551                 mNotificationManager.getNotificationChannel(mId);
552         compareChannels(channel, createdChannel);
553         // Lockscreen Visibility and canBypassDnd no longer settable.
554         assertTrue(createdChannel.getLockscreenVisibility() != Notification.VISIBILITY_SECRET);
555         assertFalse(createdChannel.canBypassDnd());
556     }
557 
testCreateChannel_rename()558     public void testCreateChannel_rename() throws Exception {
559         NotificationChannel channel =
560                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
561         mNotificationManager.createNotificationChannel(channel);
562         channel.setName("new name");
563         mNotificationManager.createNotificationChannel(channel);
564         final NotificationChannel createdChannel =
565                 mNotificationManager.getNotificationChannel(mId);
566         compareChannels(channel, createdChannel);
567 
568         channel.setImportance(NotificationManager.IMPORTANCE_HIGH);
569         mNotificationManager.createNotificationChannel(channel);
570         assertEquals(NotificationManager.IMPORTANCE_DEFAULT,
571                 mNotificationManager.getNotificationChannel(mId).getImportance());
572     }
573 
testCreateChannel_addToGroup()574     public void testCreateChannel_addToGroup() throws Exception {
575         String oldGroup = null;
576         String newGroup = "new group";
577         mNotificationManager.createNotificationChannelGroup(
578                 new NotificationChannelGroup(newGroup, newGroup));
579 
580         NotificationChannel channel =
581                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
582         channel.setGroup(oldGroup);
583         mNotificationManager.createNotificationChannel(channel);
584 
585         channel.setGroup(newGroup);
586         mNotificationManager.createNotificationChannel(channel);
587 
588         final NotificationChannel updatedChannel =
589                 mNotificationManager.getNotificationChannel(mId);
590         assertEquals("Failed to add non-grouped channel to a group on update ",
591                 newGroup, updatedChannel.getGroup());
592     }
593 
testCreateChannel_cannotChangeGroup()594     public void testCreateChannel_cannotChangeGroup() throws Exception {
595         String oldGroup = "old group";
596         String newGroup = "new group";
597         mNotificationManager.createNotificationChannelGroup(
598                 new NotificationChannelGroup(oldGroup, oldGroup));
599         mNotificationManager.createNotificationChannelGroup(
600                 new NotificationChannelGroup(newGroup, newGroup));
601 
602         NotificationChannel channel =
603                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
604         channel.setGroup(oldGroup);
605         mNotificationManager.createNotificationChannel(channel);
606         channel.setGroup(newGroup);
607         mNotificationManager.createNotificationChannel(channel);
608         final NotificationChannel updatedChannel =
609                 mNotificationManager.getNotificationChannel(mId);
610         assertEquals("Channels should not be allowed to change groups",
611                 oldGroup, updatedChannel.getGroup());
612     }
613 
testCreateSameChannelDoesNotUpdate()614     public void testCreateSameChannelDoesNotUpdate() throws Exception {
615         final NotificationChannel channel =
616                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
617         mNotificationManager.createNotificationChannel(channel);
618         final NotificationChannel channelDupe =
619                 new NotificationChannel(mId, "name", IMPORTANCE_HIGH);
620         mNotificationManager.createNotificationChannel(channelDupe);
621         final NotificationChannel createdChannel =
622                 mNotificationManager.getNotificationChannel(mId);
623         compareChannels(channel, createdChannel);
624     }
625 
testCreateChannelAlreadyExistsNoOp()626     public void testCreateChannelAlreadyExistsNoOp() throws Exception {
627         NotificationChannel channel =
628                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
629         mNotificationManager.createNotificationChannel(channel);
630         NotificationChannel channelDupe =
631                 new NotificationChannel(mId, "name", IMPORTANCE_HIGH);
632         mNotificationManager.createNotificationChannel(channelDupe);
633         compareChannels(channel, mNotificationManager.getNotificationChannel(channel.getId()));
634     }
635 
testCreateChannelWithGroup()636     public void testCreateChannelWithGroup() throws Exception {
637         NotificationChannelGroup ncg = new NotificationChannelGroup("g", "n");
638         mNotificationManager.createNotificationChannelGroup(ncg);
639         try {
640             NotificationChannel channel =
641                     new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
642             channel.setGroup(ncg.getId());
643             mNotificationManager.createNotificationChannel(channel);
644             compareChannels(channel, mNotificationManager.getNotificationChannel(channel.getId()));
645         } finally {
646             mNotificationManager.deleteNotificationChannelGroup(ncg.getId());
647         }
648     }
649 
testCreateChannelWithBadGroup()650     public void testCreateChannelWithBadGroup() throws Exception {
651         NotificationChannel channel =
652                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
653         channel.setGroup("garbage");
654         try {
655             mNotificationManager.createNotificationChannel(channel);
656             fail("Created notification with bad group");
657         } catch (IllegalArgumentException e) {
658         }
659     }
660 
testCreateChannelInvalidImportance()661     public void testCreateChannelInvalidImportance() throws Exception {
662         NotificationChannel channel =
663                 new NotificationChannel(mId, "name", IMPORTANCE_UNSPECIFIED);
664         try {
665             mNotificationManager.createNotificationChannel(channel);
666         } catch (IllegalArgumentException e) {
667             //success
668         }
669     }
670 
testDeleteChannel()671     public void testDeleteChannel() throws Exception {
672         NotificationChannel channel =
673                 new NotificationChannel(mId, "name", IMPORTANCE_LOW);
674         mNotificationManager.createNotificationChannel(channel);
675         compareChannels(channel, mNotificationManager.getNotificationChannel(channel.getId()));
676         mNotificationManager.deleteNotificationChannel(channel.getId());
677         assertNull(mNotificationManager.getNotificationChannel(channel.getId()));
678     }
679 
testCannotDeleteDefaultChannel()680     public void testCannotDeleteDefaultChannel() throws Exception {
681         try {
682             mNotificationManager.deleteNotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID);
683             fail("Deleted default channel");
684         } catch (IllegalArgumentException e) {
685             //success
686         }
687     }
688 
testGetChannel()689     public void testGetChannel() throws Exception {
690         NotificationChannel channel1 =
691                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
692         NotificationChannel channel2 =
693                 new NotificationChannel(
694                         UUID.randomUUID().toString(), "name2", IMPORTANCE_HIGH);
695         NotificationChannel channel3 =
696                 new NotificationChannel(
697                         UUID.randomUUID().toString(), "name3", IMPORTANCE_LOW);
698         NotificationChannel channel4 =
699                 new NotificationChannel(
700                         UUID.randomUUID().toString(), "name4", IMPORTANCE_MIN);
701         mNotificationManager.createNotificationChannel(channel1);
702         mNotificationManager.createNotificationChannel(channel2);
703         mNotificationManager.createNotificationChannel(channel3);
704         mNotificationManager.createNotificationChannel(channel4);
705 
706         compareChannels(channel2,
707                 mNotificationManager.getNotificationChannel(channel2.getId()));
708         compareChannels(channel3,
709                 mNotificationManager.getNotificationChannel(channel3.getId()));
710         compareChannels(channel1,
711                 mNotificationManager.getNotificationChannel(channel1.getId()));
712         compareChannels(channel4,
713                 mNotificationManager.getNotificationChannel(channel4.getId()));
714     }
715 
testGetChannels()716     public void testGetChannels() throws Exception {
717         NotificationChannel channel1 =
718                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
719         NotificationChannel channel2 =
720                 new NotificationChannel(
721                         UUID.randomUUID().toString(), "name2", IMPORTANCE_HIGH);
722         NotificationChannel channel3 =
723                 new NotificationChannel(
724                         UUID.randomUUID().toString(), "name3", IMPORTANCE_LOW);
725         NotificationChannel channel4 =
726                 new NotificationChannel(
727                         UUID.randomUUID().toString(), "name4", IMPORTANCE_MIN);
728 
729         Map<String, NotificationChannel> channelMap = new HashMap<>();
730         channelMap.put(channel1.getId(), channel1);
731         channelMap.put(channel2.getId(), channel2);
732         channelMap.put(channel3.getId(), channel3);
733         channelMap.put(channel4.getId(), channel4);
734         mNotificationManager.createNotificationChannel(channel1);
735         mNotificationManager.createNotificationChannel(channel2);
736         mNotificationManager.createNotificationChannel(channel3);
737         mNotificationManager.createNotificationChannel(channel4);
738 
739         mNotificationManager.deleteNotificationChannel(channel3.getId());
740 
741         List<NotificationChannel> channels = mNotificationManager.getNotificationChannels();
742         for (NotificationChannel nc : channels) {
743             if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
744                 continue;
745             }
746             if (NOTIFICATION_CHANNEL_ID.equals(nc.getId())) {
747                 continue;
748             }
749             assertFalse(channel3.getId().equals(nc.getId()));
750             if (!channelMap.containsKey(nc.getId())) {
751                 // failed cleanup from prior test run; ignore
752                 continue;
753             }
754             compareChannels(channelMap.get(nc.getId()), nc);
755         }
756     }
757 
testRecreateDeletedChannel()758     public void testRecreateDeletedChannel() throws Exception {
759         NotificationChannel channel =
760                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
761         channel.setShowBadge(true);
762         NotificationChannel newChannel = new NotificationChannel(
763                 channel.getId(), channel.getName(), IMPORTANCE_HIGH);
764         mNotificationManager.createNotificationChannel(channel);
765         mNotificationManager.deleteNotificationChannel(channel.getId());
766 
767         mNotificationManager.createNotificationChannel(newChannel);
768 
769         compareChannels(channel,
770                 mNotificationManager.getNotificationChannel(newChannel.getId()));
771     }
772 
testNotify()773     public void testNotify() throws Exception {
774         mNotificationManager.cancelAll();
775 
776         final int id = 1;
777         sendNotification(id, R.drawable.black);
778         // test updating the same notification
779         sendNotification(id, R.drawable.blue);
780         sendNotification(id, R.drawable.yellow);
781 
782         // assume that sendNotification tested to make sure individual notifications were present
783         StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
784         for (StatusBarNotification sbn : sbns) {
785             if (sbn.getId() != id) {
786                 fail("we got back other notifications besides the one we posted: "
787                         + sbn.getKey());
788             }
789         }
790     }
791 
testNotify_nonexistentChannel_ignored()792     public void testNotify_nonexistentChannel_ignored() {
793         mNotificationManager.cancelAll();
794         final int id = 404;
795         final Notification notification =
796                 new Notification.Builder(mContext, "non_existent_channel")
797                         .setSmallIcon(R.drawable.black)
798                         .setContentText("This should not be posted!")
799                         .build();
800 
801         mNotificationManager.notify(id, notification);
802 
803         assertThat(mNotificationHelper.findPostedNotification(null, id, SEARCH_TYPE.APP)).isNull();
804         assertThat(mNotificationManager.getActiveNotifications()).isEmpty();
805     }
806 
testSuspendPackage_withoutShellPermission()807     public void testSuspendPackage_withoutShellPermission() throws Exception {
808         if (mActivityManager.isLowRamDevice() && !mPackageManager.hasSystemFeature(FEATURE_WATCH)) {
809             return;
810         }
811 
812         try {
813             Process proc = Runtime.getRuntime().exec("cmd notification suspend_package "
814                     + mContext.getPackageName());
815 
816             // read output of command
817             BufferedReader reader =
818                     new BufferedReader(new InputStreamReader(proc.getInputStream()));
819             StringBuilder output = new StringBuilder();
820             String line = reader.readLine();
821             while (line != null) {
822                 output.append(line);
823                 line = reader.readLine();
824             }
825             reader.close();
826             final String outputString = output.toString();
827 
828             proc.waitFor();
829 
830             // check that the output string had an error / disallowed call since it didn't have
831             // shell permission to suspend the package
832             assertTrue(outputString, outputString.contains("error"));
833             assertTrue(outputString, outputString.contains("permission denied"));
834         } catch (InterruptedException e) {
835             fail("Unsuccessful shell command");
836         }
837     }
838 
testSuspendPackage()839     public void testSuspendPackage() throws Exception {
840         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
841         assertNotNull(mListener);
842 
843         CountDownLatch notificationPostedLatch = mListener.setPostedCountDown(1);
844         sendNotification(1, R.drawable.black);
845         // wait for notification listener to receive notification
846         notificationPostedLatch.await(500, TimeUnit.MILLISECONDS);
847         assertEquals(1, mListener.mPosted.size());
848 
849         CountDownLatch notificationRankingLatch = mListener.setRankingUpdateCountDown(1);
850         // suspend package, ranking should be updated with suspended = true
851         suspendPackage(mContext.getPackageName(), InstrumentationRegistry.getInstrumentation(),
852                 true);
853         // wait for notification listener to get response
854         notificationRankingLatch.await(500, TimeUnit.MILLISECONDS);
855         NotificationListenerService.RankingMap rankingMap = mListener.mRankingMap;
856         NotificationListenerService.Ranking outRanking = new NotificationListenerService.Ranking();
857         for (String key : rankingMap.getOrderedKeys()) {
858             if (key.contains(mListener.getPackageName())) {
859                 rankingMap.getRanking(key, outRanking);
860                 Log.d(TAG, "key=" + key + " suspended=" + outRanking.isSuspended());
861                 assertTrue(outRanking.isSuspended());
862             }
863         }
864 
865         notificationRankingLatch = mListener.setRankingUpdateCountDown(1);
866         // unsuspend package, ranking should be updated with suspended = false
867         suspendPackage(mContext.getPackageName(), InstrumentationRegistry.getInstrumentation(),
868                 false);
869         // wait for notification listener to get response
870         notificationRankingLatch.await(500, TimeUnit.MILLISECONDS);
871         rankingMap = mListener.mRankingMap;
872         for (String key : rankingMap.getOrderedKeys()) {
873             if (key.contains(mListener.getPackageName())) {
874                 rankingMap.getRanking(key, outRanking);
875                 Log.d(TAG, "key=" + key + " suspended=" + outRanking.isSuspended());
876                 assertFalse(outRanking.isSuspended());
877             }
878         }
879 
880         mListener.resetData();
881     }
882 
testSuspendedPackageSendsNotification()883     public void testSuspendedPackageSendsNotification() throws Exception {
884         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
885         assertNotNull(mListener);
886         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
887 
888         // suspend package, post notification while package is suspended, see notification
889         // in ranking map with suspended = true
890         suspendPackage(mContext.getPackageName(), InstrumentationRegistry.getInstrumentation(),
891                 true);
892         sendNotification(1, R.drawable.black);
893         // wait for notification listener to receive notification
894         postedLatch.await(500, TimeUnit.MILLISECONDS);
895         assertEquals(1, mListener.mPosted.size()); // apps targeting P receive notification
896         NotificationListenerService.RankingMap rankingMap = mListener.mRankingMap;
897         NotificationListenerService.Ranking outRanking = new NotificationListenerService.Ranking();
898         for (String key : rankingMap.getOrderedKeys()) {
899             if (key.contains(mListener.getPackageName())) {
900                 rankingMap.getRanking(key, outRanking);
901                 Log.d(TAG, "key=" + key + " suspended=" + outRanking.isSuspended());
902                 assertTrue(outRanking.isSuspended());
903             }
904         }
905 
906         // unsuspend package, ranking should be updated with suspended = false
907         suspendPackage(mContext.getPackageName(), InstrumentationRegistry.getInstrumentation(),
908                 false);
909         Thread.sleep(500); // wait for notification listener to get response
910         assertEquals(1, mListener.mPosted.size()); // should see previously posted notification
911         rankingMap = mListener.mRankingMap;
912         for (String key : rankingMap.getOrderedKeys()) {
913             if (key.contains(mListener.getPackageName())) {
914                 rankingMap.getRanking(key, outRanking);
915                 Log.d(TAG, "key=" + key + " suspended=" + outRanking.isSuspended());
916                 assertFalse(outRanking.isSuspended());
917             }
918         }
919 
920         mListener.resetData();
921     }
922 
testShowBadging_ranking()923     public void testShowBadging_ranking() throws Exception {
924         final int originalBadging = Settings.Secure.getInt(
925                 mContext.getContentResolver(), Settings.Secure.NOTIFICATION_BADGING);
926 
927         SystemUtil.runWithShellPermissionIdentity(() ->
928                 Settings.Secure.putInt(mContext.getContentResolver(),
929                         Settings.Secure.NOTIFICATION_BADGING, 1));
930         assertEquals(1, Settings.Secure.getInt(
931                 mContext.getContentResolver(), Settings.Secure.NOTIFICATION_BADGING));
932 
933         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
934         assertNotNull(mListener);
935         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
936         try {
937             sendNotification(1, R.drawable.black);
938             // wait for notification listener to receive notification
939             postedLatch.await(500, TimeUnit.MILLISECONDS);
940             NotificationListenerService.RankingMap rankingMap = mListener.mRankingMap;
941             NotificationListenerService.Ranking outRanking =
942                     new NotificationListenerService.Ranking();
943             for (String key : rankingMap.getOrderedKeys()) {
944                 if (key.contains(mListener.getPackageName())) {
945                     rankingMap.getRanking(key, outRanking);
946                     assertTrue(outRanking.canShowBadge());
947                 }
948             }
949 
950             CountDownLatch rankingUpdateLatch = mListener.setRankingUpdateCountDown(1);
951 
952             // turn off badging globally
953             SystemUtil.runWithShellPermissionIdentity(() ->
954                     Settings.Secure.putInt(mContext.getContentResolver(),
955                             Settings.Secure.NOTIFICATION_BADGING, 0));
956 
957             // wait for ranking update
958             rankingUpdateLatch.await(500, TimeUnit.MILLISECONDS);
959 
960             rankingMap = mListener.mRankingMap;
961             outRanking = new NotificationListenerService.Ranking();
962             for (String key : rankingMap.getOrderedKeys()) {
963                 if (key.contains(mListener.getPackageName())) {
964                     assertFalse(outRanking.canShowBadge());
965                 }
966             }
967 
968             mListener.resetData();
969         } finally {
970             SystemUtil.runWithShellPermissionIdentity(() ->
971                     Settings.Secure.putInt(mContext.getContentResolver(),
972                             Settings.Secure.NOTIFICATION_BADGING, originalBadging));
973         }
974     }
975 
testKeyChannelGroupOverrideImportanceExplanation_ranking()976     public void testKeyChannelGroupOverrideImportanceExplanation_ranking() throws Exception {
977         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
978         assertNotNull(mListener);
979 
980         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
981 
982         final int notificationId = 1;
983         sendNotification(notificationId, R.drawable.black);
984         // wait for notification listener to receive notification
985         postedLatch.await(500, TimeUnit.MILLISECONDS);
986 
987         NotificationListenerService.RankingMap rankingMap = mListener.mRankingMap;
988         NotificationListenerService.Ranking outRanking =
989                 new NotificationListenerService.Ranking();
990 
991         StatusBarNotification sbn = mNotificationHelper.findPostedNotification(null, notificationId,
992                 SEARCH_TYPE.POSTED);
993 
994         // check that the key and channel ids are the same in the ranking as the posted notification
995         for (String key : rankingMap.getOrderedKeys()) {
996             if (key.contains(mListener.getPackageName())) {
997                 rankingMap.getRanking(key, outRanking);
998 
999                 // check notification key match
1000                 assertEquals(sbn.getKey(), outRanking.getKey());
1001 
1002                 // check notification channel ids match
1003                 assertEquals(sbn.getNotification().getChannelId(), outRanking.getChannel().getId());
1004 
1005                 // check override group key match
1006                 assertEquals(sbn.getOverrideGroupKey(), outRanking.getOverrideGroupKey());
1007 
1008                 // check importance explanation isn't null
1009                 assertNotNull(outRanking.getImportanceExplanation());
1010             }
1011         }
1012     }
1013 
testNotify_blockedChannel()1014     public void testNotify_blockedChannel() throws Exception {
1015         mNotificationManager.cancelAll();
1016 
1017         NotificationChannel channel =
1018                 new NotificationChannel(mId, "name", IMPORTANCE_NONE);
1019         mNotificationManager.createNotificationChannel(channel);
1020 
1021         int id = 1;
1022         final Notification notification =
1023                 new Notification.Builder(mContext, mId)
1024                         .setSmallIcon(R.drawable.black)
1025                         .setWhen(System.currentTimeMillis())
1026                         .setContentTitle("notify#" + id)
1027                         .setContentText("This is #" + id + "notification  ")
1028                         .build();
1029         mNotificationManager.notify(id, notification);
1030 
1031         assertTrue(mNotificationHelper.isNotificationGone(id, SEARCH_TYPE.APP));
1032     }
1033 
testCancel()1034     public void testCancel() throws Exception {
1035         final int id = 9;
1036         sendNotification(id, R.drawable.black);
1037         // Wait for the notification posted not just enqueued
1038         try {
1039             Thread.sleep(500);
1040         } catch (InterruptedException e) {
1041         }
1042         mNotificationManager.cancel(id);
1043 
1044         assertTrue(mNotificationHelper.isNotificationGone(id, SEARCH_TYPE.APP));
1045     }
1046 
testCancelAll()1047     public void testCancelAll() throws Exception {
1048         sendNotification(1, R.drawable.black);
1049         sendNotification(2, R.drawable.blue);
1050         sendNotification(3, R.drawable.yellow);
1051 
1052         if (DEBUG) {
1053             Log.d(TAG, "posted 3 notifications, here they are: ");
1054             StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
1055             for (StatusBarNotification sbn : sbns) {
1056                 Log.d(TAG, "  " + sbn);
1057             }
1058             Log.d(TAG, "about to cancel...");
1059         }
1060         mNotificationManager.cancelAll();
1061 
1062         for (int id = 1; id <= 3; id++) {
1063             assertTrue(mNotificationHelper.isNotificationGone(id, SEARCH_TYPE.APP));
1064         }
1065 
1066     }
1067 
testNotifyWithTimeout()1068     public void testNotifyWithTimeout() throws Exception {
1069         mNotificationManager.cancelAll();
1070         final int id = 128;
1071         final long timeout = 1000;
1072 
1073         final Notification notification =
1074                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
1075                         .setSmallIcon(R.drawable.black)
1076                         .setContentTitle("notify#" + id)
1077                         .setContentText("This is #" + id + "notification  ")
1078                         .setTimeoutAfter(timeout)
1079                         .build();
1080         mNotificationManager.notify(id, notification);
1081 
1082         assertNotNull(mNotificationHelper.findPostedNotification(null, id, SEARCH_TYPE.APP));
1083 
1084         try {
1085             Thread.sleep(timeout);
1086         } catch (InterruptedException ex) {
1087             // pass
1088         }
1089         assertTrue(mNotificationHelper.isNotificationGone(id, SEARCH_TYPE.APP));
1090     }
1091 
testStyle()1092     public void testStyle() throws Exception {
1093         Notification.Style style = new Notification.Style() {
1094             public boolean areNotificationsVisiblyDifferent(Notification.Style other) {
1095                 return false;
1096             }
1097         };
1098 
1099         Notification.Builder builder = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID);
1100         style.setBuilder(builder);
1101 
1102         Notification notification = null;
1103         try {
1104             notification = style.build();
1105         } catch (IllegalArgumentException e) {
1106             fail(e.getMessage());
1107         }
1108 
1109         assertNotNull(notification);
1110 
1111         Notification builderNotification = builder.build();
1112         assertEquals(builderNotification, notification);
1113     }
1114 
testStyle_getStandardView()1115     public void testStyle_getStandardView() throws Exception {
1116         Notification.Builder builder = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID);
1117         int layoutId = 0;
1118 
1119         TestStyle overrideStyle = new TestStyle();
1120         overrideStyle.setBuilder(builder);
1121         RemoteViews result = overrideStyle.testGetStandardView(layoutId);
1122 
1123         assertNotNull(result);
1124         assertEquals(layoutId, result.getLayoutId());
1125     }
1126 
1127     private class TestStyle extends Notification.Style {
areNotificationsVisiblyDifferent(Notification.Style other)1128         public boolean areNotificationsVisiblyDifferent(Notification.Style other) {
1129             return false;
1130         }
1131 
testGetStandardView(int layoutId)1132         public RemoteViews testGetStandardView(int layoutId) {
1133             // Wrapper method, since getStandardView is protected and otherwise unused in Android
1134             return getStandardView(layoutId);
1135         }
1136     }
1137 
testMediaStyle_empty()1138     public void testMediaStyle_empty() {
1139         Notification.MediaStyle style = new Notification.MediaStyle();
1140         assertNotNull(style);
1141     }
1142 
testMediaStyle()1143     public void testMediaStyle() {
1144         mNotificationManager.cancelAll();
1145         final int id = 99;
1146         MediaSession session = new MediaSession(getContext(), "media");
1147 
1148         final Notification notification =
1149                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
1150                         .setSmallIcon(R.drawable.black)
1151                         .setContentTitle("notify#" + id)
1152                         .setContentText("This is #" + id + "notification  ")
1153                         .addAction(new Notification.Action.Builder(
1154                                 Icon.createWithResource(getContext(), R.drawable.icon_black),
1155                                 "play", getPendingIntent()).build())
1156                         .addAction(new Notification.Action.Builder(
1157                                 Icon.createWithResource(getContext(), R.drawable.icon_blue),
1158                                 "pause", getPendingIntent()).build())
1159                         .setStyle(new Notification.MediaStyle()
1160                                 .setShowActionsInCompactView(0, 1)
1161                                 .setMediaSession(session.getSessionToken()))
1162                         .build();
1163         mNotificationManager.notify(id, notification);
1164 
1165         assertNotNull(mNotificationHelper.findPostedNotification(null, id, SEARCH_TYPE.APP));
1166     }
1167 
testInboxStyle()1168     public void testInboxStyle() {
1169         final int id = 100;
1170 
1171         final Notification notification =
1172                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
1173                         .setSmallIcon(R.drawable.black)
1174                         .setContentTitle("notify#" + id)
1175                         .setContentText("This is #" + id + "notification  ")
1176                         .addAction(new Notification.Action.Builder(
1177                                 Icon.createWithResource(getContext(), R.drawable.icon_black),
1178                                 "a1", getPendingIntent()).build())
1179                         .addAction(new Notification.Action.Builder(
1180                                 Icon.createWithResource(getContext(), R.drawable.icon_blue),
1181                                 "a2", getPendingIntent()).build())
1182                         .setStyle(new Notification.InboxStyle().addLine("line")
1183                                 .setSummaryText("summary"))
1184                         .build();
1185         mNotificationManager.notify(id, notification);
1186 
1187         assertNotNull(mNotificationHelper.findPostedNotification(null, id, SEARCH_TYPE.APP));
1188     }
1189 
testBigTextStyle()1190     public void testBigTextStyle() {
1191         final int id = 101;
1192 
1193         final Notification notification =
1194                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
1195                         .setSmallIcon(R.drawable.black)
1196                         .setContentTitle("notify#" + id)
1197                         .setContentText("This is #" + id + "notification  ")
1198                         .addAction(new Notification.Action.Builder(
1199                                 Icon.createWithResource(getContext(), R.drawable.icon_black),
1200                                 "a1", getPendingIntent()).build())
1201                         .addAction(new Notification.Action.Builder(
1202                                 Icon.createWithResource(getContext(), R.drawable.icon_blue),
1203                                 "a2", getPendingIntent()).build())
1204                         .setStyle(new Notification.BigTextStyle()
1205                                 .setBigContentTitle("big title")
1206                                 .bigText("big text")
1207                                 .setSummaryText("summary"))
1208                         .build();
1209         mNotificationManager.notify(id, notification);
1210 
1211         assertNotNull(mNotificationHelper.findPostedNotification(null, id, SEARCH_TYPE.APP));
1212     }
1213 
testBigPictureStyle()1214     public void testBigPictureStyle() {
1215         final int id = 102;
1216 
1217         final Notification notification =
1218                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
1219                         .setSmallIcon(R.drawable.black)
1220                         .setContentTitle("notify#" + id)
1221                         .setContentText("This is #" + id + "notification  ")
1222                         .addAction(new Notification.Action.Builder(
1223                                 Icon.createWithResource(getContext(), R.drawable.icon_black),
1224                                 "a1", getPendingIntent()).build())
1225                         .addAction(new Notification.Action.Builder(
1226                                 Icon.createWithResource(getContext(), R.drawable.icon_blue),
1227                                 "a2", getPendingIntent()).build())
1228                         .setStyle(new Notification.BigPictureStyle()
1229                                 .setBigContentTitle("title")
1230                                 .bigPicture(Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565))
1231                                 .bigLargeIcon(
1232                                         Icon.createWithResource(getContext(), R.drawable.icon_blue))
1233                                 .setSummaryText("summary")
1234                                 .setContentDescription("content description"))
1235                         .build();
1236         mNotificationManager.notify(id, notification);
1237 
1238         assertNotNull(mNotificationHelper.findPostedNotification(null, id, SEARCH_TYPE.APP));
1239     }
1240 
testAutogrouping()1241     public void testAutogrouping() throws Exception {
1242         sendNotification(801, R.drawable.black);
1243         sendNotification(802, R.drawable.blue);
1244         sendNotification(803, R.drawable.yellow);
1245         sendNotification(804, R.drawable.yellow);
1246 
1247         assertNotificationCount(5);
1248         assertAllPostedNotificationsAutogrouped();
1249     }
1250 
testAutogrouping_autogroupStaysUntilAllNotificationsCanceled()1251     public void testAutogrouping_autogroupStaysUntilAllNotificationsCanceled() throws Exception {
1252         sendNotification(701, R.drawable.black);
1253         sendNotification(702, R.drawable.blue);
1254         sendNotification(703, R.drawable.yellow);
1255         sendNotification(704, R.drawable.yellow);
1256 
1257         assertNotificationCount(5);
1258         assertAllPostedNotificationsAutogrouped();
1259 
1260         // Assert all notis stay in the same autogroup until all children are canceled
1261         for (int i = 704; i > 701; i--) {
1262             cancelAndPoll(i);
1263             assertNotificationCount(i - 700);
1264             assertAllPostedNotificationsAutogrouped();
1265         }
1266         cancelAndPoll(701);
1267         assertNotificationCount(0);
1268     }
1269 
testAutogrouping_autogroupStaysUntilAllNotificationsAddedToGroup()1270     public void testAutogrouping_autogroupStaysUntilAllNotificationsAddedToGroup()
1271             throws Exception {
1272         String newGroup = "new!";
1273         sendNotification(901, R.drawable.black);
1274         sendNotification(902, R.drawable.blue);
1275         sendNotification(903, R.drawable.yellow);
1276         sendNotification(904, R.drawable.yellow);
1277 
1278         List<Integer> postedIds = new ArrayList<>();
1279         postedIds.add(901);
1280         postedIds.add(902);
1281         postedIds.add(903);
1282         postedIds.add(904);
1283 
1284         assertNotificationCount(5);
1285         assertAllPostedNotificationsAutogrouped();
1286 
1287         // Assert all notis stay in the same autogroup until all children are canceled
1288         for (int i = 904; i > 901; i--) {
1289             sendNotification(i, newGroup, R.drawable.blue);
1290             postedIds.remove(postedIds.size() - 1);
1291             assertNotificationCount(5);
1292             assertOnlySomeNotificationsAutogrouped(postedIds);
1293         }
1294         sendNotification(901, newGroup, R.drawable.blue);
1295         assertNotificationCount(4); // no more autogroup summary
1296         postedIds.remove(0);
1297         assertOnlySomeNotificationsAutogrouped(postedIds);
1298     }
1299 
testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled()1300     public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled()
1301             throws Exception {
1302         String newGroup = "new!";
1303         sendNotification(910, R.drawable.black);
1304         sendNotification(920, R.drawable.blue);
1305         sendNotification(930, R.drawable.yellow);
1306         sendNotification(940, R.drawable.yellow);
1307 
1308         List<Integer> postedIds = new ArrayList<>();
1309         postedIds.add(910);
1310         postedIds.add(920);
1311         postedIds.add(930);
1312         postedIds.add(940);
1313 
1314         assertNotificationCount(5);
1315         assertAllPostedNotificationsAutogrouped();
1316 
1317         // regroup all but one of the children
1318         for (int i = postedIds.size() - 1; i > 0; i--) {
1319             try {
1320                 Thread.sleep(200);
1321             } catch (InterruptedException ex) {
1322                 // pass
1323             }
1324             int id = postedIds.remove(i);
1325             sendNotification(id, newGroup, R.drawable.blue);
1326             assertNotificationCount(5);
1327             assertOnlySomeNotificationsAutogrouped(postedIds);
1328         }
1329 
1330         // send a new non-grouped notification. since the autogroup summary still exists,
1331         // the notification should be added to it
1332         sendNotification(950, R.drawable.blue);
1333         postedIds.add(950);
1334         try {
1335             Thread.sleep(200);
1336         } catch (InterruptedException ex) {
1337             // pass
1338         }
1339         assertOnlySomeNotificationsAutogrouped(postedIds);
1340     }
1341 
testPostFullScreenIntent_permission()1342     public void testPostFullScreenIntent_permission() {
1343         int id = 6000;
1344 
1345         final Notification notification =
1346                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
1347                         .setSmallIcon(R.drawable.black)
1348                         .setWhen(System.currentTimeMillis())
1349                         .setFullScreenIntent(getPendingIntent(), true)
1350                         .setContentText("This is #FSI notification")
1351                         .setContentIntent(getPendingIntent())
1352                         .build();
1353         mNotificationManager.notify(id, notification);
1354 
1355         StatusBarNotification n = mNotificationHelper.findPostedNotification(
1356                 null, id, SEARCH_TYPE.APP);
1357         assertNotNull(n);
1358         assertEquals(notification.fullScreenIntent, n.getNotification().fullScreenIntent);
1359     }
1360 
testNotificationDelegate_grantAndPost()1361     public void testNotificationDelegate_grantAndPost() throws Exception {
1362         final Intent intent = new Intent(mContext, GetResultActivity.class);
1363         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1364         GetResultActivity activity = (GetResultActivity) mInstrumentation.startActivitySync(intent);
1365         mInstrumentation.waitForIdleSync();
1366         activity.clearResult();
1367 
1368         // grant this test permission to post
1369         final Intent activityIntent = new Intent();
1370         activityIntent.setPackage(TEST_APP);
1371         activityIntent.setAction(Intent.ACTION_MAIN);
1372         activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
1373 
1374         activity.startActivityForResult(activityIntent, REQUEST_CODE);
1375         assertEquals(RESULT_OK, activity.getResult().resultCode);
1376 
1377         // send notification
1378         Notification n = new Notification.Builder(mContext, "channel")
1379                 .setSmallIcon(android.R.drawable.ic_media_play)
1380                 .build();
1381         mNotificationManager.notifyAsPackage(TEST_APP, "tag", 0, n);
1382 
1383         assertNotNull(mNotificationHelper.findPostedNotification("tag", 0, SEARCH_TYPE.APP));
1384         final Intent revokeIntent = new Intent(Intent.ACTION_MAIN);
1385         revokeIntent.setClassName(TEST_APP, REVOKE_CLASS);
1386         activity.startActivityForResult(revokeIntent, REQUEST_CODE);
1387         assertEquals(RESULT_OK, activity.getResult().resultCode);
1388     }
1389 
testNotificationDelegate_grantAndPostAndCancel()1390     public void testNotificationDelegate_grantAndPostAndCancel() throws Exception {
1391         final Intent intent = new Intent(mContext, GetResultActivity.class);
1392         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1393         GetResultActivity activity = (GetResultActivity) mInstrumentation.startActivitySync(intent);
1394         mInstrumentation.waitForIdleSync();
1395         activity.clearResult();
1396 
1397         // grant this test permission to post
1398         final Intent activityIntent = new Intent();
1399         activityIntent.setPackage(TEST_APP);
1400         activityIntent.setAction(Intent.ACTION_MAIN);
1401         activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
1402 
1403         activity.startActivityForResult(activityIntent, REQUEST_CODE);
1404         assertEquals(RESULT_OK, activity.getResult().resultCode);
1405 
1406         // send notification
1407         Notification n = new Notification.Builder(mContext, "channel")
1408                 .setSmallIcon(android.R.drawable.ic_media_play)
1409                 .build();
1410         mNotificationManager.notifyAsPackage(TEST_APP, "toBeCanceled", 10000, n);
1411         assertNotNull(mNotificationHelper.findPostedNotification("toBeCanceled", 10000,
1412                 SEARCH_TYPE.APP));
1413         mNotificationManager.cancelAsPackage(TEST_APP, "toBeCanceled", 10000);
1414         assertTrue(mNotificationHelper.isNotificationGone(10000, SEARCH_TYPE.APP));
1415         final Intent revokeIntent = new Intent(Intent.ACTION_MAIN);
1416         revokeIntent.setClassName(TEST_APP, REVOKE_CLASS);
1417         activity.startActivityForResult(revokeIntent, REQUEST_CODE);
1418         assertEquals(RESULT_OK, activity.getResult().resultCode);
1419     }
1420 
testNotificationDelegate_cannotCancelNotificationsPostedByDelegator()1421     public void testNotificationDelegate_cannotCancelNotificationsPostedByDelegator()
1422             throws Exception {
1423         final Intent intent = new Intent(mContext, GetResultActivity.class);
1424         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1425         GetResultActivity activity = (GetResultActivity) mInstrumentation.startActivitySync(intent);
1426         mInstrumentation.waitForIdleSync();
1427         activity.clearResult();
1428 
1429         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
1430         assertNotNull(mListener);
1431 
1432         // grant this test permission to post
1433         final Intent activityIntent = new Intent(Intent.ACTION_MAIN);
1434         activityIntent.setClassName(TEST_APP, DELEGATE_POST_CLASS);
1435 
1436         activity.startActivityForResult(activityIntent, REQUEST_CODE);
1437         assertEquals(RESULT_OK, activity.getResult().resultCode);
1438 
1439         assertNotNull(mNotificationHelper.findPostedNotification(null, 9, SEARCH_TYPE.LISTENER));
1440 
1441         try {
1442             mNotificationManager.cancelAsPackage(TEST_APP, null, 9);
1443             fail("Delegate should not be able to cancel notification they did not post");
1444         } catch (SecurityException e) {
1445             // yay
1446         }
1447 
1448         // double check that the notification does still exist
1449         assertNotNull(mNotificationHelper.findPostedNotification(null, 9, SEARCH_TYPE.LISTENER));
1450 
1451         final Intent revokeIntent = new Intent(Intent.ACTION_MAIN);
1452         revokeIntent.setClassName(TEST_APP, REVOKE_CLASS);
1453         activity.startActivityForResult(revokeIntent, REQUEST_CODE);
1454         assertEquals(RESULT_OK, activity.getResult().resultCode);
1455     }
1456 
testNotificationDelegate_grantAndReadChannels()1457     public void testNotificationDelegate_grantAndReadChannels() throws Exception {
1458         final Intent intent = new Intent(mContext, GetResultActivity.class);
1459         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1460         GetResultActivity activity = (GetResultActivity) mInstrumentation.startActivitySync(intent);
1461         mInstrumentation.waitForIdleSync();
1462         activity.clearResult();
1463 
1464         // grant this test permission to post
1465         final Intent activityIntent = new Intent();
1466         activityIntent.setPackage(TEST_APP);
1467         activityIntent.setAction(Intent.ACTION_MAIN);
1468         activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
1469 
1470         activity.startActivityForResult(activityIntent, REQUEST_CODE);
1471         assertEquals(RESULT_OK, activity.getResult().resultCode);
1472 
1473         List<NotificationChannel> channels =
1474                 mContext.createPackageContextAsUser(TEST_APP, /* flags= */ 0, mContext.getUser())
1475                         .getSystemService(NotificationManager.class)
1476                         .getNotificationChannels();
1477 
1478         assertNotNull(channels);
1479 
1480         final Intent revokeIntent = new Intent(Intent.ACTION_MAIN);
1481         revokeIntent.setClassName(TEST_APP, REVOKE_CLASS);
1482         activity.startActivityForResult(revokeIntent, REQUEST_CODE);
1483         assertEquals(RESULT_OK, activity.getResult().resultCode);
1484     }
1485 
testNotificationDelegate_grantAndReadChannel()1486     public void testNotificationDelegate_grantAndReadChannel() throws Exception {
1487         final Intent intent = new Intent(mContext, GetResultActivity.class);
1488         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1489         GetResultActivity activity = (GetResultActivity) mInstrumentation.startActivitySync(intent);
1490         mInstrumentation.waitForIdleSync();
1491         activity.clearResult();
1492 
1493         // grant this test permission to post
1494         final Intent activityIntent = new Intent();
1495         activityIntent.setPackage(TEST_APP);
1496         activityIntent.setAction(Intent.ACTION_MAIN);
1497         activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
1498 
1499         activity.startActivityForResult(activityIntent, REQUEST_CODE);
1500         assertEquals(RESULT_OK, activity.getResult().resultCode);
1501 
1502         NotificationChannel channel =
1503                 mContext.createPackageContextAsUser(TEST_APP, /* flags= */ 0, mContext.getUser())
1504                         .getSystemService(NotificationManager.class)
1505                         .getNotificationChannel("channel");
1506 
1507         assertNotNull(channel);
1508 
1509         final Intent revokeIntent = new Intent(Intent.ACTION_MAIN);
1510         revokeIntent.setClassName(TEST_APP, REVOKE_CLASS);
1511         activity.startActivityForResult(revokeIntent, REQUEST_CODE);
1512         assertEquals(RESULT_OK, activity.getResult().resultCode);
1513     }
1514 
testNotificationDelegate_grantAndRevoke()1515     public void testNotificationDelegate_grantAndRevoke() throws Exception {
1516         final Intent intent = new Intent(mContext, GetResultActivity.class);
1517         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1518         GetResultActivity activity = (GetResultActivity) mInstrumentation.startActivitySync(intent);
1519         mInstrumentation.waitForIdleSync();
1520         activity.clearResult();
1521 
1522         // grant this test permission to post
1523         final Intent activityIntent = new Intent();
1524         activityIntent.setPackage(TEST_APP);
1525         activityIntent.setAction(Intent.ACTION_MAIN);
1526         activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
1527 
1528         activity.startActivityForResult(activityIntent, REQUEST_CODE);
1529         assertEquals(RESULT_OK, activity.getResult().resultCode);
1530 
1531         assertTrue(mNotificationManager.canNotifyAsPackage(TEST_APP));
1532 
1533         final Intent revokeIntent = new Intent(Intent.ACTION_MAIN);
1534         revokeIntent.setClassName(TEST_APP, REVOKE_CLASS);
1535         activity.startActivityForResult(revokeIntent, REQUEST_CODE);
1536         assertEquals(RESULT_OK, activity.getResult().resultCode);
1537 
1538         try {
1539             // send notification
1540             Notification n = new Notification.Builder(mContext, "channel")
1541                     .setSmallIcon(android.R.drawable.ic_media_play)
1542                     .build();
1543             mNotificationManager.notifyAsPackage(TEST_APP, "tag", 0, n);
1544             fail("Should not be able to post as a delegate when permission revoked");
1545         } catch (SecurityException e) {
1546             // yay
1547         }
1548     }
1549 
testNotificationIcon()1550     public void testNotificationIcon() {
1551         int id = 6000;
1552 
1553         Notification notification =
1554                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
1555                         .setSmallIcon(android.R.drawable.ic_media_play)
1556                         .setWhen(System.currentTimeMillis())
1557                         .setFullScreenIntent(getPendingIntent(), true)
1558                         .setContentText("This notification has a resource icon")
1559                         .setContentIntent(getPendingIntent())
1560                         .build();
1561         mNotificationManager.notify(id, notification);
1562 
1563         notification =
1564                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
1565                         .setSmallIcon(Icon.createWithResource(mContext, android.R.id.icon))
1566                         .setWhen(System.currentTimeMillis())
1567                         .setFullScreenIntent(getPendingIntent(), true)
1568                         .setContentText("This notification has an Icon icon")
1569                         .setContentIntent(getPendingIntent())
1570                         .build();
1571         mNotificationManager.notify(id, notification);
1572 
1573         StatusBarNotification n = mNotificationHelper.findPostedNotification(
1574                 null, id, SEARCH_TYPE.APP);
1575         assertNotNull(n);
1576     }
1577 
testShouldHideSilentStatusIcons()1578     public void testShouldHideSilentStatusIcons() throws Exception {
1579         try {
1580             mNotificationManager.shouldHideSilentStatusBarIcons();
1581             fail("Non-privileged apps should not get this information");
1582         } catch (SecurityException e) {
1583             // pass
1584         }
1585 
1586         mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
1587         // no exception this time
1588         mNotificationManager.shouldHideSilentStatusBarIcons();
1589     }
1590 
1591     /* Confirm that the optional methods of TestNotificationListener still exist and
1592      * don't fail. */
testNotificationListenerMethods()1593     public void testNotificationListenerMethods() {
1594         NotificationListenerService listener = new TestNotificationListener();
1595         listener.onListenerConnected();
1596 
1597         listener.onSilentStatusBarIconsVisibilityChanged(false);
1598 
1599         listener.onNotificationPosted(null);
1600         listener.onNotificationPosted(null, null);
1601 
1602         listener.onNotificationRemoved(null);
1603         listener.onNotificationRemoved(null, null);
1604 
1605         listener.onNotificationChannelGroupModified("", UserHandle.CURRENT, null,
1606                 NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_ADDED);
1607         listener.onNotificationChannelModified("", UserHandle.CURRENT, null,
1608                 NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_ADDED);
1609 
1610         listener.onListenerDisconnected();
1611     }
1612 
performNotificationProviderAction(@onNull String action)1613     private void performNotificationProviderAction(@NonNull String action) {
1614         // Create an intent to launch an activity which just posts or cancels notifications
1615         Intent activityIntent = new Intent(Intent.ACTION_MAIN);
1616         activityIntent.setClassName(NOTIFICATIONPROVIDER, RICH_NOTIFICATION_ACTIVITY);
1617         activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1618         activityIntent.putExtra("action", action);
1619         mContext.startActivity(activityIntent);
1620     }
1621 
testNotificationUriPermissionsGranted()1622     public void testNotificationUriPermissionsGranted() throws Exception {
1623         Uri background7Uri = Uri.parse(
1624                 "content://com.android.test.notificationprovider.provider/background7.png");
1625         Uri background8Uri = Uri.parse(
1626                 "content://com.android.test.notificationprovider.provider/background8.png");
1627 
1628         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
1629         assertNotNull(mListener);
1630 
1631         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
1632 
1633         try {
1634             // Post #7
1635             performNotificationProviderAction("send-7");
1636 
1637             postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
1638             assertEquals(background7Uri, getNotificationBackgroundImageUri(7));
1639             assertTrue(mNotificationHelper.isNotificationGone(8, SEARCH_TYPE.LISTENER));
1640             assertAccessible(background7Uri);
1641             assertInaccessible(background8Uri);
1642 
1643             // Reset the notification posted latch.
1644             postedLatch = mListener.setPostedCountDown(1);
1645 
1646             // Post #8
1647             performNotificationProviderAction("send-8");
1648 
1649             postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
1650             assertEquals(background7Uri, getNotificationBackgroundImageUri(7));
1651             assertEquals(background8Uri, getNotificationBackgroundImageUri(8));
1652             assertAccessible(background7Uri);
1653             assertAccessible(background8Uri);
1654 
1655             // Add a notification removed latch.
1656             CountDownLatch removedLatch = mListener.setRemovedCountDown(1);
1657 
1658             // Cancel #7
1659             performNotificationProviderAction("cancel-7");
1660 
1661             removedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
1662             assertTrue(mNotificationHelper.isNotificationGone(7, SEARCH_TYPE.LISTENER));
1663             assertEquals(background8Uri, getNotificationBackgroundImageUri(8));
1664             assertInaccessible(background7Uri);
1665             assertAccessible(background8Uri);
1666 
1667             // Reset the notification reemoved latch.
1668             removedLatch = mListener.setRemovedCountDown(1);
1669 
1670             // Cancel #8
1671             performNotificationProviderAction("cancel-8");
1672 
1673             removedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
1674             assertTrue(mNotificationHelper.isNotificationGone(7, SEARCH_TYPE.LISTENER));
1675             assertTrue(mNotificationHelper.isNotificationGone(8, SEARCH_TYPE.LISTENER));
1676             assertInaccessible(background7Uri);
1677             assertInaccessible(background8Uri);
1678 
1679         } finally {
1680             // Clean up -- reset any remaining notifications
1681             performNotificationProviderAction("reset");
1682             Thread.sleep(500);
1683         }
1684     }
1685 
testNotificationUriPermissionsGrantedToNewListeners()1686     public void testNotificationUriPermissionsGrantedToNewListeners() throws Exception {
1687         Uri background7Uri = Uri.parse(
1688                 "content://com.android.test.notificationprovider.provider/background7.png");
1689 
1690         try {
1691             // Post #7
1692             performNotificationProviderAction("send-7");
1693             // Don't have access the notification yet, but we can test the URI
1694             assertInaccessible(background7Uri);
1695 
1696             mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
1697             assertNotNull(mListener);
1698 
1699             mNotificationHelper.findPostedNotification(null, 7, SEARCH_TYPE.LISTENER);
1700 
1701             assertEquals(background7Uri, getNotificationBackgroundImageUri(7));
1702             assertAccessible(background7Uri);
1703 
1704         } finally {
1705             // Clean Up -- Cancel #7
1706             performNotificationProviderAction("cancel-7");
1707             Thread.sleep(500);
1708         }
1709     }
1710 
testNotificationUriPermissionsRevokedFromRemovedListeners()1711     public void testNotificationUriPermissionsRevokedFromRemovedListeners() throws Exception {
1712         Uri background7Uri = Uri.parse(
1713                 "content://com.android.test.notificationprovider.provider/background7.png");
1714 
1715         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
1716         mListener = TestNotificationListener.getInstance();
1717         assertNotNull(mListener);
1718 
1719         try {
1720             // Post #7
1721             performNotificationProviderAction("send-7");
1722             mNotificationHelper.findPostedNotification(null, 7, SEARCH_TYPE.POSTED);
1723 
1724             assertEquals(background7Uri, getNotificationBackgroundImageUri(7));
1725             assertAccessible(background7Uri);
1726 
1727             // Remove the listener to ensure permissions get revoked
1728             mNotificationHelper.disableListener(STUB_PACKAGE_NAME);
1729             Thread.sleep(500); // wait for listener to be disabled
1730 
1731             assertInaccessible(background7Uri);
1732 
1733         } finally {
1734             // Clean Up -- Cancel #7
1735             performNotificationProviderAction("cancel-7");
1736             Thread.sleep(500);
1737         }
1738     }
1739 
1740     private class NotificationListenerConnection implements ServiceConnection {
1741         private final Semaphore mSemaphore = new Semaphore(0);
1742 
1743         @Override
onServiceConnected(ComponentName className, IBinder service)1744         public void onServiceConnected(ComponentName className, IBinder service) {
1745             if (URI_ACCESS_SERVICE.equals(className)) {
1746                 mNotificationUriAccessService = INotificationUriAccessService.Stub.asInterface(
1747                         service);
1748             }
1749             if (NLS_CONTROL_SERVICE.equals(className)) {
1750                 mNLSControlService = INLSControlService.Stub.asInterface(service);
1751             }
1752             mSemaphore.release();
1753         }
1754 
1755         @Override
onServiceDisconnected(ComponentName className)1756         public void onServiceDisconnected(ComponentName className) {
1757             if (URI_ACCESS_SERVICE.equals(className)) {
1758                 mNotificationUriAccessService = null;
1759             }
1760             if (NLS_CONTROL_SERVICE.equals(className)) {
1761                 mNLSControlService = null;
1762             }
1763         }
1764 
waitForService()1765         public void waitForService() {
1766             try {
1767                 if (mSemaphore.tryAcquire(5, TimeUnit.SECONDS)) {
1768                     return;
1769                 }
1770             } catch (InterruptedException e) {
1771             }
1772             fail("failed to connec to service");
1773         }
1774     }
1775 
testNotificationUriPermissionsRevokedOnlyFromRemovedListeners()1776     public void testNotificationUriPermissionsRevokedOnlyFromRemovedListeners() throws Exception {
1777         Uri background7Uri = Uri.parse(
1778                 "content://com.android.test.notificationprovider.provider/background7.png");
1779 
1780         // Connect to a service in the NotificationListener app which allows us to validate URI
1781         // permissions granted to a second app, so that we show that permissions aren't being
1782         // revoked too broadly.
1783         final Intent intent = new Intent();
1784         intent.setComponent(URI_ACCESS_SERVICE);
1785         NotificationListenerConnection connection = new NotificationListenerConnection();
1786         mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
1787         connection.waitForService();
1788 
1789         // Before starting the test, make sure the service works, that there is no listener, and
1790         // that the URI starts inaccessible to that process.
1791         mNotificationUriAccessService.ensureNotificationListenerServiceConnected(false);
1792         assertFalse(mNotificationUriAccessService.isFileUriAccessible(background7Uri));
1793 
1794         // Give the NotificationListener app access to notifications, and validate that.
1795         toggleExternalListenerAccess(new ComponentName("com.android.test.notificationlistener",
1796                 "com.android.test.notificationlistener.TestNotificationListener"), true);
1797         Thread.sleep(500);
1798         mNotificationUriAccessService.ensureNotificationListenerServiceConnected(true);
1799         assertFalse(mNotificationUriAccessService.isFileUriAccessible(background7Uri));
1800 
1801         // Give the test app access to notifications, and get that listener
1802         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
1803         assertNotNull(mListener);
1804 
1805         try {
1806             try {
1807                 // Post #7
1808                 performNotificationProviderAction("send-7");
1809 
1810                 // Check that both the test app (this code) and the external app have URI access.
1811                 assertEquals(background7Uri, getNotificationBackgroundImageUri(7));
1812                 assertAccessible(background7Uri);
1813                 assertTrue(mNotificationUriAccessService.isFileUriAccessible(background7Uri));
1814 
1815                 // Remove the listener to ensure permissions get revoked
1816                 mNotificationHelper.disableListener(STUB_PACKAGE_NAME);
1817                 Thread.sleep(500); // wait for listener to be disabled
1818 
1819                 // Ensure that revoking listener access to this one app does not effect the other.
1820                 assertInaccessible(background7Uri);
1821                 assertTrue(mNotificationUriAccessService.isFileUriAccessible(background7Uri));
1822 
1823             } finally {
1824                 // Clean Up -- Cancel #7
1825                 performNotificationProviderAction("cancel-7");
1826                 Thread.sleep(500);
1827             }
1828 
1829             // Finally, cancelling the permission must still revoke those other permissions.
1830             assertFalse(mNotificationUriAccessService.isFileUriAccessible(background7Uri));
1831 
1832         } finally {
1833             // Clean Up -- Make sure the external listener is has access revoked
1834             toggleExternalListenerAccess(new ComponentName("com.android.test.notificationlistener",
1835                     "com.android.test.notificationlistener.TestNotificationListener"), false);
1836         }
1837     }
1838 
testNotificationListenerRequestUnbind()1839     public void testNotificationListenerRequestUnbind() throws Exception {
1840         final Intent intent = new Intent();
1841         intent.setComponent(NLS_CONTROL_SERVICE);
1842         NotificationListenerConnection connection = new NotificationListenerConnection();
1843         mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
1844         connection.waitForService();
1845 
1846         // Give the NotificationListener app access to notifications, and validate that.
1847         toggleExternalListenerAccess(NO_AUTOBIND_NLS, true);
1848         Thread.sleep(500);
1849 
1850         // Give the test app access to notifications, and get that listener
1851         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
1852         assertNotNull(mListener);
1853 
1854         try {
1855             // Check that the listener service is not auto-bound (manifest meta-data)
1856             assertFalse(mNLSControlService.isNotificationListenerConnected());
1857 
1858             // Request bind NLS
1859             mNLSControlService.requestRebindComponent();
1860             Thread.sleep(500);
1861             assertTrue(mNLSControlService.isNotificationListenerConnected());
1862 
1863             // Request unbind NLS
1864             mNLSControlService.requestUnbindComponent();
1865             Thread.sleep(500);
1866             assertFalse(mNLSControlService.isNotificationListenerConnected());
1867         } finally {
1868             // Clean Up -- Make sure the external listener is has access revoked
1869             toggleExternalListenerAccess(NO_AUTOBIND_NLS, false);
1870         }
1871     }
1872 
testNotificationListenerAutobindMetaData()1873     public void testNotificationListenerAutobindMetaData() throws Exception {
1874         final ServiceInfo info = mPackageManager.getServiceInfo(NO_AUTOBIND_NLS,
1875                 PackageManager.GET_META_DATA
1876                         | PackageManager.MATCH_DIRECT_BOOT_AWARE
1877                         | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
1878 
1879         assertNotNull(info);
1880         assertTrue(info.metaData.containsKey(META_DATA_DEFAULT_AUTOBIND));
1881         assertFalse(info.metaData.getBoolean(META_DATA_DEFAULT_AUTOBIND, true));
1882     }
1883 
assertAccessible(Uri uri)1884     private void assertAccessible(Uri uri)
1885             throws IOException {
1886         ContentResolver contentResolver = mContext.getContentResolver();
1887         for (int tries = 3; tries-- > 0; ) {
1888             try (AssetFileDescriptor fd = contentResolver.openAssetFile(uri, "r", null)) {
1889                 if (fd != null) {
1890                     return;
1891                 }
1892             } catch (SecurityException e) {
1893             }
1894             try {
1895                 Thread.sleep(100);
1896             } catch (InterruptedException ex) {
1897             }
1898         }
1899         fail("Uri " + uri + "is not accessible");
1900     }
1901 
assertInaccessible(Uri uri)1902     private void assertInaccessible(Uri uri)
1903             throws IOException {
1904         ContentResolver contentResolver = mContext.getContentResolver();
1905         for (int tries = 3; tries-- > 0; ) {
1906             try (AssetFileDescriptor fd = contentResolver.openAssetFile(uri, "r", null)) {
1907             } catch (SecurityException e) {
1908                 return;
1909             }
1910             try {
1911                 Thread.sleep(100);
1912             } catch (InterruptedException ex) {
1913             }
1914         }
1915         fail("Uri " + uri + "is still accessible");
1916     }
1917 
1918     @NonNull
getNotificationBackgroundImageUri(int notificationId)1919     private Uri getNotificationBackgroundImageUri(int notificationId) {
1920         StatusBarNotification sbn = mNotificationHelper.findPostedNotification(null,
1921                 notificationId, SEARCH_TYPE.LISTENER);
1922         assertNotNull(sbn);
1923         String imageUriString = sbn.getNotification().extras
1924                 .getString(Notification.EXTRA_BACKGROUND_IMAGE_URI);
1925         assertNotNull(imageUriString);
1926         return Uri.parse(imageUriString);
1927     }
1928 
uncheck(ThrowingSupplier<T> supplier)1929     private <T> T uncheck(ThrowingSupplier<T> supplier) {
1930         try {
1931             return supplier.get();
1932         } catch (Exception e) {
1933             throw new CompletionException(e);
1934         }
1935     }
1936 
testNotificationListener_setNotificationsShown()1937     public void testNotificationListener_setNotificationsShown() throws Exception {
1938         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
1939         assertNotNull(mListener);
1940         CountDownLatch postedLatch = mListener.setPostedCountDown(2);
1941         final int notificationId1 = 1003;
1942         final int notificationId2 = 1004;
1943 
1944         sendNotification(notificationId1, R.drawable.black);
1945         sendNotification(notificationId2, R.drawable.black);
1946         // wait for notification listener to receive notification
1947         postedLatch.await(500, TimeUnit.MILLISECONDS);
1948 
1949         StatusBarNotification sbn1 = mNotificationHelper.findPostedNotification(
1950                 null, notificationId1, SEARCH_TYPE.LISTENER);
1951         StatusBarNotification sbn2 = mNotificationHelper.findPostedNotification(
1952                 null, notificationId2, SEARCH_TYPE.LISTENER);
1953         mListener.setNotificationsShown(new String[]{sbn1.getKey()});
1954 
1955         mNotificationHelper.disableListener(STUB_PACKAGE_NAME);
1956         Thread.sleep(500); // wait for listener to be disallowed
1957         try {
1958             mListener.setNotificationsShown(new String[]{sbn2.getKey()});
1959             fail("Should not be able to set shown if listener access isn't granted");
1960         } catch (SecurityException e) {
1961             // expected
1962         }
1963     }
1964 
testNotificationListener_getNotificationChannels()1965     public void testNotificationListener_getNotificationChannels() throws Exception {
1966         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
1967         assertNotNull(mListener);
1968 
1969         try {
1970             mListener.getNotificationChannels(mContext.getPackageName(), UserHandle.CURRENT);
1971             fail("Shouldn't be able get channels without CompanionDeviceManager#getAssociations()");
1972         } catch (SecurityException e) {
1973             // expected
1974         }
1975     }
1976 
testNotificationListener_getNotificationChannelGroups()1977     public void testNotificationListener_getNotificationChannelGroups() throws Exception {
1978         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
1979         assertNotNull(mListener);
1980         try {
1981             mListener.getNotificationChannelGroups(mContext.getPackageName(), UserHandle.CURRENT);
1982             fail("Should not be able get groups without CompanionDeviceManager#getAssociations()");
1983         } catch (SecurityException e) {
1984             // expected
1985         }
1986     }
1987 
testNotificationListener_updateNotificationChannel()1988     public void testNotificationListener_updateNotificationChannel() throws Exception {
1989         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
1990         assertNotNull(mListener);
1991 
1992         NotificationChannel channel = new NotificationChannel(
1993                 NOTIFICATION_CHANNEL_ID, "name", IMPORTANCE_DEFAULT);
1994         try {
1995             mListener.updateNotificationChannel(mContext.getPackageName(), UserHandle.CURRENT,
1996                     channel);
1997             fail("Shouldn't be able to update channel without "
1998                     + "CompanionDeviceManager#getAssociations()");
1999         } catch (SecurityException e) {
2000             // expected
2001         }
2002     }
2003 
testNotificationListener_getActiveNotifications()2004     public void testNotificationListener_getActiveNotifications() throws Exception {
2005         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
2006         assertNotNull(mListener);
2007         CountDownLatch postedLatch = mListener.setPostedCountDown(2);
2008         final int notificationId1 = 1001;
2009         final int notificationId2 = 1002;
2010 
2011         sendNotification(notificationId1, R.drawable.black);
2012         sendNotification(notificationId2, R.drawable.black);
2013         // wait for notification listener to receive notification
2014         postedLatch.await(500, TimeUnit.MILLISECONDS);
2015 
2016         StatusBarNotification sbn1 = mNotificationHelper.findPostedNotification(
2017                 null, notificationId1, SEARCH_TYPE.LISTENER);
2018         StatusBarNotification sbn2 = mNotificationHelper.findPostedNotification(
2019                 null, notificationId2, SEARCH_TYPE.LISTENER);
2020         StatusBarNotification[] notifs =
2021                 mListener.getActiveNotifications(new String[]{sbn2.getKey(), sbn1.getKey()});
2022         assertEquals(sbn2.getKey(), notifs[0].getKey());
2023         assertEquals(sbn2.getId(), notifs[0].getId());
2024         assertEquals(sbn2.getPackageName(), notifs[0].getPackageName());
2025 
2026         assertEquals(sbn1.getKey(), notifs[1].getKey());
2027         assertEquals(sbn1.getId(), notifs[1].getId());
2028         assertEquals(sbn1.getPackageName(), notifs[1].getPackageName());
2029     }
2030 
2031 
testNotificationListener_getCurrentRanking()2032     public void testNotificationListener_getCurrentRanking() throws Exception {
2033         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
2034         assertNotNull(mListener);
2035         CountDownLatch rankingUpdateLatch = mListener.setRankingUpdateCountDown(1);
2036 
2037         sendNotification(1, R.drawable.black);
2038         rankingUpdateLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
2039         mNotificationHelper.findPostedNotification(null, 1, SEARCH_TYPE.POSTED);
2040 
2041         assertEquals(mListener.mRankingMap, mListener.getCurrentRanking());
2042     }
2043 
testNotificationListener_cancelNotifications()2044     public void testNotificationListener_cancelNotifications() throws Exception {
2045         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
2046         assertNotNull(mListener);
2047         final int notificationId = 1006;
2048 
2049         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
2050         sendNotification(notificationId, R.drawable.black);
2051         // wait for notification listener to receive notification
2052         postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
2053 
2054         StatusBarNotification sbn = mNotificationHelper.findPostedNotification(null, notificationId,
2055                 SEARCH_TYPE.LISTENER);
2056 
2057         mListener.cancelNotification(sbn.getPackageName(), sbn.getTag(), sbn.getId());
2058         // Beginning with Lollipop, this cancelNotification signature no longer cancels the
2059         // notification.
2060         if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
2061             assertNotNull(mNotificationHelper.findPostedNotification(null, notificationId,
2062                     SEARCH_TYPE.LISTENER));
2063         } else {
2064             // Tested in LegacyNotificationManager20Test
2065             assertTrue(mNotificationHelper.isNotificationGone(
2066                     notificationId, SEARCH_TYPE.LISTENER));
2067         }
2068 
2069         mListener.cancelNotifications(new String[]{sbn.getKey()});
2070         if (getCancellationReason(sbn.getKey())
2071                 != NotificationListenerService.REASON_LISTENER_CANCEL) {
2072             fail("Failed to cancel notification id=" + notificationId);
2073         }
2074     }
2075 
testNotificationAssistant_cancelNotifications()2076     public void testNotificationAssistant_cancelNotifications() throws Exception {
2077         mAssistant = mNotificationHelper.enableAssistant(STUB_PACKAGE_NAME);
2078         assertNotNull(mAssistant);
2079         final int notificationId = 1006;
2080 
2081         sendNotification(notificationId, R.drawable.black);
2082         Thread.sleep(500); // wait for notification listener to receive notification
2083 
2084         StatusBarNotification sbn = mNotificationHelper.findPostedNotification(null, notificationId,
2085                 SEARCH_TYPE.APP);
2086 
2087         mAssistant.cancelNotifications(new String[]{sbn.getKey()});
2088         int gotReason = getAssistantCancellationReason(sbn.getKey());
2089         if (gotReason != NotificationListenerService.REASON_ASSISTANT_CANCEL) {
2090             fail("Failed cancellation from assistant, notification id=" + notificationId
2091                     + "; got reason=" + gotReason);
2092         }
2093     }
2094 
testNotificationManagerPolicy_priorityCategoriesToString()2095     public void testNotificationManagerPolicy_priorityCategoriesToString() {
2096         String zeroString = NotificationManager.Policy.priorityCategoriesToString(0);
2097         assertEquals("priorityCategories of 0 produces empty string", "", zeroString);
2098 
2099         String oneString = NotificationManager.Policy.priorityCategoriesToString(1);
2100         assertNotNull("priorityCategories of 1 returns a string", oneString);
2101         boolean lengthGreaterThanZero = oneString.length() > 0;
2102         assertTrue("priorityCategories of 1 returns a string with length greater than 0",
2103                 lengthGreaterThanZero);
2104 
2105         String badNumberString = NotificationManager.Policy.priorityCategoriesToString(1234567);
2106         assertNotNull("priorityCategories with a non-relevant int returns a string",
2107                 badNumberString);
2108     }
2109 
testNotificationManagerPolicy_prioritySendersToString()2110     public void testNotificationManagerPolicy_prioritySendersToString() {
2111         String zeroString = NotificationManager.Policy.prioritySendersToString(0);
2112         assertNotNull("prioritySenders of 1 returns a string", zeroString);
2113         boolean lengthGreaterThanZero = zeroString.length() > 0;
2114         assertTrue("prioritySenders of 1 returns a string with length greater than 0",
2115                 lengthGreaterThanZero);
2116 
2117         String badNumberString = NotificationManager.Policy.prioritySendersToString(1234567);
2118         assertNotNull("prioritySenders with a non-relevant int returns a string", badNumberString);
2119     }
2120 
testNotificationManagerPolicy_suppressedEffectsToString()2121     public void testNotificationManagerPolicy_suppressedEffectsToString() {
2122         String zeroString = NotificationManager.Policy.suppressedEffectsToString(0);
2123         assertEquals("suppressedEffects of 0 produces empty string", "", zeroString);
2124 
2125         String oneString = NotificationManager.Policy.suppressedEffectsToString(1);
2126         assertNotNull("suppressedEffects of 1 returns a string", oneString);
2127         boolean lengthGreaterThanZero = oneString.length() > 0;
2128         assertTrue("suppressedEffects of 1 returns a string with length greater than 0",
2129                 lengthGreaterThanZero);
2130 
2131         String badNumberString = NotificationManager.Policy.suppressedEffectsToString(1234567);
2132         assertNotNull("suppressedEffects with a non-relevant int returns a string",
2133                 badNumberString);
2134     }
2135 
testOriginalChannelImportance()2136     public void testOriginalChannelImportance() {
2137         NotificationChannel channel = new NotificationChannel(mId, "my channel", IMPORTANCE_HIGH);
2138 
2139         mNotificationManager.createNotificationChannel(channel);
2140 
2141         NotificationChannel actual = mNotificationManager.getNotificationChannel(channel.getId());
2142         assertEquals(IMPORTANCE_HIGH, actual.getImportance());
2143         assertEquals(IMPORTANCE_HIGH, actual.getOriginalImportance());
2144 
2145         // Apps are allowed to downgrade channel importance if the user has not changed any
2146         // fields on this channel yet.
2147         channel.setImportance(IMPORTANCE_DEFAULT);
2148         mNotificationManager.createNotificationChannel(channel);
2149 
2150         actual = mNotificationManager.getNotificationChannel(channel.getId());
2151         assertEquals(IMPORTANCE_DEFAULT, actual.getImportance());
2152         assertEquals(IMPORTANCE_HIGH, actual.getOriginalImportance());
2153     }
2154 
testCreateConversationChannel()2155     public void testCreateConversationChannel() {
2156         final NotificationChannel channel =
2157                 new NotificationChannel(mId, "Messages", IMPORTANCE_DEFAULT);
2158 
2159         String conversationId = "person a";
2160 
2161         final NotificationChannel conversationChannel =
2162                 new NotificationChannel(mId + "child",
2163                         "Messages from " + conversationId, IMPORTANCE_DEFAULT);
2164         conversationChannel.setConversationId(channel.getId(), conversationId);
2165 
2166         mNotificationManager.createNotificationChannel(channel);
2167         mNotificationManager.createNotificationChannel(conversationChannel);
2168 
2169         compareChannels(conversationChannel,
2170                 mNotificationManager.getNotificationChannel(channel.getId(), conversationId));
2171     }
2172 
testConversationRankingFields()2173     public void testConversationRankingFields() throws Exception {
2174         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
2175         assertNotNull(mListener);
2176         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
2177 
2178         createDynamicShortcut();
2179         mNotificationManager.notify(177, getConversationNotification().build());
2180 
2181         // wait for notification listener to receive notification
2182         postedLatch.await(500, TimeUnit.MILLISECONDS);
2183         assertNotNull(mNotificationHelper.findPostedNotification(null, 177, SEARCH_TYPE.LISTENER));
2184         assertEquals(1, mListener.mPosted.size());
2185 
2186         NotificationListenerService.RankingMap rankingMap = mListener.mRankingMap;
2187         NotificationListenerService.Ranking outRanking = new NotificationListenerService.Ranking();
2188         for (String key : rankingMap.getOrderedKeys()) {
2189             if (key.contains(mListener.getPackageName())) {
2190                 rankingMap.getRanking(key, outRanking);
2191                 assertTrue(outRanking.isConversation());
2192                 assertEquals(SHARE_SHORTCUT_ID, outRanking.getConversationShortcutInfo().getId());
2193             }
2194         }
2195     }
2196 
testDemoteConversationChannel()2197     public void testDemoteConversationChannel() {
2198         final NotificationChannel channel =
2199                 new NotificationChannel(mId, "Messages", IMPORTANCE_DEFAULT);
2200 
2201         String conversationId = "person a";
2202 
2203         final NotificationChannel conversationChannel =
2204                 new NotificationChannel(mId + "child",
2205                         "Messages from " + conversationId, IMPORTANCE_DEFAULT);
2206         conversationChannel.setConversationId(channel.getId(), conversationId);
2207 
2208         mNotificationManager.createNotificationChannel(channel);
2209         mNotificationManager.createNotificationChannel(conversationChannel);
2210 
2211         conversationChannel.setDemoted(true);
2212 
2213         SystemUtil.runWithShellPermissionIdentity(() ->
2214                 mNotificationManager.updateNotificationChannel(
2215                         mContext.getPackageName(), android.os.Process.myUid(), channel));
2216 
2217         assertEquals(false, mNotificationManager.getNotificationChannel(
2218                 channel.getId(), conversationId).isDemoted());
2219     }
2220 
testDeleteConversationChannels()2221     public void testDeleteConversationChannels() throws Exception {
2222         setUpNotifListener();
2223         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
2224 
2225         createDynamicShortcut();
2226 
2227         final NotificationChannel channel =
2228                 new NotificationChannel(mId, "Messages", IMPORTANCE_DEFAULT);
2229 
2230         final NotificationChannel conversationChannel =
2231                 new NotificationChannel(mId + "child",
2232                         "Messages from " + SHARE_SHORTCUT_ID, IMPORTANCE_DEFAULT);
2233         conversationChannel.setConversationId(channel.getId(), SHARE_SHORTCUT_ID);
2234 
2235         mNotificationManager.createNotificationChannel(channel);
2236         mNotificationManager.createNotificationChannel(conversationChannel);
2237 
2238         mNotificationManager.notify(177, getConversationNotification().build());
2239 
2240         // wait for notification listener to receive notification
2241         postedLatch.await(500, TimeUnit.MILLISECONDS);
2242         assertNotNull(mNotificationHelper.findPostedNotification(null, 177, SEARCH_TYPE.LISTENER));
2243         assertEquals(1, mListener.mPosted.size());
2244 
2245         deleteShortcuts();
2246 
2247         Thread.sleep(300); // wait for deletion to propagate
2248 
2249         assertFalse(mNotificationManager.getNotificationChannel(channel.getId(),
2250                 conversationChannel.getConversationId()).isConversation());
2251 
2252     }
2253 
2254     /**
2255      * This method verifies that an app can't bypass background restrictions by retrieving their own
2256      * notification and triggering it.
2257      */
2258     @AsbSecurityTest(cveBugId = 185388103)
testActivityStartFromRetrievedNotification_isBlocked()2259     public void testActivityStartFromRetrievedNotification_isBlocked() throws Exception {
2260         deactivateGracePeriod();
2261         EventCallback callback = new EventCallback();
2262         int notificationId = 6007;
2263 
2264         // Post notification and fire its pending intent
2265         sendTrampolineMessage(TRAMPOLINE_SERVICE_API_30, MESSAGE_SERVICE_NOTIFICATION,
2266                 notificationId, callback);
2267         PollingCheck.waitFor(TIMEOUT_MS, () -> uncheck(() -> {
2268             sendTrampolineMessage(TRAMPOLINE_SERVICE, MESSAGE_CLICK_NOTIFICATION, notificationId,
2269                     callback);
2270             // timeoutMs = 1ms below because surrounding waitFor already handles retry & timeout.
2271             return callback.waitFor(EventCallback.NOTIFICATION_CLICKED, /* timeoutMs */ 1);
2272         }));
2273 
2274         assertFalse("Activity start should have been blocked",
2275                 callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
2276     }
2277 
testActivityStartOnBroadcastTrampoline_isBlocked()2278     public void testActivityStartOnBroadcastTrampoline_isBlocked() throws Exception {
2279         deactivateGracePeriod();
2280         setUpNotifListener();
2281         mListener.addTestPackage(TRAMPOLINE_APP);
2282         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
2283         EventCallback callback = new EventCallback();
2284         int notificationId = 6001;
2285 
2286         // Post notification and fire its pending intent
2287         sendTrampolineMessage(TRAMPOLINE_SERVICE, MESSAGE_BROADCAST_NOTIFICATION, notificationId,
2288                 callback);
2289         postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
2290         StatusBarNotification statusBarNotification = mNotificationHelper.findPostedNotification(
2291                 null, notificationId, SEARCH_TYPE.LISTENER);
2292         assertNotNull("Notification not posted on time", statusBarNotification);
2293         statusBarNotification.getNotification().contentIntent.send();
2294 
2295         assertTrue("Broadcast not received on time",
2296                 callback.waitFor(EventCallback.BROADCAST_RECEIVED, TIMEOUT_LONG_MS));
2297         assertFalse("Activity start should have been blocked",
2298                 callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
2299     }
2300 
testActivityStartOnServiceTrampoline_isBlocked()2301     public void testActivityStartOnServiceTrampoline_isBlocked() throws Exception {
2302         deactivateGracePeriod();
2303         setUpNotifListener();
2304         mListener.addTestPackage(TRAMPOLINE_APP);
2305         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
2306         EventCallback callback = new EventCallback();
2307         int notificationId = 6002;
2308 
2309         // Post notification and fire its pending intent
2310         sendTrampolineMessage(TRAMPOLINE_SERVICE, MESSAGE_SERVICE_NOTIFICATION, notificationId,
2311                 callback);
2312         postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
2313         StatusBarNotification statusBarNotification = mNotificationHelper.findPostedNotification(
2314                 null, notificationId, SEARCH_TYPE.LISTENER);
2315         assertNotNull("Notification not posted on time", statusBarNotification);
2316         statusBarNotification.getNotification().contentIntent.send();
2317 
2318         assertTrue("Service not started on time",
2319                 callback.waitFor(EventCallback.SERVICE_STARTED, TIMEOUT_MS));
2320         assertFalse("Activity start should have been blocked",
2321                 callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
2322     }
2323 
testActivityStartOnBroadcastTrampoline_whenApi30_isAllowed()2324     public void testActivityStartOnBroadcastTrampoline_whenApi30_isAllowed() throws Exception {
2325         deactivateGracePeriod();
2326         setUpNotifListener();
2327         mListener.addTestPackage(TRAMPOLINE_APP_API_30);
2328         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
2329         EventCallback callback = new EventCallback();
2330         int notificationId = 6003;
2331 
2332         // Post notification and fire its pending intent
2333         sendTrampolineMessage(TRAMPOLINE_SERVICE_API_30, MESSAGE_BROADCAST_NOTIFICATION,
2334                 notificationId, callback);
2335         postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
2336         StatusBarNotification statusBarNotification = mNotificationHelper.findPostedNotification(
2337                 null, notificationId, SEARCH_TYPE.LISTENER);
2338         assertNotNull("Notification not posted on time", statusBarNotification);
2339         statusBarNotification.getNotification().contentIntent.send();
2340 
2341         assertTrue("Broadcast not received on time",
2342                 callback.waitFor(EventCallback.BROADCAST_RECEIVED, TIMEOUT_LONG_MS));
2343         assertTrue("Activity not started",
2344                 callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
2345     }
2346 
testActivityStartOnServiceTrampoline_whenApi30_isAllowed()2347     public void testActivityStartOnServiceTrampoline_whenApi30_isAllowed() throws Exception {
2348         deactivateGracePeriod();
2349         setUpNotifListener();
2350         mListener.addTestPackage(TRAMPOLINE_APP_API_30);
2351         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
2352         EventCallback callback = new EventCallback();
2353         int notificationId = 6004;
2354 
2355         // Post notification and fire its pending intent
2356         sendTrampolineMessage(TRAMPOLINE_SERVICE_API_30, MESSAGE_SERVICE_NOTIFICATION,
2357                 notificationId, callback);
2358         postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
2359         StatusBarNotification statusBarNotification = mNotificationHelper.findPostedNotification(
2360                 null, notificationId, SEARCH_TYPE.LISTENER);
2361         assertNotNull("Notification not posted on time", statusBarNotification);
2362         statusBarNotification.getNotification().contentIntent.send();
2363 
2364         assertTrue("Service not started on time",
2365                 callback.waitFor(EventCallback.SERVICE_STARTED, TIMEOUT_MS));
2366         assertTrue("Activity not started",
2367                 callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
2368     }
2369 
testActivityStartOnBroadcastTrampoline_whenDefaultBrowser_isBlocked()2370     public void testActivityStartOnBroadcastTrampoline_whenDefaultBrowser_isBlocked()
2371             throws Exception {
2372         deactivateGracePeriod();
2373         setDefaultBrowser(TRAMPOLINE_APP);
2374         setUpNotifListener();
2375         mListener.addTestPackage(TRAMPOLINE_APP);
2376         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
2377         EventCallback callback = new EventCallback();
2378         int notificationId = 6005;
2379 
2380         // Post notification and fire its pending intent
2381         sendTrampolineMessage(TRAMPOLINE_SERVICE, MESSAGE_BROADCAST_NOTIFICATION, notificationId,
2382                 callback);
2383         postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
2384         StatusBarNotification statusBarNotification = mNotificationHelper.findPostedNotification(
2385                 null, notificationId, SEARCH_TYPE.LISTENER);
2386         assertNotNull("Notification not posted on time", statusBarNotification);
2387         statusBarNotification.getNotification().contentIntent.send();
2388 
2389         assertTrue("Broadcast not received on time",
2390                 callback.waitFor(EventCallback.BROADCAST_RECEIVED, TIMEOUT_LONG_MS));
2391         assertFalse("Activity started",
2392                 callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
2393     }
2394 
testActivityStartOnBroadcastTrampoline_whenDefaultBrowserApi32_isAllowed()2395     public void testActivityStartOnBroadcastTrampoline_whenDefaultBrowserApi32_isAllowed()
2396             throws Exception {
2397         deactivateGracePeriod();
2398         setDefaultBrowser(TRAMPOLINE_APP_API_32);
2399         setUpNotifListener();
2400         mListener.addTestPackage(TRAMPOLINE_APP_API_32);
2401         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
2402         EventCallback callback = new EventCallback();
2403         int notificationId = 6005;
2404 
2405         // Post notification and fire its pending intent
2406         sendTrampolineMessage(TRAMPOLINE_SERVICE_API_32, MESSAGE_BROADCAST_NOTIFICATION,
2407                 notificationId, callback);
2408         postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
2409         StatusBarNotification statusBarNotification = mNotificationHelper.findPostedNotification(
2410                 null, notificationId, SEARCH_TYPE.LISTENER);
2411         assertNotNull("Notification not posted on time", statusBarNotification);
2412         statusBarNotification.getNotification().contentIntent.send();
2413 
2414         assertTrue("Broadcast not received on time",
2415                 callback.waitFor(EventCallback.BROADCAST_RECEIVED, TIMEOUT_LONG_MS));
2416         assertTrue("Activity not started",
2417                 callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
2418     }
2419 
testActivityStartOnServiceTrampoline_whenDefaultBrowser_isBlocked()2420     public void testActivityStartOnServiceTrampoline_whenDefaultBrowser_isBlocked()
2421             throws Exception {
2422         deactivateGracePeriod();
2423         setDefaultBrowser(TRAMPOLINE_APP);
2424         setUpNotifListener();
2425         mListener.addTestPackage(TRAMPOLINE_APP);
2426         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
2427         EventCallback callback = new EventCallback();
2428         int notificationId = 6006;
2429 
2430         // Post notification and fire its pending intent
2431         sendTrampolineMessage(TRAMPOLINE_SERVICE, MESSAGE_SERVICE_NOTIFICATION, notificationId,
2432                 callback);
2433         postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
2434         StatusBarNotification statusBarNotification = mNotificationHelper.findPostedNotification(
2435                 null, notificationId, SEARCH_TYPE.LISTENER);
2436         assertNotNull("Notification not posted on time", statusBarNotification);
2437         statusBarNotification.getNotification().contentIntent.send();
2438 
2439         assertTrue("Service not started on time",
2440                 callback.waitFor(EventCallback.SERVICE_STARTED, TIMEOUT_MS));
2441         assertFalse("Activity started",
2442                 callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
2443     }
2444 
testActivityStartOnServiceTrampoline_whenDefaultBrowserApi32_isAllowed()2445     public void testActivityStartOnServiceTrampoline_whenDefaultBrowserApi32_isAllowed()
2446             throws Exception {
2447         deactivateGracePeriod();
2448         setDefaultBrowser(TRAMPOLINE_APP_API_32);
2449         setUpNotifListener();
2450         mListener.addTestPackage(TRAMPOLINE_APP_API_32);
2451         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
2452         EventCallback callback = new EventCallback();
2453         int notificationId = 6006;
2454 
2455         // Post notification and fire its pending intent
2456         sendTrampolineMessage(TRAMPOLINE_SERVICE_API_32, MESSAGE_SERVICE_NOTIFICATION,
2457                 notificationId, callback);
2458         postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
2459         StatusBarNotification statusBarNotification = mNotificationHelper.findPostedNotification(
2460                 null, notificationId, SEARCH_TYPE.LISTENER);
2461         assertNotNull("Notification not posted on time", statusBarNotification);
2462         statusBarNotification.getNotification().contentIntent.send();
2463 
2464         assertTrue("Service not started on time",
2465                 callback.waitFor(EventCallback.SERVICE_STARTED, TIMEOUT_MS));
2466         assertTrue("Activity not started",
2467                 callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
2468     }
2469 
testGrantRevokeNotificationManagerApis_works()2470     public void testGrantRevokeNotificationManagerApis_works() {
2471         SystemUtil.runWithShellPermissionIdentity(() -> {
2472             ComponentName componentName =
2473                     new ComponentName(STUB_PACKAGE_NAME, TestNotificationListener.class.getName());
2474             mNotificationManager.setNotificationListenerAccessGranted(
2475                     componentName, true, true);
2476 
2477             assertThat(
2478                     mNotificationManager.getEnabledNotificationListeners(),
2479                     hasItem(componentName));
2480 
2481             mNotificationManager.setNotificationListenerAccessGranted(
2482                     componentName, false, false);
2483 
2484             assertThat(
2485                     "Non-user-set changes should not override user-set",
2486                     mNotificationManager.getEnabledNotificationListeners(),
2487                     hasItem(componentName));
2488         });
2489     }
2490 
testGrantRevokeNotificationManagerApis_exclusiveToPermissionController()2491     public void testGrantRevokeNotificationManagerApis_exclusiveToPermissionController() {
2492         List<PackageInfo> allPackages = mPackageManager.getInstalledPackages(
2493                 PackageManager.MATCH_DISABLED_COMPONENTS
2494                         | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS);
2495         List<String> allowedPackages = Arrays.asList(
2496                 mPackageManager.getPermissionControllerPackageName(),
2497                 "com.android.shell");
2498         for (PackageInfo pkg : allPackages) {
2499             if (!pkg.applicationInfo.isSystemApp()
2500                     && mPackageManager.checkPermission(
2501                     Manifest.permission.MANAGE_NOTIFICATION_LISTENERS, pkg.packageName)
2502                     == PackageManager.PERMISSION_GRANTED
2503                     && !allowedPackages.contains(pkg.packageName)) {
2504                 fail(pkg.packageName + " can't hold "
2505                         + Manifest.permission.MANAGE_NOTIFICATION_LISTENERS);
2506             }
2507         }
2508     }
2509 
testChannelDeletion_cancelReason()2510     public void testChannelDeletion_cancelReason() throws Exception {
2511         setUpNotifListener();
2512         CountDownLatch notificationPostedLatch = mListener.setPostedCountDown(1);
2513 
2514         sendNotification(566, R.drawable.black);
2515         // wait for notification listener to receive notification
2516         notificationPostedLatch.await(500, TimeUnit.MILLISECONDS);
2517         assertEquals(1, mListener.mPosted.size());
2518         String key = mListener.mPosted.get(0).getKey();
2519 
2520         mNotificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
2521 
2522         assertEquals(NotificationListenerService.REASON_CHANNEL_REMOVED,
2523                 getCancellationReason(key));
2524     }
2525 
testMediaStyleRemotePlayback_noPermission()2526     public void testMediaStyleRemotePlayback_noPermission() throws Exception {
2527         int id = 99;
2528         final String deviceName = "device name";
2529         final int deviceIcon = 123;
2530         final PendingIntent deviceIntent = getPendingIntent();
2531         final Notification notification =
2532                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
2533                         .setSmallIcon(R.drawable.black)
2534                         .setStyle(new Notification.MediaStyle()
2535                                 .setRemotePlaybackInfo(deviceName, deviceIcon, deviceIntent))
2536                         .build();
2537         mNotificationManager.notify(id, notification);
2538 
2539         StatusBarNotification sbn = mNotificationHelper.findPostedNotification(null, id,
2540                 SEARCH_TYPE.APP);
2541         assertNotNull(sbn);
2542 
2543         assertFalse(sbn.getNotification().extras
2544                 .containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE));
2545         assertFalse(sbn.getNotification().extras
2546                 .containsKey(Notification.EXTRA_MEDIA_REMOTE_ICON));
2547         assertFalse(sbn.getNotification().extras
2548                 .containsKey(Notification.EXTRA_MEDIA_REMOTE_INTENT));
2549     }
2550 
testMediaStyleRemotePlayback_hasPermission()2551     public void testMediaStyleRemotePlayback_hasPermission() throws Exception {
2552         int id = 99;
2553         final String deviceName = "device name";
2554         final int deviceIcon = 123;
2555         final PendingIntent deviceIntent = getPendingIntent();
2556         final Notification notification =
2557                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
2558                         .setSmallIcon(R.drawable.black)
2559                         .setStyle(new Notification.MediaStyle()
2560                                 .setRemotePlaybackInfo(deviceName, deviceIcon, deviceIntent))
2561                         .build();
2562 
2563         SystemUtil.runWithShellPermissionIdentity(() -> {
2564             mNotificationManager.notify(id, notification);
2565         }, android.Manifest.permission.MEDIA_CONTENT_CONTROL);
2566 
2567         StatusBarNotification sbn = mNotificationHelper.findPostedNotification(
2568                 null, id, SEARCH_TYPE.APP);
2569         assertNotNull(sbn);
2570         assertEquals(deviceName, sbn.getNotification().extras
2571                 .getString(Notification.EXTRA_MEDIA_REMOTE_DEVICE));
2572         assertEquals(deviceIcon, sbn.getNotification().extras
2573                 .getInt(Notification.EXTRA_MEDIA_REMOTE_ICON));
2574         assertEquals(deviceIntent, sbn.getNotification().extras
2575                 .getParcelable(Notification.EXTRA_MEDIA_REMOTE_INTENT));
2576     }
2577 
testCustomMediaStyleRemotePlayback_noPermission()2578     public void testCustomMediaStyleRemotePlayback_noPermission() throws Exception {
2579         int id = 99;
2580         final String deviceName = "device name";
2581         final int deviceIcon = 123;
2582         final PendingIntent deviceIntent = getPendingIntent();
2583         final Notification notification =
2584                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
2585                         .setSmallIcon(R.drawable.black)
2586                         .setStyle(new Notification.DecoratedMediaCustomViewStyle()
2587                                 .setRemotePlaybackInfo(deviceName, deviceIcon, deviceIntent))
2588                         .build();
2589         mNotificationManager.notify(id, notification);
2590 
2591         StatusBarNotification sbn = mNotificationHelper.findPostedNotification(
2592                 null, id, SEARCH_TYPE.APP);
2593         assertNotNull(sbn);
2594 
2595         assertFalse(sbn.getNotification().extras
2596                 .containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE));
2597         assertFalse(sbn.getNotification().extras
2598                 .containsKey(Notification.EXTRA_MEDIA_REMOTE_ICON));
2599         assertFalse(sbn.getNotification().extras
2600                 .containsKey(Notification.EXTRA_MEDIA_REMOTE_INTENT));
2601     }
2602 
testCustomMediaStyleRemotePlayback_hasPermission()2603     public void testCustomMediaStyleRemotePlayback_hasPermission() throws Exception {
2604         int id = 99;
2605         final String deviceName = "device name";
2606         final int deviceIcon = 123;
2607         final PendingIntent deviceIntent = getPendingIntent();
2608         final Notification notification =
2609                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
2610                         .setSmallIcon(R.drawable.black)
2611                         .setStyle(new Notification.DecoratedMediaCustomViewStyle()
2612                                 .setRemotePlaybackInfo(deviceName, deviceIcon, deviceIntent))
2613                         .build();
2614 
2615         SystemUtil.runWithShellPermissionIdentity(() -> {
2616             mNotificationManager.notify(id, notification);
2617         }, android.Manifest.permission.MEDIA_CONTENT_CONTROL);
2618 
2619         StatusBarNotification sbn = mNotificationHelper.findPostedNotification(
2620                 null, id, SEARCH_TYPE.APP);
2621         assertNotNull(sbn);
2622         assertEquals(deviceName, sbn.getNotification().extras
2623                 .getString(Notification.EXTRA_MEDIA_REMOTE_DEVICE));
2624         assertEquals(deviceIcon, sbn.getNotification().extras
2625                 .getInt(Notification.EXTRA_MEDIA_REMOTE_ICON));
2626         assertEquals(deviceIntent, sbn.getNotification().extras
2627                 .getParcelable(Notification.EXTRA_MEDIA_REMOTE_INTENT));
2628     }
2629 
testNoPermission()2630     public void testNoPermission() throws Exception {
2631         int id = 7;
2632         SystemUtil.runWithShellPermissionIdentity(
2633                 () -> mContext.getSystemService(PermissionManager.class)
2634                         .revokePostNotificationPermissionWithoutKillForTest(
2635                                 mContext.getPackageName(),
2636                                 android.os.Process.myUserHandle().getIdentifier()),
2637                 REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL,
2638                 REVOKE_RUNTIME_PERMISSIONS);
2639 
2640         final Notification notification =
2641                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
2642                         .setSmallIcon(R.drawable.black)
2643                         .build();
2644         mNotificationManager.notify(id, notification);
2645 
2646         assertTrue(mNotificationHelper.isNotificationGone(id, SEARCH_TYPE.APP));
2647     }
2648 
testIsAmbient()2649     public void testIsAmbient() throws Exception {
2650         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
2651         assertNotNull(mListener);
2652         CountDownLatch postedLatch = mListener.setPostedCountDown(2);
2653 
2654         NotificationChannel lowChannel = new NotificationChannel(
2655                 "testIsAmbientLOW", "testIsAmbientLOW", IMPORTANCE_LOW);
2656         NotificationChannel minChannel = new NotificationChannel(
2657                 "testIsAmbientMIN", "testIsAmbientMIN", IMPORTANCE_MIN);
2658         mNotificationManager.createNotificationChannel(lowChannel);
2659         mNotificationManager.createNotificationChannel(minChannel);
2660 
2661         final Notification lowN =
2662                 new Notification.Builder(mContext, lowChannel.getId())
2663                         .setSmallIcon(R.drawable.black)
2664                         .build();
2665         final Notification minN =
2666                 new Notification.Builder(mContext, minChannel.getId())
2667                         .setSmallIcon(R.drawable.black)
2668                         .build();
2669         mNotificationManager.notify("lowN", 1, lowN);
2670         mNotificationManager.notify("minN", 1, minN);
2671 
2672         postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
2673         StatusBarNotification lowSbn = mNotificationHelper.findPostedNotification("lowN", 1,
2674                 SEARCH_TYPE.POSTED);
2675         StatusBarNotification minSbn = mNotificationHelper.findPostedNotification("minN", 1,
2676                 SEARCH_TYPE.POSTED);
2677 
2678         NotificationListenerService.RankingMap rankingMap = mListener.mRankingMap;
2679         NotificationListenerService.Ranking outRanking = new NotificationListenerService.Ranking();
2680 
2681         rankingMap.getRanking(lowSbn.getKey(), outRanking);
2682         assertFalse(outRanking.isAmbient());
2683 
2684         rankingMap.getRanking(minSbn.getKey(), outRanking);
2685         assertEquals(outRanking.getKey(), IMPORTANCE_MIN, outRanking.getChannel().getImportance());
2686         assertTrue(outRanking.isAmbient());
2687     }
2688 
testFlagForegroundServiceNeedsRealFgs()2689     public void testFlagForegroundServiceNeedsRealFgs() throws Exception {
2690         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
2691         assertNotNull(mListener);
2692         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
2693 
2694         final Notification n =
2695                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
2696                         .setSmallIcon(R.drawable.black)
2697                         .setFlag(FLAG_FOREGROUND_SERVICE, true)
2698                         .build();
2699         mNotificationManager.notify("testFlagForegroundServiceNeedsRealFgs", 1, n);
2700         postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
2701         StatusBarNotification sbn = mNotificationHelper.findPostedNotification(
2702                 "testFlagForegroundServiceNeedsRealFgs", 1, SEARCH_TYPE.POSTED);
2703 
2704         assertEquals(0, (sbn.getNotification().flags & FLAG_FOREGROUND_SERVICE));
2705     }
2706 
testFlagUserInitiatedJobNeedsRealUij()2707     public void testFlagUserInitiatedJobNeedsRealUij() throws Exception {
2708         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
2709         assertNotNull(mListener);
2710         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
2711 
2712         final Notification n =
2713                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
2714                         .setSmallIcon(R.drawable.black)
2715                         .setFlag(FLAG_USER_INITIATED_JOB, true)
2716                         .build();
2717         mNotificationManager.notify("testFlagUserInitiatedJobNeedsRealUij", 1, n);
2718         postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
2719         StatusBarNotification sbn = mNotificationHelper.findPostedNotification(
2720                 "testFlagUserInitiatedJobNeedsRealUij", 1, SEARCH_TYPE.POSTED);
2721 
2722         assertFalse(sbn.getNotification().isUserInitiatedJob());
2723     }
2724 
2725     private static class EventCallback extends Handler {
2726         private static final int BROADCAST_RECEIVED = 1;
2727         private static final int SERVICE_STARTED = 2;
2728         private static final int ACTIVITY_STARTED = 3;
2729         private static final int NOTIFICATION_CLICKED = 4;
2730 
2731         private final Map<Integer, CompletableFuture<Integer>> mEvents =
2732                 Collections.synchronizedMap(new ArrayMap<>());
2733 
EventCallback()2734         private EventCallback() {
2735             super(Looper.getMainLooper());
2736         }
2737 
2738         @Override
handleMessage(Message message)2739         public void handleMessage(Message message) {
2740             mEvents.computeIfAbsent(message.what, e -> new CompletableFuture<>()).obtrudeValue(
2741                     message.arg1);
2742         }
2743 
waitFor(int event, long timeoutMs)2744         public boolean waitFor(int event, long timeoutMs) {
2745             try {
2746                 return mEvents.computeIfAbsent(event, e -> new CompletableFuture<>()).get(timeoutMs,
2747                         TimeUnit.MILLISECONDS) == 0;
2748             } catch (InterruptedException | ExecutionException | TimeoutException e) {
2749                 return false;
2750             }
2751         }
2752     }
2753 }
2754