• 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_NO_CLEAR;
28 import static android.app.Notification.FLAG_USER_INITIATED_JOB;
29 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
30 import static android.app.NotificationManager.IMPORTANCE_HIGH;
31 import static android.app.NotificationManager.IMPORTANCE_LOW;
32 import static android.app.NotificationManager.IMPORTANCE_MIN;
33 import static android.app.NotificationManager.IMPORTANCE_NONE;
34 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
35 import static android.content.pm.PackageManager.FEATURE_LEANBACK;
36 import static android.content.pm.PackageManager.FEATURE_WATCH;
37 import static android.service.notification.NotificationListenerService.META_DATA_DEFAULT_AUTOBIND;
38 
39 import static com.google.common.truth.Truth.assertThat;
40 
41 import static org.hamcrest.CoreMatchers.hasItem;
42 import static org.junit.Assert.assertEquals;
43 import static org.junit.Assert.assertFalse;
44 import static org.junit.Assert.assertNotNull;
45 import static org.junit.Assert.assertNull;
46 import static org.junit.Assert.assertThat;
47 import static org.junit.Assert.assertThrows;
48 import static org.junit.Assert.assertTrue;
49 import static org.junit.Assert.fail;
50 import static org.junit.Assume.assumeFalse;
51 
52 import android.Manifest;
53 import android.app.Notification;
54 import android.app.NotificationChannel;
55 import android.app.NotificationChannelGroup;
56 import android.app.NotificationManager;
57 import android.app.NotificationManager.CallNotificationEventListener;
58 import android.app.PendingIntent;
59 import android.app.compat.CompatChanges;
60 import android.app.role.RoleManager;
61 import android.app.stubs.GetResultActivity;
62 import android.app.stubs.R;
63 import android.app.stubs.shared.FutureServiceConnection;
64 import android.app.stubs.shared.NotificationHelper.SEARCH_TYPE;
65 import android.app.stubs.shared.TestNotificationListener;
66 import android.content.ComponentName;
67 import android.content.ContentResolver;
68 import android.content.Context;
69 import android.content.Intent;
70 import android.content.ServiceConnection;
71 import android.content.pm.PackageInfo;
72 import android.content.pm.PackageManager;
73 import android.content.pm.ServiceInfo;
74 import android.content.res.AssetFileDescriptor;
75 import android.graphics.Bitmap;
76 import android.graphics.drawable.Icon;
77 import android.media.AudioAttributes;
78 import android.media.session.MediaSession;
79 import android.net.Uri;
80 import android.os.Build;
81 import android.os.Handler;
82 import android.os.IBinder;
83 import android.os.Looper;
84 import android.os.Message;
85 import android.os.Messenger;
86 import android.os.SystemProperties;
87 import android.os.UserHandle;
88 import android.permission.PermissionManager;
89 import android.permission.cts.PermissionUtils;
90 import android.platform.test.annotations.AsbSecurityTest;
91 import android.platform.test.annotations.RequiresDevice;
92 import android.platform.test.annotations.RequiresFlagsDisabled;
93 import android.platform.test.annotations.RequiresFlagsEnabled;
94 import android.provider.Settings;
95 import android.service.notification.Flags;
96 import android.service.notification.NotificationListenerService;
97 import android.service.notification.StatusBarNotification;
98 import android.util.ArrayMap;
99 import android.util.Log;
100 import android.widget.RemoteViews;
101 
102 import androidx.annotation.NonNull;
103 import androidx.annotation.Nullable;
104 import androidx.test.filters.LargeTest;
105 import androidx.test.platform.app.InstrumentationRegistry;
106 import androidx.test.runner.AndroidJUnit4;
107 import androidx.test.uiautomator.UiDevice;
108 
109 import com.android.bedstead.harrier.DeviceState;
110 import com.android.bedstead.multiuser.annotations.RequireRunNotOnVisibleBackgroundNonProfileUser;
111 import com.android.compatibility.common.util.PollingCheck;
112 import com.android.compatibility.common.util.SystemUtil;
113 import com.android.compatibility.common.util.ThrowingSupplier;
114 import com.android.compatibility.common.util.UserHelper;
115 import com.android.modules.utils.build.SdkLevel;
116 import com.android.test.notificationlistener.INLSControlService;
117 import com.android.test.notificationlistener.INotificationUriAccessService;
118 
119 import com.google.common.base.Preconditions;
120 
121 import org.junit.After;
122 import org.junit.Before;
123 import org.junit.ClassRule;
124 import org.junit.FixMethodOrder;
125 import org.junit.Ignore;
126 import org.junit.Rule;
127 import org.junit.Test;
128 import org.junit.runner.RunWith;
129 import org.junit.runners.MethodSorters;
130 
131 import java.io.BufferedReader;
132 import java.io.IOException;
133 import java.io.InputStreamReader;
134 import java.util.ArrayList;
135 import java.util.Arrays;
136 import java.util.Collections;
137 import java.util.HashMap;
138 import java.util.List;
139 import java.util.Map;
140 import java.util.UUID;
141 import java.util.concurrent.CompletableFuture;
142 import java.util.concurrent.CompletionException;
143 import java.util.concurrent.CountDownLatch;
144 import java.util.concurrent.ExecutionException;
145 import java.util.concurrent.Executor;
146 import java.util.concurrent.Semaphore;
147 import java.util.concurrent.TimeUnit;
148 import java.util.concurrent.TimeoutException;
149 
150 // TODO(b/380297485): This test module has lots of assumption checks because NotificationListeners
151 // don't support visible background users. Remove the assumption checks once NotificationListeners
152 // support visible background users.
153 /* This tests NotificationListenerService together with NotificationManager, as you need to have
154  * notifications to manipulate in order to test the listener service. */
155 @RunWith(AndroidJUnit4.class)
156 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
157 public class NotificationManagerTest extends BaseNotificationManagerTest {
158     public static final String NOTIFICATIONPROVIDER = "com.android.test.notificationprovider";
159     public static final String RICH_NOTIFICATION_ACTIVITY =
160             "com.android.test.notificationprovider.RichNotificationActivity";
161     final String TAG = NotificationManagerTest.class.getSimpleName();
162     final boolean DEBUG = false;
163 
164     @ClassRule
165     @Rule
166     public static final DeviceState sDeviceState = new DeviceState();
167 
168     private static final long ENFORCE_NO_CLEAR_FLAG_ON_MEDIA_NOTIFICATION = 264179692L;
169     private static final String DELEGATE_POST_CLASS = TEST_APP + ".NotificationDelegateAndPost";
170     private static final String REVOKE_CLASS = TEST_APP + ".NotificationRevoker";
171 
172     private static final String TRAMPOLINE_APP =
173             "com.android.test.notificationtrampoline.current";
174     private static final String TRAMPOLINE_APP_API_30 =
175             "com.android.test.notificationtrampoline.api30";
176     private static final String TRAMPOLINE_APP_API_32 =
177             "com.android.test.notificationtrampoline.api32";
178     private static final ComponentName TRAMPOLINE_SERVICE =
179             new ComponentName(TRAMPOLINE_APP,
180                     "com.android.test.notificationtrampoline.NotificationTrampolineTestService");
181     private static final ComponentName TRAMPOLINE_SERVICE_API_30 =
182             new ComponentName(TRAMPOLINE_APP_API_30,
183                     "com.android.test.notificationtrampoline.NotificationTrampolineTestService");
184     private static final ComponentName TRAMPOLINE_SERVICE_API_32 =
185             new ComponentName(TRAMPOLINE_APP_API_32,
186                     "com.android.test.notificationtrampoline.NotificationTrampolineTestService");
187 
188     private static final String PRESSURE_APP_00 =
189             "com.android.test.NotificationPressure00";
190     private static final String PRESSURE_APP_01 =
191             "com.android.test.NotificationPressure01";
192     private static final String PRESSURE_APP_02 =
193             "com.android.test.NotificationPressure02";
194     private static final String PRESSURE_APP_03 =
195             "com.android.test.NotificationPressure03";
196     private static final String PRESSURE_APP_04 =
197             "com.android.test.NotificationPressure04";
198     private static final String PRESSURE_APP_05 =
199             "com.android.test.NotificationPressure05";
200     private static final String PRESSURE_APP_06 =
201             "com.android.test.NotificationPressure06";
202     private static final String PRESSURE_APP_07 =
203             "com.android.test.NotificationPressure07";
204 
205     private static final ComponentName PRESSURE_SERVICE_00 = new ComponentName(PRESSURE_APP_00,
206             "com.android.test.notificationtrampoline.NotificationTrampolineTestService");
207     private static final ComponentName PRESSURE_SERVICE_01 = new ComponentName(PRESSURE_APP_01,
208             "com.android.test.notificationtrampoline.NotificationTrampolineTestService");
209     private static final ComponentName PRESSURE_SERVICE_02 = new ComponentName(PRESSURE_APP_02,
210             "com.android.test.notificationtrampoline.NotificationTrampolineTestService");
211     private static final ComponentName PRESSURE_SERVICE_03 = new ComponentName(PRESSURE_APP_03,
212             "com.android.test.notificationtrampoline.NotificationTrampolineTestService");
213     private static final ComponentName PRESSURE_SERVICE_04 = new ComponentName(PRESSURE_APP_04,
214             "com.android.test.notificationtrampoline.NotificationTrampolineTestService");
215     private static final ComponentName PRESSURE_SERVICE_05 = new ComponentName(PRESSURE_APP_05,
216             "com.android.test.notificationtrampoline.NotificationTrampolineTestService");
217     private static final ComponentName PRESSURE_SERVICE_06 = new ComponentName(PRESSURE_APP_06,
218             "com.android.test.notificationtrampoline.NotificationTrampolineTestService");
219     private static final ComponentName PRESSURE_SERVICE_07 = new ComponentName(PRESSURE_APP_07,
220             "com.android.test.notificationtrampoline.NotificationTrampolineTestService");
221 
222 
223     private static final ComponentName URI_ACCESS_SERVICE = new ComponentName(
224             "com.android.test.notificationlistener",
225             "com.android.test.notificationlistener.NotificationUriAccessService");
226 
227     private static final ComponentName NLS_CONTROL_SERVICE = new ComponentName(
228             "com.android.test.notificationlistener",
229             "com.android.test.notificationlistener.NLSControlService");
230 
231     private static final ComponentName NO_AUTOBIND_NLS = new ComponentName(
232             "com.android.test.notificationlistener",
233             "com.android.test.notificationlistener.TestNotificationListenerNoAutobind");
234 
235     private static final String STUB_PACKAGE_NAME = "android.app.stubs";
236 
237     private static final long TIMEOUT_LONG_MS = 10000;
238     private static final long TIMEOUT_MS = 4000;
239 
240     private static final long POST_TIMEOUT = 200;
241     private static final long TIMEOUT_FORCE_REGROUP_MS = TIMEOUT_MS + POST_TIMEOUT;
242     private static final int MESSAGE_BROADCAST_NOTIFICATION = 1;
243     private static final int MESSAGE_SERVICE_NOTIFICATION = 2;
244     private static final int MESSAGE_CLICK_NOTIFICATION = 3;
245     private static final int MESSAGE_CANCEL_ALL_NOTIFICATIONS = 4;
246 
247     private String mId;
248     private INotificationUriAccessService mNotificationUriAccessService;
249     private INLSControlService mNLSControlService;
250     private FutureServiceConnection mTrampolineConnection;
251     private UserHelper mUserHelper;
252 
253     @Nullable
254     private List<String> mPreviousDefaultBrowser;
255 
256     @Before
setUp()257     public void setUp() throws Exception {
258         PermissionUtils.grantPermission(mContext.getPackageName(), POST_NOTIFICATIONS);
259         PermissionUtils.grantPermission(STUB_PACKAGE_NAME, POST_NOTIFICATIONS);
260         PermissionUtils.grantPermission(TEST_APP, POST_NOTIFICATIONS);
261         PermissionUtils.grantPermission(TRAMPOLINE_APP, POST_NOTIFICATIONS);
262         PermissionUtils.grantPermission(TRAMPOLINE_APP_API_30, POST_NOTIFICATIONS);
263         PermissionUtils.grantPermission(TRAMPOLINE_APP_API_32, POST_NOTIFICATIONS);
264         PermissionUtils.grantPermission(NOTIFICATIONPROVIDER, POST_NOTIFICATIONS);
265         PermissionUtils.grantPermission(PRESSURE_APP_00, POST_NOTIFICATIONS);
266         PermissionUtils.grantPermission(PRESSURE_APP_01, POST_NOTIFICATIONS);
267         PermissionUtils.grantPermission(PRESSURE_APP_02, POST_NOTIFICATIONS);
268         PermissionUtils.grantPermission(PRESSURE_APP_03, POST_NOTIFICATIONS);
269         PermissionUtils.grantPermission(PRESSURE_APP_04, POST_NOTIFICATIONS);
270         PermissionUtils.grantPermission(PRESSURE_APP_05, POST_NOTIFICATIONS);
271         PermissionUtils.grantPermission(PRESSURE_APP_06, POST_NOTIFICATIONS);
272         PermissionUtils.grantPermission(PRESSURE_APP_07, POST_NOTIFICATIONS);
273         PermissionUtils.setAppOp(mContext.getPackageName(),
274                 android.Manifest.permission.ACCESS_NOTIFICATIONS, MODE_ALLOWED);
275 
276         // This will leave a set of channels on the device with each test run.
277         mId = UUID.randomUUID().toString();
278         mUserHelper = new UserHelper(mContext);
279 
280         // delay between tests so notifications aren't dropped by the rate limiter
281         try {
282             Thread.sleep(500);
283         } catch (InterruptedException e) {
284         }
285     }
286 
287     @After
tearDown()288     public void tearDown() throws Exception {
289         // For trampoline tests
290         if (mTrampolineConnection != null) {
291             mContext.unbindService(mTrampolineConnection);
292             mTrampolineConnection = null;
293         }
294         if (mListener != null) {
295             mListener.removeTestPackage(TRAMPOLINE_APP_API_30);
296             mListener.removeTestPackage(TRAMPOLINE_APP);
297         }
298         if (mPreviousDefaultBrowser != null) {
299             restoreDefaultBrowser();
300         }
301 
302         // Use test API to prevent PermissionManager from killing the test process when revoking
303         // permission.
304         SystemUtil.runWithShellPermissionIdentity(
305                 () -> mContext.getSystemService(PermissionManager.class)
306                         .revokePostNotificationPermissionWithoutKillForTest(
307                                 mContext.getPackageName(),
308                                 android.os.Process.myUserHandle().getIdentifier()),
309                 REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL,
310                 REVOKE_RUNTIME_PERMISSIONS);
311         PermissionUtils.revokePermission(STUB_PACKAGE_NAME, POST_NOTIFICATIONS);
312         PermissionUtils.revokePermission(TEST_APP, POST_NOTIFICATIONS);
313         PermissionUtils.revokePermission(TRAMPOLINE_APP, POST_NOTIFICATIONS);
314         PermissionUtils.revokePermission(NOTIFICATIONPROVIDER, POST_NOTIFICATIONS);
315         PermissionUtils.revokePermission(PRESSURE_APP_00, POST_NOTIFICATIONS);
316         PermissionUtils.revokePermission(PRESSURE_APP_01, POST_NOTIFICATIONS);
317         PermissionUtils.revokePermission(PRESSURE_APP_02, POST_NOTIFICATIONS);
318         PermissionUtils.revokePermission(PRESSURE_APP_03, POST_NOTIFICATIONS);
319         PermissionUtils.revokePermission(PRESSURE_APP_04, POST_NOTIFICATIONS);
320         PermissionUtils.revokePermission(PRESSURE_APP_05, POST_NOTIFICATIONS);
321         PermissionUtils.revokePermission(PRESSURE_APP_06, POST_NOTIFICATIONS);
322         PermissionUtils.revokePermission(PRESSURE_APP_07, POST_NOTIFICATIONS);
323         PermissionUtils.setAppOp(mContext.getPackageName(),
324                 android.Manifest.permission.ACCESS_NOTIFICATIONS, MODE_DEFAULT);
325     }
326 
getPendingIntent()327     private PendingIntent getPendingIntent() {
328         return PendingIntent.getActivity(
329                 mContext, 0, new Intent(mContext, this.getClass()),
330                 PendingIntent.FLAG_MUTABLE_UNAUDITED);
331     }
332 
isAutogroupSummary(Notification n)333     private boolean isAutogroupSummary(Notification n) {
334         return n.getGroup() != null && (n.flags & Notification.FLAG_AUTOGROUP_SUMMARY) != 0;
335     }
336 
isGroupSummary(Notification n)337     private boolean isGroupSummary(Notification n) {
338         return n.getGroup() != null && (n.flags & Notification.FLAG_GROUP_SUMMARY) != 0;
339     }
340 
assertOnlySomeNotificationsAutogrouped(List<Integer> autoGroupedIds)341     private void assertOnlySomeNotificationsAutogrouped(List<Integer> autoGroupedIds)
342             throws Exception {
343         String expectedGroupKey = null;
344         Thread.sleep(150);
345 
346         StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
347         for (StatusBarNotification sbn : sbns) {
348             final boolean expectAutogrouped;
349             if (android.service.notification.Flags.notificationForceGrouping()) {
350                 expectAutogrouped = isAutogroupSummary(sbn.getNotification())
351                         || autoGroupedIds.contains(sbn.getId());
352             } else {
353                 expectAutogrouped = isGroupSummary(sbn.getNotification())
354                         || autoGroupedIds.contains(sbn.getId());
355             }
356             if (expectAutogrouped) {
357                 assertTrue(sbn.getKey() + " is unexpectedly not autogrouped",
358                         sbn.getOverrideGroupKey() != null);
359                 if (expectedGroupKey == null) {
360                     expectedGroupKey = sbn.getGroupKey();
361                 }
362                 assertEquals(expectedGroupKey, sbn.getGroupKey());
363             } else {
364                 assertTrue(sbn.isGroup());
365                 assertTrue(sbn.getKey() + " is unexpectedly autogrouped,",
366                         sbn.getOverrideGroupKey() == null);
367                 assertTrue(sbn.getKey() + " has an unusual group key",
368                         sbn.getGroupKey() != expectedGroupKey);
369             }
370         }
371     }
372 
assertAllPostedNotificationsAutogrouped()373     private void assertAllPostedNotificationsAutogrouped() throws Exception {
374         String expectedGroupKey = null;
375         // Posting can take ~100 ms
376         Thread.sleep(150);
377 
378         StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
379         for (StatusBarNotification sbn : sbns) {
380             // all notis should be in a group determined by autogrouping
381             assertTrue(sbn.getOverrideGroupKey() != null);
382             if (expectedGroupKey == null) {
383                 expectedGroupKey = sbn.getGroupKey();
384             }
385             // all notis should be in the same group
386             assertEquals(expectedGroupKey, sbn.getGroupKey());
387         }
388     }
389 
getCancellationReason(String key)390     private int getCancellationReason(String key) {
391         for (int tries = 3; tries-- > 0; ) {
392             if (mListener.mRemovedReasons.containsKey(key)) {
393                 return mListener.mRemovedReasons.get(key);
394             }
395             try {
396                 Thread.sleep(1000);
397             } catch (InterruptedException ex) {
398                 // pass
399             }
400         }
401         return -1;
402     }
403 
getAssistantCancellationReason(String key)404     private int getAssistantCancellationReason(String key) {
405         for (int tries = 3; tries-- > 0; ) {
406             if (mAssistant.mRemoved.containsKey(key)) {
407                 return mAssistant.mRemoved.get(key);
408             }
409             try {
410                 Thread.sleep(1000);
411             } catch (InterruptedException ex) {
412                 // pass
413             }
414         }
415         return -1;
416     }
417 
assertNotificationCount(int expectedCount)418     private void assertNotificationCount(int expectedCount) {
419         // notification is a bit asynchronous so it may take a few ms to appear in
420         // getActiveNotifications()
421         // we will check for it for up to 400ms before giving up
422         int lastCount = 0;
423         for (int tries = 4; tries-- > 0; ) {
424             final StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
425             lastCount = sbns.length;
426             if (expectedCount == lastCount) return;
427             try {
428                 Thread.sleep(100);
429             } catch (InterruptedException ex) {
430                 // pass
431             }
432         }
433         fail("Expected " + expectedCount + " posted notifications, were " + lastCount);
434     }
435 
compareChannels(NotificationChannel expected, NotificationChannel actual)436     private void compareChannels(NotificationChannel expected, NotificationChannel actual) {
437         if (actual == null) {
438             fail("actual channel is null");
439             return;
440         }
441         if (expected == null) {
442             fail("expected channel is null");
443             return;
444         }
445         assertEquals(expected.getId(), actual.getId());
446         assertEquals(expected.getName().toString(), actual.getName().toString());
447         assertEquals(expected.getDescription(), actual.getDescription());
448         assertEquals(expected.shouldVibrate(), actual.shouldVibrate());
449         assertEquals(expected.shouldShowLights(), actual.shouldShowLights());
450         assertEquals(expected.getLightColor(), actual.getLightColor());
451         assertEquals(expected.getImportance(), actual.getImportance());
452         if (expected.getSound() == null) {
453             assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actual.getSound());
454             assertEquals(Notification.AUDIO_ATTRIBUTES_DEFAULT, actual.getAudioAttributes());
455         } else {
456             assertEquals(expected.getSound(), actual.getSound());
457             assertEquals(expected.getAudioAttributes(), actual.getAudioAttributes());
458         }
459         assertTrue(Arrays.equals(expected.getVibrationPattern(), actual.getVibrationPattern()));
460         if (android.app.Flags.notificationChannelVibrationEffectApi()) {
461             assertEquals(expected.getVibrationEffect(), actual.getVibrationEffect());
462         }
463         assertEquals(expected.getGroup(), actual.getGroup());
464         assertEquals(expected.getConversationId(), actual.getConversationId());
465         assertEquals(expected.getParentChannelId(), actual.getParentChannelId());
466         assertEquals(expected.isDemoted(), actual.isDemoted());
467     }
468 
sendTrampolineMessage(ComponentName component, int message, int notificationId, Handler callback)469     private void sendTrampolineMessage(ComponentName component, int message,
470             int notificationId, Handler callback) throws Exception {
471         if (mTrampolineConnection == null) {
472             Intent intent = new Intent();
473             intent.setComponent(component);
474             mTrampolineConnection = new FutureServiceConnection();
475             assertTrue(
476                     mContext.bindService(intent, mTrampolineConnection, Context.BIND_AUTO_CREATE));
477         }
478         Messenger service = new Messenger(mTrampolineConnection.get(TIMEOUT_MS));
479         service.send(Message.obtain(null, message, notificationId, -1, new Messenger(callback)));
480     }
481 
setDefaultBrowser(String packageName)482     private void setDefaultBrowser(String packageName) throws Exception {
483         UserHandle user = android.os.Process.myUserHandle();
484         mPreviousDefaultBrowser = SystemUtil.callWithShellPermissionIdentity(
485                 () -> mRoleManager.getRoleHoldersAsUser(RoleManager.ROLE_BROWSER, user));
486         CompletableFuture<Boolean> set = new CompletableFuture<>();
487         SystemUtil.runWithShellPermissionIdentity(
488                 () -> mRoleManager.addRoleHolderAsUser(RoleManager.ROLE_BROWSER, packageName, 0,
489                         user, mContext.getMainExecutor(), set::complete));
490         assertTrue("Failed to set " + packageName + " as default browser",
491                 set.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
492     }
493 
restoreDefaultBrowser()494     private void restoreDefaultBrowser() throws Exception {
495         Preconditions.checkState(mPreviousDefaultBrowser != null);
496         UserHandle user = android.os.Process.myUserHandle();
497         Executor executor = mContext.getMainExecutor();
498         CompletableFuture<Boolean> restored = new CompletableFuture<>();
499         SystemUtil.runWithShellPermissionIdentity(() -> {
500             mRoleManager.clearRoleHoldersAsUser(RoleManager.ROLE_BROWSER, 0, user, executor,
501                     restored::complete);
502             for (String packageName : mPreviousDefaultBrowser) {
503                 mRoleManager.addRoleHolderAsUser(RoleManager.ROLE_BROWSER, packageName,
504                         0, user, executor, restored::complete);
505             }
506         });
507         assertTrue("Failed to restore default browser",
508                 restored.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
509     }
510 
511     /**
512      * Previous tests could have started activities within the grace period, so go home to avoid
513      * allowing background activity starts due to this exemption.
514      */
deactivateGracePeriod()515     private void deactivateGracePeriod() {
516         UiDevice.getInstance(mInstrumentation).pressHome();
517     }
518 
verifyCanUseFullScreenIntent(int appOpState, boolean canSend)519     private void verifyCanUseFullScreenIntent(int appOpState, boolean canSend) throws Exception {
520         final int previousState = PermissionUtils.getAppOp(STUB_PACKAGE_NAME,
521                 Manifest.permission.USE_FULL_SCREEN_INTENT);
522         try {
523             PermissionUtils.setAppOp(STUB_PACKAGE_NAME,
524                     Manifest.permission.USE_FULL_SCREEN_INTENT,
525                     appOpState);
526 
527             if (canSend) {
528                 assertTrue(mNotificationManager.canUseFullScreenIntent());
529             } else {
530                 assertFalse(mNotificationManager.canUseFullScreenIntent());
531             }
532 
533         } finally {
534             // Clean up by setting to app op to previous state.
535             PermissionUtils.setAppOp(STUB_PACKAGE_NAME,
536                     Manifest.permission.USE_FULL_SCREEN_INTENT,
537                     previousState);
538         }
539     }
540 
541     @Test
testCanSendFullScreenIntent_modeDefault_returnsIsPermissionGranted()542     public void testCanSendFullScreenIntent_modeDefault_returnsIsPermissionGranted()
543             throws Exception {
544         final boolean isPermissionGranted = PermissionUtils.isPermissionGranted(STUB_PACKAGE_NAME,
545                 Manifest.permission.USE_FULL_SCREEN_INTENT);
546         verifyCanUseFullScreenIntent(MODE_DEFAULT, /*canSend=*/ isPermissionGranted);
547     }
548 
549     @Test
testCanSendFullScreenIntent_modeAllowed_returnsTrue()550     public void testCanSendFullScreenIntent_modeAllowed_returnsTrue() throws Exception {
551         verifyCanUseFullScreenIntent(MODE_ALLOWED, /*canSend=*/ true);
552     }
553 
554     @Test
testCanSendFullScreenIntent_modeErrored_returnsFalse()555     public void testCanSendFullScreenIntent_modeErrored_returnsFalse() throws Exception {
556         verifyCanUseFullScreenIntent(MODE_ERRORED, /*canSend=*/ false);
557     }
558 
559     @Test
testCreateChannelGroup()560     public void testCreateChannelGroup() throws Exception {
561         final NotificationChannelGroup ncg = new NotificationChannelGroup("a group", "a label");
562         final NotificationChannel channel =
563                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
564         channel.setGroup(ncg.getId());
565         mNotificationManager.createNotificationChannelGroup(ncg);
566         final NotificationChannel ungrouped =
567                 new NotificationChannel(mId + "!", "name", IMPORTANCE_DEFAULT);
568         try {
569             mNotificationManager.createNotificationChannel(channel);
570             mNotificationManager.createNotificationChannel(ungrouped);
571 
572             List<NotificationChannelGroup> ncgs =
573                     mNotificationManager.getNotificationChannelGroups();
574             assertEquals(1, ncgs.size());
575             assertEquals(ncg.getName(), ncgs.get(0).getName().toString());
576             assertEquals(ncg.getDescription(), ncgs.get(0).getDescription());
577             assertEquals(channel.getId(), ncgs.get(0).getChannels().get(0).getId());
578         } finally {
579             mNotificationManager.deleteNotificationChannelGroup(ncg.getId());
580         }
581     }
582 
583     @Test
testGetChannelGroup()584     public void testGetChannelGroup() throws Exception {
585         final NotificationChannelGroup ncg = new NotificationChannelGroup("a group", "a label");
586         ncg.setDescription("bananas");
587         final NotificationChannelGroup ncg2 = new NotificationChannelGroup("group 2", "label 2");
588         final NotificationChannel channel =
589                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
590         channel.setGroup(ncg.getId());
591 
592         mNotificationManager.createNotificationChannelGroup(ncg);
593         mNotificationManager.createNotificationChannelGroup(ncg2);
594         mNotificationManager.createNotificationChannel(channel);
595 
596         NotificationChannelGroup actual =
597                 mNotificationManager.getNotificationChannelGroup(ncg.getId());
598         assertEquals(ncg.getId(), actual.getId());
599         assertEquals(ncg.getName(), actual.getName().toString());
600         assertEquals(ncg.getDescription(), actual.getDescription());
601         assertEquals(channel.getId(), actual.getChannels().get(0).getId());
602     }
603 
604     @Test
testGetChannelGroup_groupUpdate()605     public void testGetChannelGroup_groupUpdate() {
606         final NotificationChannelGroup ncg = new NotificationChannelGroup("a group", "a label");
607         ncg.setDescription("first description");
608         mNotificationManager.createNotificationChannelGroup(ncg);
609 
610         final NotificationChannel channel =
611                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
612         channel.setGroup(ncg.getId());
613         mNotificationManager.createNotificationChannel(channel);
614 
615         NotificationChannelGroup actual =
616                 mNotificationManager.getNotificationChannelGroup(ncg.getId());
617         assertThat(actual.getId()).isEqualTo(ncg.getId());
618         assertThat(actual.getName().toString()).isEqualTo(ncg.getName().toString());
619         assertThat(actual.getDescription()).isEqualTo(ncg.getDescription());
620         compareChannels(channel, actual.getChannels().get(0));
621 
622         // Update group description
623         ncg.setDescription("new description");
624         mNotificationManager.createNotificationChannelGroup(ncg);
625 
626         NotificationChannelGroup updated =
627                 mNotificationManager.getNotificationChannelGroup(ncg.getId());
628         assertThat(updated.getId()).isEqualTo(ncg.getId());
629         assertThat(updated.getName().toString()).isEqualTo(ncg.getName().toString());
630         assertThat(updated.getDescription()).isEqualTo(ncg.getDescription());
631         compareChannels(channel, updated.getChannels().get(0));
632     }
633 
634     @Test
testGetChannelGroups()635     public void testGetChannelGroups() throws Exception {
636         final NotificationChannelGroup ncg = new NotificationChannelGroup("a group", "a label");
637         ncg.setDescription("bananas");
638         final NotificationChannelGroup ncg2 = new NotificationChannelGroup("group 2", "label 2");
639         final NotificationChannel channel =
640                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
641         channel.setGroup(ncg2.getId());
642 
643         mNotificationManager.createNotificationChannelGroup(ncg);
644         mNotificationManager.createNotificationChannelGroup(ncg2);
645         mNotificationManager.createNotificationChannel(channel);
646 
647         List<NotificationChannelGroup> actual =
648                 mNotificationManager.getNotificationChannelGroups();
649         assertEquals(2, actual.size());
650         for (NotificationChannelGroup group : actual) {
651             if (group.getId().equals(ncg.getId())) {
652                 assertEquals(group.getName(), ncg.getName().toString());
653                 assertEquals(group.getDescription(), ncg.getDescription());
654                 assertEquals(0, group.getChannels().size());
655             } else if (group.getId().equals(ncg2.getId())) {
656                 assertEquals(group.getName(), ncg2.getName().toString());
657                 assertEquals(group.getDescription(), ncg2.getDescription());
658                 assertEquals(1, group.getChannels().size());
659                 assertEquals(channel.getId(), group.getChannels().get(0).getId());
660             } else {
661                 fail("Extra group found " + group.getId());
662             }
663         }
664     }
665 
666     @Test
testDeleteChannelGroup()667     public void testDeleteChannelGroup() throws Exception {
668         final NotificationChannelGroup ncg = new NotificationChannelGroup("a group", "a label");
669         final NotificationChannel channel =
670                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
671         channel.setGroup(ncg.getId());
672         mNotificationManager.createNotificationChannelGroup(ncg);
673         mNotificationManager.createNotificationChannel(channel);
674 
675         mNotificationManager.deleteNotificationChannelGroup(ncg.getId());
676 
677         assertNull(mNotificationManager.getNotificationChannel(channel.getId()));
678         assertEquals(0, mNotificationManager.getNotificationChannelGroups().size());
679     }
680 
681     @Test
testCreateChannel()682     public void testCreateChannel() throws Exception {
683         final NotificationChannel channel =
684                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
685         channel.setDescription("bananas");
686         channel.enableVibration(true);
687         channel.setVibrationPattern(new long[]{5, 8, 2, 1});
688         channel.setSound(new Uri.Builder().scheme("test").build(),
689                 new AudioAttributes.Builder().setUsage(
690                         AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED).build());
691         channel.enableLights(true);
692         channel.setBypassDnd(true);
693         channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
694         mNotificationManager.createNotificationChannel(channel);
695         final NotificationChannel createdChannel =
696                 mNotificationManager.getNotificationChannel(mId);
697         compareChannels(channel, createdChannel);
698         // Lockscreen Visibility and canBypassDnd no longer settable.
699         assertTrue(createdChannel.getLockscreenVisibility() != Notification.VISIBILITY_SECRET);
700         assertFalse(createdChannel.canBypassDnd());
701     }
702 
703     @Test
testCreateChannel_rename()704     public void testCreateChannel_rename() throws Exception {
705         NotificationChannel channel =
706                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
707         mNotificationManager.createNotificationChannel(channel);
708         channel.setName("new name");
709         mNotificationManager.createNotificationChannel(channel);
710         final NotificationChannel createdChannel =
711                 mNotificationManager.getNotificationChannel(mId);
712         compareChannels(channel, createdChannel);
713 
714         channel.setImportance(NotificationManager.IMPORTANCE_HIGH);
715         mNotificationManager.createNotificationChannel(channel);
716         assertEquals(NotificationManager.IMPORTANCE_DEFAULT,
717                 mNotificationManager.getNotificationChannel(mId).getImportance());
718     }
719 
720     @Test
testCreateChannel_addToGroup()721     public void testCreateChannel_addToGroup() throws Exception {
722         String oldGroup = null;
723         String newGroup = "new group";
724         mNotificationManager.createNotificationChannelGroup(
725                 new NotificationChannelGroup(newGroup, newGroup));
726 
727         NotificationChannel channel =
728                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
729         channel.setGroup(oldGroup);
730         mNotificationManager.createNotificationChannel(channel);
731 
732         // Check that newGroup has no channels so far
733         NotificationChannelGroup foundGroup =
734                 mNotificationManager.getNotificationChannelGroup(newGroup);
735         assertThat(foundGroup.getChannels()).hasSize(0);
736 
737         channel.setGroup(newGroup);
738         mNotificationManager.createNotificationChannel(channel);
739 
740         final NotificationChannel updatedChannel =
741                 mNotificationManager.getNotificationChannel(mId);
742         assertEquals("Failed to add non-grouped channel to a group on update ",
743                 newGroup, updatedChannel.getGroup());
744 
745         // The resulting group should have the channel in it now
746         foundGroup = mNotificationManager.getNotificationChannelGroup(newGroup);
747         assertThat(foundGroup.getChannels()).hasSize(1);
748     }
749 
750     @Test
testCreateChannel_cannotChangeGroup()751     public void testCreateChannel_cannotChangeGroup() throws Exception {
752         String oldGroup = "old group";
753         String newGroup = "new group";
754         mNotificationManager.createNotificationChannelGroup(
755                 new NotificationChannelGroup(oldGroup, oldGroup));
756         mNotificationManager.createNotificationChannelGroup(
757                 new NotificationChannelGroup(newGroup, newGroup));
758 
759         NotificationChannel channel =
760                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
761         channel.setGroup(oldGroup);
762         mNotificationManager.createNotificationChannel(channel);
763         channel.setGroup(newGroup);
764         mNotificationManager.createNotificationChannel(channel);
765         final NotificationChannel updatedChannel =
766                 mNotificationManager.getNotificationChannel(mId);
767         assertEquals("Channels should not be allowed to change groups",
768                 oldGroup, updatedChannel.getGroup());
769     }
770 
771     @Test
testCreateSameChannelDoesNotUpdate()772     public void testCreateSameChannelDoesNotUpdate() throws Exception {
773         final NotificationChannel channel =
774                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
775         mNotificationManager.createNotificationChannel(channel);
776         final NotificationChannel channelDupe =
777                 new NotificationChannel(mId, "name", IMPORTANCE_HIGH);
778         mNotificationManager.createNotificationChannel(channelDupe);
779         final NotificationChannel createdChannel =
780                 mNotificationManager.getNotificationChannel(mId);
781         compareChannels(channel, createdChannel);
782     }
783 
784     @Test
testCreateChannelAlreadyExistsNoOp()785     public void testCreateChannelAlreadyExistsNoOp() throws Exception {
786         NotificationChannel channel =
787                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
788         mNotificationManager.createNotificationChannel(channel);
789         NotificationChannel channelDupe =
790                 new NotificationChannel(mId, "name", IMPORTANCE_HIGH);
791         mNotificationManager.createNotificationChannel(channelDupe);
792         compareChannels(channel, mNotificationManager.getNotificationChannel(channel.getId()));
793     }
794 
795     @Test
testCreateChannelWithGroup()796     public void testCreateChannelWithGroup() throws Exception {
797         NotificationChannelGroup ncg = new NotificationChannelGroup("g", "n");
798         mNotificationManager.createNotificationChannelGroup(ncg);
799         try {
800             NotificationChannel channel =
801                     new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
802             channel.setGroup(ncg.getId());
803             mNotificationManager.createNotificationChannel(channel);
804             compareChannels(channel, mNotificationManager.getNotificationChannel(channel.getId()));
805         } finally {
806             mNotificationManager.deleteNotificationChannelGroup(ncg.getId());
807         }
808     }
809 
810     @Test
testCreateChannelWithBadGroup()811     public void testCreateChannelWithBadGroup() throws Exception {
812         NotificationChannel channel =
813                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
814         channel.setGroup("garbage");
815         try {
816             mNotificationManager.createNotificationChannel(channel);
817             fail("Created notification with bad group");
818         } catch (IllegalArgumentException e) {
819         }
820     }
821 
822     @Test
testCreateChannelInvalidImportance()823     public void testCreateChannelInvalidImportance() throws Exception {
824         NotificationChannel channel =
825                 new NotificationChannel(mId, "name", IMPORTANCE_UNSPECIFIED);
826         try {
827             mNotificationManager.createNotificationChannel(channel);
828         } catch (IllegalArgumentException e) {
829             //success
830         }
831     }
832 
833     @Test
testDeleteChannel()834     public void testDeleteChannel() throws Exception {
835         NotificationChannel channel =
836                 new NotificationChannel(mId, "name", IMPORTANCE_LOW);
837         mNotificationManager.createNotificationChannel(channel);
838         compareChannels(channel, mNotificationManager.getNotificationChannel(channel.getId()));
839         mNotificationManager.deleteNotificationChannel(channel.getId());
840         assertNull(mNotificationManager.getNotificationChannel(channel.getId()));
841     }
842 
843     @Test
testCannotDeleteDefaultChannel()844     public void testCannotDeleteDefaultChannel() throws Exception {
845         try {
846             mNotificationManager.deleteNotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID);
847             fail("Deleted default channel");
848         } catch (IllegalArgumentException e) {
849             //success
850         }
851     }
852 
853     @Test
testGetChannel()854     public void testGetChannel() throws Exception {
855         NotificationChannel channel1 =
856                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
857         NotificationChannel channel2 =
858                 new NotificationChannel(
859                         UUID.randomUUID().toString(), "name2", IMPORTANCE_HIGH);
860         NotificationChannel channel3 =
861                 new NotificationChannel(
862                         UUID.randomUUID().toString(), "name3", IMPORTANCE_LOW);
863         NotificationChannel channel4 =
864                 new NotificationChannel(
865                         UUID.randomUUID().toString(), "name4", IMPORTANCE_MIN);
866         mNotificationManager.createNotificationChannel(channel1);
867         mNotificationManager.createNotificationChannel(channel2);
868         mNotificationManager.createNotificationChannel(channel3);
869         mNotificationManager.createNotificationChannel(channel4);
870 
871         compareChannels(channel2,
872                 mNotificationManager.getNotificationChannel(channel2.getId()));
873         compareChannels(channel3,
874                 mNotificationManager.getNotificationChannel(channel3.getId()));
875         compareChannels(channel1,
876                 mNotificationManager.getNotificationChannel(channel1.getId()));
877         compareChannels(channel4,
878                 mNotificationManager.getNotificationChannel(channel4.getId()));
879     }
880 
881     @Test
testGetChannels()882     public void testGetChannels() throws Exception {
883         NotificationChannel channel1 =
884                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
885         NotificationChannel channel2 =
886                 new NotificationChannel(
887                         UUID.randomUUID().toString(), "name2", IMPORTANCE_HIGH);
888         NotificationChannel channel3 =
889                 new NotificationChannel(
890                         UUID.randomUUID().toString(), "name3", IMPORTANCE_LOW);
891         NotificationChannel channel4 =
892                 new NotificationChannel(
893                         UUID.randomUUID().toString(), "name4", IMPORTANCE_MIN);
894 
895         Map<String, NotificationChannel> channelMap = new HashMap<>();
896         channelMap.put(channel1.getId(), channel1);
897         channelMap.put(channel2.getId(), channel2);
898         channelMap.put(channel3.getId(), channel3);
899         channelMap.put(channel4.getId(), channel4);
900         channelMap.put(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL);
901         mNotificationManager.createNotificationChannel(channel1);
902         mNotificationManager.createNotificationChannel(channel2);
903         mNotificationManager.createNotificationChannel(channel3);
904         mNotificationManager.createNotificationChannel(channel4);
905 
906         mNotificationManager.deleteNotificationChannel(channel3.getId());
907 
908         List<NotificationChannel> channels = mNotificationManager.getNotificationChannels();
909         for (NotificationChannel nc : channels) {
910             assertFalse(channel3.getId().equals(nc.getId()));
911             if (!channelMap.containsKey(nc.getId())) {
912                 fail("Found extra channel " + nc.getId());
913             }
914             compareChannels(channelMap.get(nc.getId()), nc);
915         }
916 
917         // 1 channel from setUp() (NOTIFICATION_CHANNEL_ID) + 3 randomUUID channels from this
918         // test
919         assertEquals(4, channels.size());
920     }
921 
922     @Test
testGetChannel_updatesWithChannelData()923     public void testGetChannel_updatesWithChannelData() throws Exception {
924         NotificationChannel channel =
925                 new NotificationChannel(UUID.randomUUID().toString(), "name", IMPORTANCE_DEFAULT);
926 
927         // Not yet created
928         assertThat(mNotificationManager.getNotificationChannel(channel.getId())).isNull();
929 
930         // After it's created, we should get an equivalent channel back
931         mNotificationManager.createNotificationChannel(channel);
932         compareChannels(channel, mNotificationManager.getNotificationChannel(channel.getId()));
933 
934         // Update channel information
935         channel.setShowBadge(false);
936         channel.setDescription("new description");
937         SystemUtil.runWithShellPermissionIdentity(
938                 () -> {
939                     mNotificationManager.updateNotificationChannel(
940                             mContext.getPackageName(), android.os.Process.myUid(), channel);
941                 });
942 
943         // now the value should match the updated channel
944         compareChannels(channel, mNotificationManager.getNotificationChannel(channel.getId()));
945     }
946 
947     @Test
testGetChannel_updatesWhenChangedToAndFromImportantConversation()948     public void testGetChannel_updatesWhenChangedToAndFromImportantConversation() {
949         NotificationChannel parent =
950                 new NotificationChannel(UUID.randomUUID().toString(), "parent", IMPORTANCE_DEFAULT);
951         NotificationChannel channel =
952                 new NotificationChannel(UUID.randomUUID().toString(), "name", IMPORTANCE_DEFAULT);
953         channel.setConversationId(parent.getId(), UUID.randomUUID().toString());
954 
955         // Not yet created
956         assertThat(mNotificationManager.getNotificationChannel(channel.getId())).isNull();
957 
958         // After it's created, we should get an equivalent channel back
959         mNotificationManager.createNotificationChannel(parent);
960         mNotificationManager.createNotificationChannel(channel);
961         compareChannels(channel, mNotificationManager.getNotificationChannel(channel.getId()));
962 
963         // Update channel to be an important conversation
964         channel.setImportantConversation(true);
965         SystemUtil.runWithShellPermissionIdentity(
966                 () -> {
967                     mNotificationManager.updateNotificationChannel(
968                             mContext.getPackageName(), android.os.Process.myUid(), channel);
969                 });
970 
971         // Expect that it matches the updated channel returned by looking for its conversation info
972         compareChannels(
973                 channel,
974                 mNotificationManager.getNotificationChannel(
975                         parent.getId(), channel.getConversationId()));
976 
977         // Demote the channel. Confirm that getNotificationChannel reflects that change as well.
978         channel.setDemoted(true);
979         SystemUtil.runWithShellPermissionIdentity(
980                 () -> {
981                     mNotificationManager.updateNotificationChannel(
982                             mContext.getPackageName(), android.os.Process.myUid(), channel);
983                 });
984         compareChannels(
985                 channel,
986                 mNotificationManager.getNotificationChannel(
987                         parent.getId(), channel.getConversationId()));
988     }
989 
990     @Test
testRecreateDeletedChannel()991     public void testRecreateDeletedChannel() throws Exception {
992         NotificationChannel channel =
993                 new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
994         channel.setShowBadge(true);
995         NotificationChannel newChannel = new NotificationChannel(
996                 channel.getId(), channel.getName(), IMPORTANCE_HIGH);
997         mNotificationManager.createNotificationChannel(channel);
998         mNotificationManager.deleteNotificationChannel(channel.getId());
999 
1000         mNotificationManager.createNotificationChannel(newChannel);
1001 
1002         compareChannels(channel,
1003                 mNotificationManager.getNotificationChannel(newChannel.getId()));
1004     }
1005 
1006     @Test
testNotify()1007     public void testNotify() throws Exception {
1008         mNotificationManager.cancelAll();
1009 
1010         final int id = 1;
1011         sendNotification(id, R.drawable.black);
1012         // test updating the same notification
1013         sendNotification(id, R.drawable.blue);
1014         sendNotification(id, R.drawable.yellow);
1015 
1016         // assume that sendNotification tested to make sure individual notifications were present
1017         StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
1018         for (StatusBarNotification sbn : sbns) {
1019             if (sbn.getId() != id) {
1020                 fail("we got back other notifications besides the one we posted: "
1021                         + sbn.getKey());
1022             }
1023         }
1024     }
1025 
1026     @Test
testNotify_nonexistentChannel_ignored()1027     public void testNotify_nonexistentChannel_ignored() {
1028         mNotificationManager.cancelAll();
1029         final int id = 404;
1030         final Notification notification =
1031                 new Notification.Builder(mContext, "non_existent_channel")
1032                         .setSmallIcon(R.drawable.black)
1033                         .setContentText("This should not be posted!")
1034                         .build();
1035 
1036         mNotificationManager.notify(id, notification);
1037 
1038         assertThat(mNotificationHelper.findPostedNotification(null, id, SEARCH_TYPE.APP)).isNull();
1039         assertThat(mNotificationManager.getActiveNotifications()).isEmpty();
1040     }
1041 
1042     @Test
testSuspendPackage_withoutShellPermission()1043     public void testSuspendPackage_withoutShellPermission() throws Exception {
1044         if (mActivityManager.isLowRamDevice() && !mPackageManager.hasSystemFeature(FEATURE_WATCH)) {
1045 
1046             return;
1047         }
1048 
1049         Process proc = Runtime.getRuntime().exec("cmd notification suspend_package "
1050                 + mContext.getPackageName());
1051 
1052         // read output of command
1053         BufferedReader reader =
1054                 new BufferedReader(new InputStreamReader(proc.getInputStream()));
1055         StringBuilder output = new StringBuilder();
1056         String line = reader.readLine();
1057         while (line != null) {
1058             output.append(line);
1059             line = reader.readLine();
1060         }
1061         reader.close();
1062         final String outputString = output.toString();
1063 
1064         proc.waitFor();
1065 
1066         // check that the output string had an error / disallowed call since it didn't have
1067         // shell permission to suspend the package
1068         assertTrue(outputString, outputString.contains("error"));
1069         assertTrue(outputString, outputString.contains("permission denied"));
1070     }
1071 
1072     @Test
1073     // TODO(b/355106764): Remove the annotation once suspend package supports visible background
1074     //  users.
1075     @RequireRunNotOnVisibleBackgroundNonProfileUser(reason = "Suspending package does not support"
1076             + " visible background users at the moment")
testSuspendPackage()1077     public void testSuspendPackage() throws Exception {
1078         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
1079         assertNotNull(mListener);
1080 
1081         CountDownLatch notificationPostedLatch = mListener.setPostedCountDown(1);
1082         sendNotification(1, R.drawable.black);
1083         // wait for notification listener to receive notification
1084         notificationPostedLatch.await(500, TimeUnit.MILLISECONDS);
1085         assertEquals(1, mListener.mPosted.size());
1086 
1087         CountDownLatch notificationRankingLatch = mListener.setRankingUpdateCountDown(1);
1088         // suspend package, ranking should be updated with suspended = true
1089         suspendPackage(mContext.getPackageName(), InstrumentationRegistry.getInstrumentation(),
1090                 true);
1091         // wait for notification listener to get response
1092         notificationRankingLatch.await(500, TimeUnit.MILLISECONDS);
1093         NotificationListenerService.RankingMap rankingMap = mListener.mRankingMap;
1094         NotificationListenerService.Ranking outRanking = new NotificationListenerService.Ranking();
1095         for (String key : rankingMap.getOrderedKeys()) {
1096             if (key.contains(mListener.getPackageName())) {
1097                 rankingMap.getRanking(key, outRanking);
1098                 Log.d(TAG, "key=" + key + " suspended=" + outRanking.isSuspended());
1099                 assertTrue(outRanking.isSuspended());
1100             }
1101         }
1102 
1103         notificationRankingLatch = mListener.setRankingUpdateCountDown(1);
1104         // unsuspend package, ranking should be updated with suspended = false
1105         suspendPackage(mContext.getPackageName(), InstrumentationRegistry.getInstrumentation(),
1106                 false);
1107         // wait for notification listener to get response
1108         notificationRankingLatch.await(500, TimeUnit.MILLISECONDS);
1109         rankingMap = mListener.mRankingMap;
1110         for (String key : rankingMap.getOrderedKeys()) {
1111             if (key.contains(mListener.getPackageName())) {
1112                 rankingMap.getRanking(key, outRanking);
1113                 Log.d(TAG, "key=" + key + " suspended=" + outRanking.isSuspended());
1114                 assertFalse(outRanking.isSuspended());
1115             }
1116         }
1117 
1118         mListener.resetData();
1119     }
1120 
1121     /**
1122      * Binds a service connection to the provided component, then sleeps before returning to ensure
1123      * the connection has time to be initialized on low ram devices.
1124      */
bindServiceConnection(ComponentName component)1125     public FutureServiceConnection bindServiceConnection(ComponentName component) throws Exception {
1126         Intent intent = new Intent();
1127         intent.setComponent(component);
1128         FutureServiceConnection service_connection = new FutureServiceConnection();
1129         assertTrue(mContext.bindService(intent, service_connection, Context.BIND_AUTO_CREATE));
1130         // We add sleep to give low ram devices a chance to bind; in the case that
1131         // bindServiceConnection is called multiple times, attempting too many connections all at
1132         // once can increase the time it takes for the service connections to bind, leading to
1133         // timeouts.
1134         Thread.sleep(4000);
1135         return service_connection;
1136     }
1137 
1138     /**
1139      * Sends a message with the specified notificationId to the provided serviceConnection.
1140      */
sendMessage(FutureServiceConnection serviceConnection, int notificationId, Handler callback)1141     public void sendMessage(FutureServiceConnection serviceConnection,
1142             int notificationId, Handler callback) throws Exception {
1143         Messenger service = new Messenger(serviceConnection.get(40000));
1144         service.send(Message.obtain(null, MESSAGE_SERVICE_NOTIFICATION, notificationId, -1,
1145                 new Messenger(callback)));
1146     }
1147 
1148     /**
1149      * Sends a cancellation message for the specified notificationId to the serviceConnection.
1150      */
sendCancelAll(FutureServiceConnection serviceConnection, Handler callback)1151     public void sendCancelAll(FutureServiceConnection serviceConnection,
1152                            Handler callback) throws Exception {
1153         Messenger service = new Messenger(serviceConnection.get(40000));
1154         service.send(Message.obtain(null, MESSAGE_CANCEL_ALL_NOTIFICATIONS, -1, -1,
1155                 new Messenger(callback)));
1156     }
1157 
1158     /**
1159      *  Function to identify if the device is a cuttlefish instance.
1160      *  The testRankingUpdateSentWithPressure CTS test verifies device behavior that requires large
1161      *  numbers of notifications sent in a short period of time, which non-production devices like
1162      *  cuttlefish cannot support.
1163      */
onCuttlefish()1164     public static boolean onCuttlefish() throws IOException {
1165         String device = SystemProperties.get("ro.product.device", "");
1166         String model = SystemProperties.get("ro.product.model", "");
1167         String name = SystemProperties.get("ro.product.name", "");
1168 
1169         // Return true for cuttlefish instances
1170         if (!device.startsWith("vsoc_")) {
1171             return false;
1172         }
1173         if (!model.startsWith("Cuttlefish ")) {
1174             return false;
1175         }
1176         if (name.startsWith("cf_") || name.startsWith("aosp_cf_")) {
1177             return true;
1178         }
1179         return false;
1180     }
1181 
1182     /**
1183      * Tests that, given a significant amount of Notification pressure from 400 notifications
1184      * posted in rapid succession, NotificationListeners don't experience binder errors.
1185      * Timing in this test is tuned to reduce flakiness while avoiding timeouts. Posting a
1186      * notification requires approximately 100 ms, so 500 notifications is expected to take about
1187      * 50 seconds. Tests will time out after approximately 60 seconds, so binding needs to happen
1188      * in under 10 seconds.
1189      */
1190     @LargeTest
1191     @RequiresDevice
1192     @Test
testRankingUpdateSentWithPressure()1193     public void testRankingUpdateSentWithPressure() throws Exception {
1194         assumeFalse("NotificationListeners do not support visible background users",
1195                 mUserHelper.isVisibleBackgroundUser());
1196         // Test should only be run for build in V
1197         if (!SdkLevel.isAtLeastV()) {
1198             return;
1199         }
1200 
1201         if (onCuttlefish()) {
1202             return;
1203         }
1204 
1205         // TODO(b/307582301): Convert this to assume once we support Junit4
1206         if (!Flags.rankingUpdateAshmem()) {
1207             return;
1208         }
1209 
1210         int notificationsPerApp = 40;
1211         int totalNotificationsSent = notificationsPerApp * 8; // 8 apps total
1212 
1213         FutureServiceConnection pressureService00 = bindServiceConnection(PRESSURE_SERVICE_00);
1214         FutureServiceConnection pressureService01 = bindServiceConnection(PRESSURE_SERVICE_01);
1215         FutureServiceConnection pressureService02 = bindServiceConnection(PRESSURE_SERVICE_02);
1216         FutureServiceConnection pressureService03 = bindServiceConnection(PRESSURE_SERVICE_03);
1217         FutureServiceConnection pressureService04 = bindServiceConnection(PRESSURE_SERVICE_04);
1218         FutureServiceConnection pressureService05 = bindServiceConnection(PRESSURE_SERVICE_05);
1219         FutureServiceConnection pressureService06 = bindServiceConnection(PRESSURE_SERVICE_06);
1220         FutureServiceConnection pressureService07 = bindServiceConnection(PRESSURE_SERVICE_07);
1221 
1222 
1223         deactivateGracePeriod();
1224         setUpNotifListener();
1225         CountDownLatch notificationPostedLatch =
1226                 mListener.setPostedCountDown(totalNotificationsSent);
1227         CountDownLatch notificationRankingUpdateLatch =
1228                 mListener.setRankingUpdateCountDown(totalNotificationsSent);
1229         CountDownLatch notificationRemovedLatch =
1230                 mListener.setRemovedCountDown(totalNotificationsSent);
1231 
1232         mListener.addTestPackage(PRESSURE_APP_00);
1233         mListener.addTestPackage(PRESSURE_APP_01);
1234         mListener.addTestPackage(PRESSURE_APP_02);
1235         mListener.addTestPackage(PRESSURE_APP_03);
1236         mListener.addTestPackage(PRESSURE_APP_04);
1237         mListener.addTestPackage(PRESSURE_APP_05);
1238         mListener.addTestPackage(PRESSURE_APP_06);
1239         mListener.addTestPackage(PRESSURE_APP_07);
1240 
1241         // For each app, sends notificationsPerApp notifications each, and ensure that
1242         // none are dropped.
1243         EventCallback callback = new EventCallback();
1244         int notificationId = 6500;
1245         boolean notificationsReceived = false;
1246         boolean notificationRankingUpdates = false;
1247         boolean notificationsRemoved = false;
1248         for (int i = 0; i < notificationsPerApp; i++) {
1249             sendMessage(pressureService00, notificationId++, callback);
1250             sendMessage(pressureService01, notificationId++, callback);
1251             sendMessage(pressureService02, notificationId++, callback);
1252             sendMessage(pressureService03, notificationId++, callback);
1253             sendMessage(pressureService04, notificationId++, callback);
1254             sendMessage(pressureService05, notificationId++, callback);
1255             sendMessage(pressureService06, notificationId++, callback);
1256             sendMessage(pressureService07, notificationId++, callback);
1257         }
1258 
1259         try {
1260             notificationsReceived = notificationPostedLatch.await(50000, TimeUnit.MILLISECONDS);
1261             notificationRankingUpdates = notificationRankingUpdateLatch.await(5000,
1262                     TimeUnit.MILLISECONDS);
1263         } catch (InterruptedException e) {
1264             Log.d(TAG, e.toString());
1265             fail("Interrupted before notifications received or ranking updates received.");
1266         }
1267 
1268         // For each app, send a message to cancel all the current notifications.
1269         sendCancelAll(pressureService00, callback);
1270         sendCancelAll(pressureService01, callback);
1271         sendCancelAll(pressureService02, callback);
1272         sendCancelAll(pressureService03, callback);
1273         sendCancelAll(pressureService04, callback);
1274         sendCancelAll(pressureService05, callback);
1275         sendCancelAll(pressureService06, callback);
1276         sendCancelAll(pressureService07, callback);
1277 
1278         // Ensure all notifications get removed.
1279         try {
1280             notificationsRemoved = notificationRemovedLatch.await(50000, TimeUnit.MILLISECONDS);
1281         } catch (InterruptedException e) {
1282             Log.d(TAG, e.toString());
1283             fail("Interrupted before all notifications removed.");
1284         }
1285 
1286         assertTrue(notificationsReceived);
1287         assertTrue(notificationRankingUpdates);
1288         assertTrue(notificationsRemoved);
1289 
1290         // Clean up. We add sleep to give low ram devices a chance to unbind the service
1291         // successfully without overwhelming the device.
1292         if (pressureService00 != null) {
1293             mContext.unbindService(pressureService00);
1294             pressureService00 = null;
1295             Thread.sleep(900);
1296         }
1297         if (pressureService01 != null) {
1298             mContext.unbindService(pressureService01);
1299             Thread.sleep(900);
1300         }
1301         if (pressureService02 != null) {
1302             mContext.unbindService(pressureService02);
1303             Thread.sleep(900);
1304         }
1305         if (pressureService03 != null) {
1306             mContext.unbindService(pressureService03);
1307             Thread.sleep(900);
1308         }
1309         if (pressureService04 != null) {
1310             mContext.unbindService(pressureService04);
1311             Thread.sleep(900);
1312         }
1313         if (pressureService05 != null) {
1314             mContext.unbindService(pressureService05);
1315             Thread.sleep(900);
1316         }
1317         if (pressureService06 != null) {
1318             mContext.unbindService(pressureService06);
1319             Thread.sleep(900);
1320         }
1321         if (pressureService07 != null) {
1322             mContext.unbindService(pressureService07);
1323             Thread.sleep(900);
1324         }
1325 
1326         if (mListener != null) {
1327             mListener.removeTestPackage(PRESSURE_APP_00);
1328             mListener.removeTestPackage(PRESSURE_APP_01);
1329             mListener.removeTestPackage(PRESSURE_APP_02);
1330             mListener.removeTestPackage(PRESSURE_APP_03);
1331             mListener.removeTestPackage(PRESSURE_APP_04);
1332             mListener.removeTestPackage(PRESSURE_APP_05);
1333             mListener.removeTestPackage(PRESSURE_APP_06);
1334             mListener.removeTestPackage(PRESSURE_APP_07);
1335         }
1336     }
1337 
1338     @Test
1339     // TODO(b/355106764): Remove the annotation once suspend package supports visible background
1340     //  users.
1341     @RequireRunNotOnVisibleBackgroundNonProfileUser(reason = "Suspending package does not support"
1342             + " visible background users at the moment")
testSuspendedPackageSendsNotification()1343     public void testSuspendedPackageSendsNotification() throws Exception {
1344         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
1345         assertNotNull(mListener);
1346         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
1347 
1348         // suspend package, post notification while package is suspended, see notification
1349         // in ranking map with suspended = true
1350         suspendPackage(mContext.getPackageName(), InstrumentationRegistry.getInstrumentation(),
1351                 true);
1352         sendNotification(1, R.drawable.black);
1353         // wait for notification listener to receive notification
1354         postedLatch.await(500, TimeUnit.MILLISECONDS);
1355         assertEquals(1, mListener.mPosted.size()); // apps targeting P receive notification
1356         NotificationListenerService.RankingMap rankingMap = mListener.mRankingMap;
1357         NotificationListenerService.Ranking outRanking = new NotificationListenerService.Ranking();
1358         for (String key : rankingMap.getOrderedKeys()) {
1359             if (key.contains(mListener.getPackageName())) {
1360                 rankingMap.getRanking(key, outRanking);
1361                 Log.d(TAG, "key=" + key + " suspended=" + outRanking.isSuspended());
1362                 assertTrue(outRanking.isSuspended());
1363             }
1364         }
1365 
1366         // unsuspend package, ranking should be updated with suspended = false
1367         suspendPackage(mContext.getPackageName(), InstrumentationRegistry.getInstrumentation(),
1368                 false);
1369         Thread.sleep(500); // wait for notification listener to get response
1370         assertEquals(1, mListener.mPosted.size()); // should see previously posted notification
1371         rankingMap = mListener.mRankingMap;
1372         for (String key : rankingMap.getOrderedKeys()) {
1373             if (key.contains(mListener.getPackageName())) {
1374                 rankingMap.getRanking(key, outRanking);
1375                 Log.d(TAG, "key=" + key + " suspended=" + outRanking.isSuspended());
1376                 assertFalse(outRanking.isSuspended());
1377             }
1378         }
1379 
1380         mListener.resetData();
1381     }
1382 
1383     @Test
testShowBadging_ranking()1384     public void testShowBadging_ranking() throws Exception {
1385         assumeFalse("NotificationListeners do not support visible background users",
1386                 mUserHelper.isVisibleBackgroundUser());
1387         final int originalBadging = Settings.Secure.getInt(
1388                 mContext.getContentResolver(), Settings.Secure.NOTIFICATION_BADGING);
1389 
1390         SystemUtil.runWithShellPermissionIdentity(() ->
1391                 Settings.Secure.putInt(mContext.getContentResolver(),
1392                         Settings.Secure.NOTIFICATION_BADGING, 1));
1393         assertEquals(1, Settings.Secure.getInt(
1394                 mContext.getContentResolver(), Settings.Secure.NOTIFICATION_BADGING));
1395 
1396         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
1397         assertNotNull(mListener);
1398         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
1399         try {
1400             sendNotification(1, R.drawable.black);
1401             // wait for notification listener to receive notification
1402             postedLatch.await(500, TimeUnit.MILLISECONDS);
1403             NotificationListenerService.RankingMap rankingMap = mListener.mRankingMap;
1404             NotificationListenerService.Ranking outRanking =
1405                     new NotificationListenerService.Ranking();
1406             for (String key : rankingMap.getOrderedKeys()) {
1407                 if (key.contains(mListener.getPackageName())) {
1408                     rankingMap.getRanking(key, outRanking);
1409                     assertTrue(outRanking.canShowBadge());
1410                 }
1411             }
1412 
1413             CountDownLatch rankingUpdateLatch = mListener.setRankingUpdateCountDown(1);
1414 
1415             // turn off badging globally
1416             SystemUtil.runWithShellPermissionIdentity(() ->
1417                     Settings.Secure.putInt(mContext.getContentResolver(),
1418                             Settings.Secure.NOTIFICATION_BADGING, 0));
1419 
1420             // wait for ranking update
1421             rankingUpdateLatch.await(500, TimeUnit.MILLISECONDS);
1422 
1423             rankingMap = mListener.mRankingMap;
1424             outRanking = new NotificationListenerService.Ranking();
1425             for (String key : rankingMap.getOrderedKeys()) {
1426                 if (key.contains(mListener.getPackageName())) {
1427                     assertFalse(outRanking.canShowBadge());
1428                 }
1429             }
1430 
1431             mListener.resetData();
1432         } finally {
1433             SystemUtil.runWithShellPermissionIdentity(() ->
1434                     Settings.Secure.putInt(mContext.getContentResolver(),
1435                             Settings.Secure.NOTIFICATION_BADGING, originalBadging));
1436         }
1437     }
1438 
1439     @Test
testKeyChannelGroupOverrideImportanceExplanation_ranking()1440     public void testKeyChannelGroupOverrideImportanceExplanation_ranking() throws Exception {
1441         assumeFalse("NotificationListeners do not support visible background users",
1442                 mUserHelper.isVisibleBackgroundUser());
1443         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
1444         assertNotNull(mListener);
1445 
1446         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
1447 
1448         final int notificationId = 1;
1449         sendNotification(notificationId, R.drawable.black);
1450         // wait for notification listener to receive notification
1451         postedLatch.await(500, TimeUnit.MILLISECONDS);
1452 
1453         NotificationListenerService.RankingMap rankingMap = mListener.mRankingMap;
1454         NotificationListenerService.Ranking outRanking =
1455                 new NotificationListenerService.Ranking();
1456 
1457         StatusBarNotification sbn = mNotificationHelper.findPostedNotification(null, notificationId,
1458                 SEARCH_TYPE.POSTED);
1459 
1460         // check that the key and channel ids are the same in the ranking as the posted notification
1461         for (String key : rankingMap.getOrderedKeys()) {
1462             if (key.contains(mListener.getPackageName())) {
1463                 rankingMap.getRanking(key, outRanking);
1464 
1465                 // check notification key match
1466                 assertEquals(sbn.getKey(), outRanking.getKey());
1467 
1468                 // check notification channel ids match
1469                 assertEquals(sbn.getNotification().getChannelId(), outRanking.getChannel().getId());
1470 
1471                 // check override group key match
1472                 assertEquals(sbn.getOverrideGroupKey(), outRanking.getOverrideGroupKey());
1473 
1474                 // check importance explanation isn't null
1475                 assertNotNull(outRanking.getImportanceExplanation());
1476             }
1477         }
1478     }
1479 
1480     @Test
testNotify_blockedChannel()1481     public void testNotify_blockedChannel() throws Exception {
1482         mNotificationManager.cancelAll();
1483 
1484         NotificationChannel channel =
1485                 new NotificationChannel(mId, "name", IMPORTANCE_NONE);
1486         mNotificationManager.createNotificationChannel(channel);
1487 
1488         int id = 1;
1489         final Notification notification =
1490                 new Notification.Builder(mContext, mId)
1491                         .setSmallIcon(R.drawable.black)
1492                         .setWhen(System.currentTimeMillis())
1493                         .setContentTitle("notify#" + id)
1494                         .setContentText("This is #" + id + "notification  ")
1495                         .build();
1496         mNotificationManager.notify(id, notification);
1497 
1498         assertTrue(mNotificationHelper.isNotificationGone(id, SEARCH_TYPE.APP));
1499     }
1500 
1501     @Test
testCancel()1502     public void testCancel() throws Exception {
1503         final int id = 9;
1504         sendNotification(id, R.drawable.black);
1505         // Wait for the notification posted not just enqueued
1506         try {
1507             Thread.sleep(500);
1508         } catch (InterruptedException e) {
1509         }
1510         mNotificationManager.cancel(id);
1511 
1512         assertTrue(mNotificationHelper.isNotificationGone(id, SEARCH_TYPE.APP));
1513     }
1514 
1515     @Test
testCancelAll()1516     public void testCancelAll() throws Exception {
1517         sendNotification(1, R.drawable.black);
1518         sendNotification(2, R.drawable.blue);
1519         sendNotification(3, R.drawable.yellow);
1520 
1521         if (DEBUG) {
1522             Log.d(TAG, "posted 3 notifications, here they are: ");
1523             StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
1524             for (StatusBarNotification sbn : sbns) {
1525                 Log.d(TAG, "  " + sbn);
1526             }
1527             Log.d(TAG, "about to cancel...");
1528         }
1529         mNotificationManager.cancelAll();
1530 
1531         for (int id = 1; id <= 3; id++) {
1532             assertTrue(mNotificationHelper.isNotificationGone(id, SEARCH_TYPE.APP));
1533         }
1534 
1535     }
1536 
1537     @Test
testNotifyWithTimeout()1538     public void testNotifyWithTimeout() throws Exception {
1539         mNotificationManager.cancelAll();
1540         final int id = 128;
1541         final long timeout = 1000;
1542 
1543         final Notification notification =
1544                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
1545                         .setSmallIcon(R.drawable.black)
1546                         .setContentTitle("notify#" + id)
1547                         .setContentText("This is #" + id + "notification  ")
1548                         .setTimeoutAfter(timeout)
1549                         .build();
1550         mNotificationManager.notify(id, notification);
1551 
1552         assertNotNull(mNotificationHelper.findPostedNotification(null, id, SEARCH_TYPE.APP));
1553 
1554         try {
1555             Thread.sleep(timeout);
1556         } catch (InterruptedException ex) {
1557             // pass
1558         }
1559         assertTrue(mNotificationHelper.isNotificationGone(id, SEARCH_TYPE.APP));
1560     }
1561 
1562     @Test
testStyle()1563     public void testStyle() throws Exception {
1564         Notification.Style style = new TestStyle();
1565 
1566         Notification.Builder builder = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID);
1567         style.setBuilder(builder);
1568 
1569         Notification notification = null;
1570         try {
1571             notification = style.build();
1572         } catch (IllegalArgumentException e) {
1573             fail(e.getMessage());
1574         }
1575 
1576         assertNotNull(notification);
1577 
1578         Notification builderNotification = builder.build();
1579         assertEquals(builderNotification, notification);
1580     }
1581 
1582     @Test
testStyle_getStandardView()1583     public void testStyle_getStandardView() throws Exception {
1584         Notification.Builder builder = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID);
1585         int layoutId = 0;
1586 
1587         TestStyle overrideStyle = new TestStyle();
1588         overrideStyle.setBuilder(builder);
1589         RemoteViews result = overrideStyle.testGetStandardView(layoutId);
1590 
1591         assertNotNull(result);
1592         assertEquals(layoutId, result.getLayoutId());
1593     }
1594 
1595     private static class TestStyle extends Notification.Style {
1596         @SuppressWarnings("UnusedMethod")
areNotificationsVisiblyDifferent(Notification.Style other)1597         public boolean areNotificationsVisiblyDifferent(Notification.Style other) {
1598             return false;
1599         }
1600 
testGetStandardView(int layoutId)1601         public RemoteViews testGetStandardView(int layoutId) {
1602             // Wrapper method, since getStandardView is protected and otherwise unused in Android
1603             return getStandardView(layoutId);
1604         }
1605     }
1606 
1607     @Test
testMediaStyle_empty()1608     public void testMediaStyle_empty() {
1609         Notification.MediaStyle style = new Notification.MediaStyle();
1610         assertNotNull(style);
1611     }
1612 
1613     @Test
testMediaStyle()1614     public void testMediaStyle() {
1615         mNotificationManager.cancelAll();
1616         final int id = 99;
1617         MediaSession session = new MediaSession(mContext, "media");
1618 
1619         final Notification notification =
1620                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
1621                         .setSmallIcon(R.drawable.black)
1622                         .setContentTitle("notify#" + id)
1623                         .setContentText("This is #" + id + "notification  ")
1624                         .addAction(new Notification.Action.Builder(
1625                                 Icon.createWithResource(mContext, R.drawable.icon_black),
1626                                 "play", getPendingIntent()).build())
1627                         .addAction(new Notification.Action.Builder(
1628                                 Icon.createWithResource(mContext, R.drawable.icon_blue),
1629                                 "pause", getPendingIntent()).build())
1630                         .setStyle(new Notification.MediaStyle()
1631                                 .setShowActionsInCompactView(0, 1)
1632                                 .setMediaSession(session.getSessionToken()))
1633                         .build();
1634         mNotificationManager.notify(id, notification);
1635 
1636         assertNotNull(mNotificationHelper.findPostedNotification(null, id, SEARCH_TYPE.APP));
1637     }
1638 
1639     @Test
testInboxStyle()1640     public void testInboxStyle() {
1641         final int id = 100;
1642 
1643         final Notification notification =
1644                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
1645                         .setSmallIcon(R.drawable.black)
1646                         .setContentTitle("notify#" + id)
1647                         .setContentText("This is #" + id + "notification  ")
1648                         .addAction(new Notification.Action.Builder(
1649                                 Icon.createWithResource(mContext, R.drawable.icon_black),
1650                                 "a1", getPendingIntent()).build())
1651                         .addAction(new Notification.Action.Builder(
1652                                 Icon.createWithResource(mContext, R.drawable.icon_blue),
1653                                 "a2", getPendingIntent()).build())
1654                         .setStyle(new Notification.InboxStyle().addLine("line")
1655                                 .setSummaryText("summary"))
1656                         .build();
1657         mNotificationManager.notify(id, notification);
1658 
1659         assertNotNull(mNotificationHelper.findPostedNotification(null, id, SEARCH_TYPE.APP));
1660     }
1661 
1662     @Test
testBigTextStyle()1663     public void testBigTextStyle() {
1664         final int id = 101;
1665 
1666         final Notification notification =
1667                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
1668                         .setSmallIcon(R.drawable.black)
1669                         .setContentTitle("notify#" + id)
1670                         .setContentText("This is #" + id + "notification  ")
1671                         .addAction(new Notification.Action.Builder(
1672                                 Icon.createWithResource(mContext, R.drawable.icon_black),
1673                                 "a1", getPendingIntent()).build())
1674                         .addAction(new Notification.Action.Builder(
1675                                 Icon.createWithResource(mContext, R.drawable.icon_blue),
1676                                 "a2", getPendingIntent()).build())
1677                         .setStyle(new Notification.BigTextStyle()
1678                                 .setBigContentTitle("big title")
1679                                 .bigText("big text")
1680                                 .setSummaryText("summary"))
1681                         .build();
1682         mNotificationManager.notify(id, notification);
1683 
1684         assertNotNull(mNotificationHelper.findPostedNotification(null, id, SEARCH_TYPE.APP));
1685     }
1686 
1687     @Test
testBigPictureStyle()1688     public void testBigPictureStyle() {
1689         final int id = 102;
1690 
1691         final Notification notification =
1692                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
1693                         .setSmallIcon(R.drawable.black)
1694                         .setContentTitle("notify#" + id)
1695                         .setContentText("This is #" + id + "notification  ")
1696                         .addAction(new Notification.Action.Builder(
1697                                 Icon.createWithResource(mContext, R.drawable.icon_black),
1698                                 "a1", getPendingIntent()).build())
1699                         .addAction(new Notification.Action.Builder(
1700                                 Icon.createWithResource(mContext, R.drawable.icon_blue),
1701                                 "a2", getPendingIntent()).build())
1702                         .setStyle(new Notification.BigPictureStyle()
1703                                 .setBigContentTitle("title")
1704                                 .bigPicture(Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565))
1705                                 .bigLargeIcon(
1706                                         Icon.createWithResource(mContext, R.drawable.icon_blue))
1707                                 .setSummaryText("summary")
1708                                 .setContentDescription("content description"))
1709                         .build();
1710         mNotificationManager.notify(id, notification);
1711 
1712         assertNotNull(mNotificationHelper.findPostedNotification(null, id, SEARCH_TYPE.APP));
1713     }
1714 
1715     @Test
testAutogrouping()1716     public void testAutogrouping() throws Exception {
1717         assumeFalse("NotificationListeners do not support visible background users",
1718                 mUserHelper.isVisibleBackgroundUser());
1719         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
1720         assertNotNull(mListener);
1721         CountDownLatch rerankLatch = mListener.setRankingUpdateCountDown(5);
1722         CountDownLatch postingLatch = mListener.setPostedCountDown(5);
1723 
1724         sendNotification(801, R.drawable.black);
1725         sendNotification(802, R.drawable.blue);
1726         sendNotification(803, R.drawable.yellow);
1727         sendNotification(804, R.drawable.yellow);
1728 
1729         // Wait until all the notifications, including the autogroup, are posted and grouped.
1730         postingLatch.await(400, TimeUnit.MILLISECONDS);
1731         rerankLatch.await(400, TimeUnit.MILLISECONDS);
1732         assertNotificationCount(5);
1733         assertAllPostedNotificationsAutogrouped();
1734     }
1735 
testAutogrouping_autogroupStaysUntilAllNotificationsCanceled_common( final int numExpectedUpdates)1736     private void testAutogrouping_autogroupStaysUntilAllNotificationsCanceled_common(
1737             final int numExpectedUpdates) throws Exception {
1738         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
1739         assertNotNull(mListener);
1740         CountDownLatch rerankLatch = mListener.setRankingUpdateCountDown(5);
1741         CountDownLatch postingLatch = mListener.setRankingUpdateCountDown(5);
1742 
1743         sendNotification(701, R.drawable.black);
1744         sendNotification(702, R.drawable.blue);
1745         sendNotification(703, R.drawable.yellow);
1746         sendNotification(704, R.drawable.yellow);
1747 
1748         // Wait until all the notifications, including the autogroup, are posted and grouped.
1749         rerankLatch.await(400, TimeUnit.MILLISECONDS);
1750         postingLatch.await(400, TimeUnit.MILLISECONDS);
1751         assertNotificationCount(5);
1752         assertAllPostedNotificationsAutogrouped();
1753 
1754         // Assert all notis stay in the same autogroup until all children are canceled
1755         CountDownLatch removedLatch;
1756         for (int i = 704; i > 701; i--) {
1757             rerankLatch = mListener.setRankingUpdateCountDown(numExpectedUpdates);
1758             removedLatch = mListener.setRemovedCountDown(numExpectedUpdates);
1759 
1760             cancelAndPoll(i);
1761 
1762             removedLatch.await(400, TimeUnit.MILLISECONDS);
1763             rerankLatch.await(400, TimeUnit.MILLISECONDS);
1764             assertNotificationCount(i - 700);
1765             assertAllPostedNotificationsAutogrouped();
1766         }
1767         removedLatch = mListener.setRankingUpdateCountDown(1);
1768         rerankLatch = mListener.setRankingUpdateCountDown(1);
1769         cancelAndPoll(701);
1770         rerankLatch.await(400, TimeUnit.MILLISECONDS);
1771         assertNotificationCount(0);
1772     }
1773 
1774     @Test
1775     @RequiresFlagsDisabled(com.android.server.notification.Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
testAutogrouping_autogroupStaysUntilAllNotificationsCanceled()1776     public void testAutogrouping_autogroupStaysUntilAllNotificationsCanceled() throws Exception {
1777         testAutogrouping_autogroupStaysUntilAllNotificationsCanceled_common(1);
1778     }
1779 
1780     @Test
1781     @RequiresFlagsEnabled(com.android.server.notification.Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
testAutogrouping_autogroupStaysUntilAllNotificationsCanceled_summaryUpdated()1782     public void testAutogrouping_autogroupStaysUntilAllNotificationsCanceled_summaryUpdated()
1783             throws Exception {
1784         assumeFalse("NotificationListeners do not support visible background users",
1785                 mUserHelper.isVisibleBackgroundUser());
1786         testAutogrouping_autogroupStaysUntilAllNotificationsCanceled_common(2);
1787     }
1788 
testAutogrouping_autogroupStaysUntilAllNotificationsAddedToGroup_common( final int numExpectedUpdates)1789     private void testAutogrouping_autogroupStaysUntilAllNotificationsAddedToGroup_common(
1790             final int numExpectedUpdates) throws Exception {
1791         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
1792         assertNotNull(mListener);
1793         CountDownLatch rerankLatch = mListener.setRankingUpdateCountDown(5);
1794         CountDownLatch postingLatch = mListener.setPostedCountDown(5);
1795 
1796         String newGroup = "new!";
1797         sendNotification(901, R.drawable.black);
1798         sendNotification(902, R.drawable.blue);
1799         sendNotification(903, R.drawable.yellow);
1800         sendNotification(904, R.drawable.yellow);
1801 
1802         List<Integer> postedIds = new ArrayList<>();
1803         postedIds.add(901);
1804         postedIds.add(902);
1805         postedIds.add(903);
1806         postedIds.add(904);
1807 
1808         // Wait until all the notifications, including the autogroup, are posted and grouped.
1809         postingLatch.await(400, TimeUnit.MILLISECONDS);
1810         rerankLatch.await(400, TimeUnit.MILLISECONDS);
1811         assertNotificationCount(5);
1812         assertAllPostedNotificationsAutogrouped();
1813 
1814         // Assert all notis stay in the same autogroup until all children are canceled
1815         for (int i = 904; i > 901; i--) {
1816             postingLatch = mListener.setPostedCountDown(numExpectedUpdates);
1817             rerankLatch = mListener.setRankingUpdateCountDown(numExpectedUpdates);
1818 
1819             sendNotification(i, newGroup, R.drawable.blue);
1820             postedIds.remove(postedIds.size() - 1);
1821 
1822             postingLatch.await(400, TimeUnit.MILLISECONDS);
1823             rerankLatch.await(400, TimeUnit.MILLISECONDS);
1824             assertNotificationCount(5);
1825             assertOnlySomeNotificationsAutogrouped(postedIds);
1826         }
1827         postingLatch = mListener.setPostedCountDown(1);
1828         rerankLatch = mListener.setRankingUpdateCountDown(1);
1829         sendNotification(901, newGroup, R.drawable.blue);
1830 
1831         postingLatch.await(400, TimeUnit.MILLISECONDS);
1832         rerankLatch.await(400, TimeUnit.MILLISECONDS);
1833         assertNotificationCount(4); // no more autogroup summary
1834         postedIds.remove(0);
1835         assertOnlySomeNotificationsAutogrouped(postedIds);
1836     }
1837 
1838     @Test
1839     @RequiresFlagsDisabled(com.android.server.notification.Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
testAutogrouping_autogroupStaysUntilAllNotificationsAddedToGroup()1840     public void testAutogrouping_autogroupStaysUntilAllNotificationsAddedToGroup()
1841             throws Exception {
1842         testAutogrouping_autogroupStaysUntilAllNotificationsAddedToGroup_common(1);
1843     }
1844 
1845     @Test
1846     @RequiresFlagsEnabled(com.android.server.notification.Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
testAutogrouping_autogroupStaysUntilAllNotificationsAddedToGroup_summaryUpdated()1847     public void testAutogrouping_autogroupStaysUntilAllNotificationsAddedToGroup_summaryUpdated()
1848             throws Exception {
1849         assumeFalse("NotificationListeners do not support visible background users",
1850                 mUserHelper.isVisibleBackgroundUser());
1851         testAutogrouping_autogroupStaysUntilAllNotificationsAddedToGroup_common(2);
1852     }
1853 
testAutogrouping_forceGrouping_common(boolean summaryOnly)1854     private void testAutogrouping_forceGrouping_common(boolean summaryOnly) throws Exception {
1855         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
1856         assertNotNull(mListener);
1857         CountDownLatch postingLatch = mListener.setPostedCountDown(5);
1858         CountDownLatch rerankLatch = mListener.setRankingUpdateCountDown(5);
1859 
1860         String testGroup = "testGroup";
1861         sendNotification(910, testGroup, summaryOnly, R.drawable.black, false, null);
1862         sendNotification(920, testGroup, summaryOnly, R.drawable.blue, false, null);
1863         sendNotification(930, testGroup, summaryOnly, R.drawable.yellow, false, null);
1864         sendNotification(940, testGroup, summaryOnly, R.drawable.yellow, false, null);
1865 
1866         List<Integer> postedIds = new ArrayList<>();
1867         postedIds.add(910);
1868         postedIds.add(920);
1869         postedIds.add(930);
1870         postedIds.add(940);
1871 
1872         // Wait until all the notifications, including the autogroup, are posted and grouped.
1873         postingLatch.await(TIMEOUT_FORCE_REGROUP_MS, TimeUnit.MILLISECONDS);
1874         rerankLatch.await(TIMEOUT_FORCE_REGROUP_MS, TimeUnit.MILLISECONDS);
1875         assertNotificationCount(5);
1876         assertAllPostedNotificationsAutogrouped();
1877 
1878         // Cancel all autogrouped notifications
1879         CountDownLatch removedLatch;
1880         for (int i = postedIds.size() - 1; i >= 0; i--) {
1881             rerankLatch = mListener.setRankingUpdateCountDown(1);
1882             removedLatch = mListener.setRemovedCountDown(1);
1883             int id = postedIds.remove(i);
1884             cancelAndPoll(id);
1885             removedLatch.await(400, TimeUnit.MILLISECONDS);
1886             rerankLatch.await(400, TimeUnit.MILLISECONDS);
1887             assertAllPostedNotificationsAutogrouped();
1888         }
1889         // Autogroup summary should be canceled
1890         postingLatch.await(400, TimeUnit.MILLISECONDS);
1891         rerankLatch.await(400, TimeUnit.MILLISECONDS);
1892         assertNotificationCount(0);
1893     }
1894 
1895     @Test
1896     @RequiresFlagsEnabled(android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING)
testAutogrouping_groupWithoutSummary()1897     public void testAutogrouping_groupWithoutSummary() throws Exception {
1898         assumeFalse("NotificationListeners do not support visible background users",
1899                 mUserHelper.isVisibleBackgroundUser());
1900         // Post notifications with a group name BUT without a summary notification
1901         testAutogrouping_forceGrouping_common(false);
1902     }
1903 
1904     @Test
1905     @RequiresFlagsEnabled(android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING)
testAutogrouping_summaryWithoutChildren()1906     public void testAutogrouping_summaryWithoutChildren() throws Exception {
1907         assumeFalse("NotificationListeners do not support visible background users",
1908                 mUserHelper.isVisibleBackgroundUser());
1909         // Post group summary notifications BUT no group children
1910         testAutogrouping_forceGrouping_common(true);
1911     }
1912 
1913     @Test
1914     @RequiresFlagsEnabled({android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING,
1915             com.android.server.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
testAutogrouping_sparseGroups()1916     public void testAutogrouping_sparseGroups() throws Exception {
1917         assumeFalse("NotificationListeners do not support visible background users",
1918                 mUserHelper.isVisibleBackgroundUser());
1919         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
1920         assertNotNull(mListener);
1921 
1922         //Post multiple groups with a single summary & a single child notification: "sparse groups"
1923         final int numGroups = 7;
1924         final int startId = 900;
1925         final int startSummaryId = 990;
1926         List<Integer> postedIds = new ArrayList<>();
1927         List<Integer> postedSummaryIds = new ArrayList<>();
1928         for (int i = 0; i < numGroups; i++) {
1929             CountDownLatch postingLatch = mListener.setPostedCountDown(2);
1930             CountDownLatch rerankLatch = mListener.setRankingUpdateCountDown(2);
1931             String testGroup = "testGroup " + i;
1932             sendNotification(startId + i, testGroup, false, R.drawable.black, false, null);
1933             sendNotification(startSummaryId + i, testGroup, true, R.drawable.blue, false, null);
1934             postedIds.add(startId + i);
1935             postedSummaryIds.add(startSummaryId + i);
1936             postingLatch.await(400, TimeUnit.MILLISECONDS);
1937             rerankLatch.await(400, TimeUnit.MILLISECONDS);
1938         }
1939 
1940         CountDownLatch removedLatch = mListener.setRemovedCountDown(numGroups);
1941         // Wait until all the notifications, including the autogroup, are posted and grouped.
1942         CountDownLatch postingLatch = mListener.setPostedCountDown(1);
1943         CountDownLatch rerankLatch = mListener.setRankingUpdateCountDown(1);
1944         postingLatch.await(TIMEOUT_FORCE_REGROUP_MS, TimeUnit.MILLISECONDS);
1945         rerankLatch.await(TIMEOUT_FORCE_REGROUP_MS, TimeUnit.MILLISECONDS);
1946         removedLatch.await(TIMEOUT_FORCE_REGROUP_MS, TimeUnit.MILLISECONDS);
1947         // Only child notifications + autogroup summary are left
1948         assertNotificationCount(numGroups + 1);
1949         assertAllPostedNotificationsAutogrouped();
1950         // Check that all posted summaries were removed
1951         for (int summaryId: postedSummaryIds) {
1952             assertThat(mNotificationHelper.isNotificationGone(summaryId, SEARCH_TYPE.APP))
1953                     .isTrue();
1954         }
1955         assertThat(removedLatch.getCount()).isEqualTo(0);
1956 
1957         // Cancel all autogrouped notifications
1958         for (int i = postedIds.size() - 1; i >= 0; i--) {
1959             rerankLatch = mListener.setRankingUpdateCountDown(1);
1960             removedLatch = mListener.setRemovedCountDown(1);
1961             int id = postedIds.remove(i);
1962             cancelAndPoll(id);
1963             removedLatch.await(400, TimeUnit.MILLISECONDS);
1964             rerankLatch.await(400, TimeUnit.MILLISECONDS);
1965             assertAllPostedNotificationsAutogrouped();
1966         }
1967 
1968         // Autogroup summary should be canceled
1969         removedLatch = mListener.setRemovedCountDown(1);
1970         removedLatch.await(400, TimeUnit.MILLISECONDS);
1971         assertNotificationCount(0);
1972     }
1973 
1974     @Test
1975     @RequiresFlagsEnabled({android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING,
1976             com.android.server.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
testAutogrouping_sparseGroups_appCancelsRemovedSummary()1977     public void testAutogrouping_sparseGroups_appCancelsRemovedSummary() throws Exception {
1978         assumeFalse("NotificationListeners do not support visible background users",
1979                 mUserHelper.isVisibleBackgroundUser());
1980         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
1981         assertNotNull(mListener);
1982 
1983         //Post multiple groups with a single summary & a single child notification: "sparse groups"
1984         final int numGroups = 7;
1985         final int startId = 900;
1986         final int startSummaryId = 990;
1987         List<Integer> postedSummaryIds = new ArrayList<>();
1988         for (int i = 0; i < numGroups; i++) {
1989             CountDownLatch postingLatch = mListener.setPostedCountDown(2);
1990             CountDownLatch rerankLatch = mListener.setRankingUpdateCountDown(2);
1991             String testGroup = "testGroup " + i;
1992             sendNotification(startId + i, testGroup, false, R.drawable.black, false, null);
1993             sendNotification(startSummaryId + i, testGroup, true, R.drawable.blue, false, null);
1994             postedSummaryIds.add(startSummaryId + i);
1995             postingLatch.await(400, TimeUnit.MILLISECONDS);
1996             rerankLatch.await(400, TimeUnit.MILLISECONDS);
1997         }
1998 
1999         CountDownLatch removedLatch = mListener.setRemovedCountDown(numGroups);
2000         // Wait until all the notifications, including the autogroup, are posted and grouped.
2001         CountDownLatch postingLatch = mListener.setPostedCountDown(1);
2002         CountDownLatch rerankLatch = mListener.setRankingUpdateCountDown(1);
2003         postingLatch.await(TIMEOUT_FORCE_REGROUP_MS, TimeUnit.MILLISECONDS);
2004         rerankLatch.await(TIMEOUT_FORCE_REGROUP_MS, TimeUnit.MILLISECONDS);
2005         removedLatch.await(TIMEOUT_FORCE_REGROUP_MS, TimeUnit.MILLISECONDS);
2006         // Only child notifications + autogroup summary are left
2007         assertNotificationCount(numGroups + 1);
2008         assertAllPostedNotificationsAutogrouped();
2009         // Check that all posted summaries were removed
2010         for (int summaryId: postedSummaryIds) {
2011             assertThat(mNotificationHelper.isNotificationGone(summaryId, SEARCH_TYPE.APP))
2012                     .isTrue();
2013         }
2014         assertThat(removedLatch.getCount()).isEqualTo(0);
2015 
2016         // App cancels summary notifications that removed by autogrouping
2017         //      => the original group's child notifications are canceled
2018         for (int i = postedSummaryIds.size() - 1; i >= 0; i--) {
2019             rerankLatch = mListener.setRankingUpdateCountDown(1);
2020             removedLatch = mListener.setRemovedCountDown(1);
2021             int id = postedSummaryIds.remove(i);
2022             cancelAndPoll(id);
2023             removedLatch.await(400, TimeUnit.MILLISECONDS);
2024             rerankLatch.await(400, TimeUnit.MILLISECONDS);
2025             assertAllPostedNotificationsAutogrouped();
2026         }
2027 
2028         // Autogroup summary should be canceled
2029         removedLatch = mListener.setRemovedCountDown(1);
2030         removedLatch.await(400, TimeUnit.MILLISECONDS);
2031         assertNotificationCount(0);
2032     }
2033 
testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled_common( final int numExpectedUpdates)2034     private void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled_common(
2035                 final int numExpectedUpdates) throws Exception {
2036         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
2037         assertNotNull(mListener);
2038         CountDownLatch postingLatch = mListener.setPostedCountDown(5);
2039         CountDownLatch rerankLatch = mListener.setRankingUpdateCountDown(5);
2040 
2041         String newGroup = "new!";
2042         sendNotification(910, R.drawable.black);
2043         sendNotification(920, R.drawable.blue);
2044         sendNotification(930, R.drawable.yellow);
2045         sendNotification(940, R.drawable.yellow);
2046 
2047         List<Integer> postedIds = new ArrayList<>();
2048         postedIds.add(910);
2049         postedIds.add(920);
2050         postedIds.add(930);
2051         postedIds.add(940);
2052 
2053         // Wait until all the notifications, including the autogroup, are posted and grouped.
2054         postingLatch.await(400, TimeUnit.MILLISECONDS);
2055         rerankLatch.await(400, TimeUnit.MILLISECONDS);
2056         assertNotificationCount(5);
2057         assertAllPostedNotificationsAutogrouped();
2058 
2059         // regroup all but one of the children
2060         for (int i = postedIds.size() - 1; i > 0; i--) {
2061             postingLatch = mListener.setPostedCountDown(numExpectedUpdates);
2062             rerankLatch = mListener.setRankingUpdateCountDown(numExpectedUpdates);
2063 
2064             int id = postedIds.remove(i);
2065             sendNotification(id, newGroup, R.drawable.blue);
2066 
2067             postingLatch.await(400, TimeUnit.MILLISECONDS);
2068             rerankLatch.await(400, TimeUnit.MILLISECONDS);
2069             assertNotificationCount(5);
2070             assertOnlySomeNotificationsAutogrouped(postedIds);
2071         }
2072 
2073         if (android.service.notification.Flags.notificationForceGrouping()) {
2074             // post new group summary => avoid forced regrouping
2075             int newGroupSummaryId = 999;
2076             sendNotification(newGroupSummaryId, newGroup, true, R.drawable.yellow, false, null);
2077             postingLatch.await(400, TimeUnit.MILLISECONDS);
2078             rerankLatch.await(400, TimeUnit.MILLISECONDS);
2079             assertNotificationCount(6);
2080         }
2081 
2082         // send a new non-grouped notification. since the autogroup summary still exists,
2083         // the notification should be added to it
2084         rerankLatch = mListener.setRankingUpdateCountDown(numExpectedUpdates);
2085         postingLatch = mListener.setPostedCountDown(numExpectedUpdates);
2086         sendNotification(950, R.drawable.blue);
2087         postedIds.add(950);
2088 
2089         postingLatch.await(400, TimeUnit.MILLISECONDS);
2090         rerankLatch.await(400, TimeUnit.MILLISECONDS);
2091         assertOnlySomeNotificationsAutogrouped(postedIds);
2092     }
2093 
2094     @Test
2095     @RequiresFlagsDisabled(com.android.server.notification.Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled()2096     public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled()
2097             throws Exception {
2098         testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled_common(1);
2099     }
2100 
2101     @Test
2102     @RequiresFlagsEnabled(com.android.server.notification.Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled_summaryUpdated()2103     public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled_summaryUpdated()
2104             throws Exception {
2105         assumeFalse("NotificationListeners do not support visible background users",
2106                 mUserHelper.isVisibleBackgroundUser());
2107         // The autogroup summary should update as well => wait for 2 notification updates
2108         testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled_common(2);
2109     }
2110 
2111     @Test
testPostFullScreenIntent_permission()2112     public void testPostFullScreenIntent_permission() {
2113         int id = 6000;
2114 
2115         final Notification notification =
2116                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
2117                         .setSmallIcon(R.drawable.black)
2118                         .setWhen(System.currentTimeMillis())
2119                         .setFullScreenIntent(getPendingIntent(), true)
2120                         .setContentText("This is #FSI notification")
2121                         .setContentIntent(getPendingIntent())
2122                         .build();
2123         mNotificationManager.notify(id, notification);
2124 
2125         StatusBarNotification n = mNotificationHelper.findPostedNotification(
2126                 null, id, SEARCH_TYPE.APP);
2127         assertNotNull(n);
2128         assertEquals(notification.fullScreenIntent, n.getNotification().fullScreenIntent);
2129     }
2130 
2131     @Test
testNotificationDelegate_grantAndPost()2132     public void testNotificationDelegate_grantAndPost() throws Exception {
2133         final Intent intent = new Intent(mContext, GetResultActivity.class);
2134         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2135         GetResultActivity activity = (GetResultActivity) mInstrumentation.startActivitySync(intent);
2136         mInstrumentation.waitForIdleSync();
2137         activity.clearResult();
2138 
2139         // grant this test permission to post
2140         final Intent activityIntent = new Intent();
2141         activityIntent.setPackage(TEST_APP);
2142         activityIntent.setAction(Intent.ACTION_MAIN);
2143         activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
2144 
2145         activity.startActivityForResult(activityIntent, REQUEST_CODE);
2146         assertEquals(RESULT_OK, activity.getResult().resultCode);
2147 
2148         // send notification
2149         Notification n = new Notification.Builder(mContext, "channel")
2150                 .setSmallIcon(android.R.drawable.ic_media_play)
2151                 .build();
2152         mNotificationManager.notifyAsPackage(TEST_APP, "tag", 0, n);
2153 
2154         assertNotNull(mNotificationHelper.findPostedNotification("tag", 0, SEARCH_TYPE.APP));
2155         final Intent revokeIntent = new Intent(Intent.ACTION_MAIN);
2156         revokeIntent.setClassName(TEST_APP, REVOKE_CLASS);
2157         activity.startActivityForResult(revokeIntent, REQUEST_CODE);
2158         assertEquals(RESULT_OK, activity.getResult().resultCode);
2159     }
2160 
2161     @Test
testNotificationDelegate_grantAndPostAndCancel()2162     public void testNotificationDelegate_grantAndPostAndCancel() throws Exception {
2163         final Intent intent = new Intent(mContext, GetResultActivity.class);
2164         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2165         GetResultActivity activity = (GetResultActivity) mInstrumentation.startActivitySync(intent);
2166         mInstrumentation.waitForIdleSync();
2167         activity.clearResult();
2168 
2169         // grant this test permission to post
2170         final Intent activityIntent = new Intent();
2171         activityIntent.setPackage(TEST_APP);
2172         activityIntent.setAction(Intent.ACTION_MAIN);
2173         activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
2174 
2175         activity.startActivityForResult(activityIntent, REQUEST_CODE);
2176         assertEquals(RESULT_OK, activity.getResult().resultCode);
2177 
2178         // send notification
2179         Notification n = new Notification.Builder(mContext, "channel")
2180                 .setSmallIcon(android.R.drawable.ic_media_play)
2181                 .build();
2182         mNotificationManager.notifyAsPackage(TEST_APP, "toBeCanceled", 10000, n);
2183         assertNotNull(mNotificationHelper.findPostedNotification("toBeCanceled", 10000,
2184                 SEARCH_TYPE.APP));
2185         mNotificationManager.cancelAsPackage(TEST_APP, "toBeCanceled", 10000);
2186         assertTrue(mNotificationHelper.isNotificationGone(10000, SEARCH_TYPE.APP));
2187         final Intent revokeIntent = new Intent(Intent.ACTION_MAIN);
2188         revokeIntent.setClassName(TEST_APP, REVOKE_CLASS);
2189         activity.startActivityForResult(revokeIntent, REQUEST_CODE);
2190         assertEquals(RESULT_OK, activity.getResult().resultCode);
2191     }
2192 
2193     @Test
testNotificationDelegate_cannotCancelNotificationsPostedByDelegator()2194     public void testNotificationDelegate_cannotCancelNotificationsPostedByDelegator()
2195             throws Exception {
2196         assumeFalse("NotificationListeners do not support visible background users",
2197                 mUserHelper.isVisibleBackgroundUser());
2198         final Intent intent = new Intent(mContext, GetResultActivity.class);
2199         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2200         GetResultActivity activity = (GetResultActivity) mInstrumentation.startActivitySync(intent);
2201         mInstrumentation.waitForIdleSync();
2202         activity.clearResult();
2203 
2204         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
2205         assertNotNull(mListener);
2206 
2207         // grant this test permission to post
2208         final Intent activityIntent = new Intent(Intent.ACTION_MAIN);
2209         activityIntent.setClassName(TEST_APP, DELEGATE_POST_CLASS);
2210 
2211         activity.startActivityForResult(activityIntent, REQUEST_CODE);
2212         assertEquals(RESULT_OK, activity.getResult().resultCode);
2213 
2214         assertNotNull(mNotificationHelper.findPostedNotification(null, 9, SEARCH_TYPE.LISTENER));
2215 
2216         try {
2217             mNotificationManager.cancelAsPackage(TEST_APP, null, 9);
2218             fail("Delegate should not be able to cancel notification they did not post");
2219         } catch (SecurityException e) {
2220             // yay
2221         }
2222 
2223         // double check that the notification does still exist
2224         assertNotNull(mNotificationHelper.findPostedNotification(null, 9, SEARCH_TYPE.LISTENER));
2225 
2226         final Intent revokeIntent = new Intent(Intent.ACTION_MAIN);
2227         revokeIntent.setClassName(TEST_APP, REVOKE_CLASS);
2228         activity.startActivityForResult(revokeIntent, REQUEST_CODE);
2229         assertEquals(RESULT_OK, activity.getResult().resultCode);
2230     }
2231 
2232     @Test
testNotificationDelegate_grantAndReadChannels()2233     public void testNotificationDelegate_grantAndReadChannels() throws Exception {
2234         final Intent intent = new Intent(mContext, GetResultActivity.class);
2235         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2236         GetResultActivity activity = (GetResultActivity) mInstrumentation.startActivitySync(intent);
2237         mInstrumentation.waitForIdleSync();
2238         activity.clearResult();
2239 
2240         // grant this test permission to post
2241         final Intent activityIntent = new Intent();
2242         activityIntent.setPackage(TEST_APP);
2243         activityIntent.setAction(Intent.ACTION_MAIN);
2244         activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
2245 
2246         activity.startActivityForResult(activityIntent, REQUEST_CODE);
2247         assertEquals(RESULT_OK, activity.getResult().resultCode);
2248 
2249         List<NotificationChannel> channels =
2250                 mContext.createPackageContextAsUser(TEST_APP, /* flags= */ 0, mContext.getUser())
2251                         .getSystemService(NotificationManager.class)
2252                         .getNotificationChannels();
2253 
2254         assertNotNull(channels);
2255 
2256         final Intent revokeIntent = new Intent(Intent.ACTION_MAIN);
2257         revokeIntent.setClassName(TEST_APP, REVOKE_CLASS);
2258         activity.startActivityForResult(revokeIntent, REQUEST_CODE);
2259         assertEquals(RESULT_OK, activity.getResult().resultCode);
2260     }
2261 
2262     @Test
testNotificationDelegate_grantAndReadChannelAndRevoke()2263     public void testNotificationDelegate_grantAndReadChannelAndRevoke() throws Exception {
2264         final Intent intent = new Intent(mContext, GetResultActivity.class);
2265         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2266         GetResultActivity activity = (GetResultActivity) mInstrumentation.startActivitySync(intent);
2267         mInstrumentation.waitForIdleSync();
2268         activity.clearResult();
2269 
2270         // grant this test permission to post
2271         final Intent activityIntent = new Intent();
2272         activityIntent.setPackage(TEST_APP);
2273         activityIntent.setAction(Intent.ACTION_MAIN);
2274         activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
2275 
2276         activity.startActivityForResult(activityIntent, REQUEST_CODE);
2277         assertEquals(RESULT_OK, activity.getResult().resultCode);
2278 
2279         NotificationChannel channel =
2280                 mContext.createPackageContextAsUser(TEST_APP, /* flags= */ 0, mContext.getUser())
2281                         .getSystemService(NotificationManager.class)
2282                         .getNotificationChannel("channel");
2283 
2284         assertNotNull(channel);
2285 
2286         final Intent revokeIntent = new Intent(Intent.ACTION_MAIN);
2287         revokeIntent.setClassName(TEST_APP, REVOKE_CLASS);
2288         activity.startActivityForResult(revokeIntent, REQUEST_CODE);
2289         assertEquals(RESULT_OK, activity.getResult().resultCode);
2290 
2291         // Verify that the delegate can no longer get channel information
2292         assertThrows(
2293                 SecurityException.class,
2294                 () ->
2295                         mContext.createPackageContextAsUser(
2296                                         TEST_APP, /* flags= */ 0, mContext.getUser())
2297                                 .getSystemService(NotificationManager.class)
2298                                 .getNotificationChannel("channel"));
2299     }
2300 
2301     @Test
testNotificationDelegate_grantAndRevoke()2302     public void testNotificationDelegate_grantAndRevoke() throws Exception {
2303         final Intent intent = new Intent(mContext, GetResultActivity.class);
2304         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2305         GetResultActivity activity = (GetResultActivity) mInstrumentation.startActivitySync(intent);
2306         mInstrumentation.waitForIdleSync();
2307         activity.clearResult();
2308 
2309         // grant this test permission to post
2310         final Intent activityIntent = new Intent();
2311         activityIntent.setPackage(TEST_APP);
2312         activityIntent.setAction(Intent.ACTION_MAIN);
2313         activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
2314 
2315         activity.startActivityForResult(activityIntent, REQUEST_CODE);
2316         assertEquals(RESULT_OK, activity.getResult().resultCode);
2317 
2318         assertTrue(mNotificationManager.canNotifyAsPackage(TEST_APP));
2319 
2320         final Intent revokeIntent = new Intent(Intent.ACTION_MAIN);
2321         revokeIntent.setClassName(TEST_APP, REVOKE_CLASS);
2322         activity.startActivityForResult(revokeIntent, REQUEST_CODE);
2323         assertEquals(RESULT_OK, activity.getResult().resultCode);
2324 
2325         try {
2326             // send notification
2327             Notification n = new Notification.Builder(mContext, "channel")
2328                     .setSmallIcon(android.R.drawable.ic_media_play)
2329                     .build();
2330             mNotificationManager.notifyAsPackage(TEST_APP, "tag", 0, n);
2331             fail("Should not be able to post as a delegate when permission revoked");
2332         } catch (SecurityException e) {
2333             // yay
2334         }
2335     }
2336 
2337     @Test
testNotificationIcon()2338     public void testNotificationIcon() throws Exception {
2339         assumeFalse("NotificationListeners do not support visible background users",
2340                 mUserHelper.isVisibleBackgroundUser());
2341         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
2342         assertNotNull(mListener);
2343 
2344         CountDownLatch postedLatch = mListener.setPostedCountDown(2);
2345 
2346         int id = 6000;
2347 
2348         Notification notification =
2349                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
2350                         .setSmallIcon(android.R.drawable.ic_media_play)
2351                         .setWhen(System.currentTimeMillis())
2352                         .setFullScreenIntent(getPendingIntent(), true)
2353                         .setContentText("This notification has a resource icon")
2354                         .setContentIntent(getPendingIntent())
2355                         .build();
2356         mNotificationManager.notify(id, notification);
2357 
2358         notification =
2359                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
2360                         .setSmallIcon(Icon.createWithResource(mContext, R.drawable.icon_black))
2361                         .setWhen(System.currentTimeMillis())
2362                         .setFullScreenIntent(getPendingIntent(), true)
2363                         .setContentText("This notification has an Icon icon")
2364                         .setContentIntent(getPendingIntent())
2365                         .build();
2366         mNotificationManager.notify(id, notification);
2367         postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
2368 
2369         StatusBarNotification n = mNotificationHelper.findPostedNotification(
2370                 null, id, SEARCH_TYPE.POSTED);
2371         assertNotNull(n);
2372     }
2373 
2374     @Test
testShouldHideSilentStatusIcons()2375     public void testShouldHideSilentStatusIcons() throws Exception {
2376         assumeFalse("NotificationListeners do not support visible background users",
2377                 mUserHelper.isVisibleBackgroundUser());
2378         try {
2379             mNotificationManager.shouldHideSilentStatusBarIcons();
2380             fail("Non-privileged apps should not get this information");
2381         } catch (SecurityException e) {
2382             // pass
2383         }
2384 
2385         mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
2386         // no exception this time
2387         mNotificationManager.shouldHideSilentStatusBarIcons();
2388     }
2389 
2390     /* Confirm that the optional methods of TestNotificationListener still exist and
2391      * don't fail. */
2392     @Test
testNotificationListenerMethods()2393     public void testNotificationListenerMethods() {
2394         NotificationListenerService listener = new TestNotificationListener();
2395         listener.onListenerConnected();
2396 
2397         listener.onSilentStatusBarIconsVisibilityChanged(false);
2398 
2399         listener.onNotificationPosted(null);
2400         listener.onNotificationPosted(null, null);
2401 
2402         listener.onNotificationRemoved(null);
2403         listener.onNotificationRemoved(null, null);
2404 
2405         listener.onNotificationChannelGroupModified("", mContext.getUser(), null,
2406                 NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_ADDED);
2407         listener.onNotificationChannelModified("", mContext.getUser(), null,
2408                 NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_ADDED);
2409 
2410         listener.onListenerDisconnected();
2411     }
2412 
performNotificationProviderAction(@onNull String action)2413     private void performNotificationProviderAction(@NonNull String action) {
2414         // Create an intent to launch an activity which just posts or cancels notifications
2415         Intent activityIntent = new Intent(Intent.ACTION_MAIN);
2416         activityIntent.setClassName(NOTIFICATIONPROVIDER, RICH_NOTIFICATION_ACTIVITY);
2417         activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2418         activityIntent.putExtra("action", action);
2419         mContext.startActivity(activityIntent);
2420     }
2421 
2422     @Test
testNotificationUriPermissionsGranted()2423     public void testNotificationUriPermissionsGranted() throws Exception {
2424         assumeFalse("NotificationListeners do not support visible background users",
2425                 mUserHelper.isVisibleBackgroundUser());
2426         Uri background7Uri = Uri.parse(
2427                 "content://com.android.test.notificationprovider.provider/background7.png");
2428         Uri background8Uri = Uri.parse(
2429                 "content://com.android.test.notificationprovider.provider/background8.png");
2430 
2431         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
2432         assertNotNull(mListener);
2433 
2434         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
2435 
2436         try {
2437             // Post #7
2438             performNotificationProviderAction("send-7");
2439 
2440             postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
2441             assertEquals(background7Uri, getNotificationBackgroundImageUri(7));
2442             assertTrue(mNotificationHelper.isNotificationGone(8, SEARCH_TYPE.LISTENER));
2443             assertAccessible(background7Uri);
2444             assertInaccessible(background8Uri);
2445 
2446             // Reset the notification posted latch.
2447             postedLatch = mListener.setPostedCountDown(1);
2448 
2449             // Post #8
2450             performNotificationProviderAction("send-8");
2451 
2452             postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
2453             assertEquals(background7Uri, getNotificationBackgroundImageUri(7));
2454             assertEquals(background8Uri, getNotificationBackgroundImageUri(8));
2455             assertAccessible(background7Uri);
2456             assertAccessible(background8Uri);
2457 
2458             // Add a notification removed latch.
2459             CountDownLatch removedLatch = mListener.setRemovedCountDown(1);
2460 
2461             // Cancel #7
2462             performNotificationProviderAction("cancel-7");
2463 
2464             removedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
2465             assertTrue(mNotificationHelper.isNotificationGone(7, SEARCH_TYPE.LISTENER));
2466             assertEquals(background8Uri, getNotificationBackgroundImageUri(8));
2467             assertInaccessible(background7Uri);
2468             assertAccessible(background8Uri);
2469 
2470             // Reset the notification reemoved latch.
2471             removedLatch = mListener.setRemovedCountDown(1);
2472 
2473             // Cancel #8
2474             performNotificationProviderAction("cancel-8");
2475 
2476             removedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
2477             assertTrue(mNotificationHelper.isNotificationGone(7, SEARCH_TYPE.LISTENER));
2478             assertTrue(mNotificationHelper.isNotificationGone(8, SEARCH_TYPE.LISTENER));
2479             assertInaccessible(background7Uri);
2480             assertInaccessible(background8Uri);
2481 
2482         } finally {
2483             // Clean up -- reset any remaining notifications
2484             performNotificationProviderAction("reset");
2485             Thread.sleep(500);
2486         }
2487     }
2488 
2489     @Test
testNotificationUriPermissionsGrantedToNewListeners()2490     public void testNotificationUriPermissionsGrantedToNewListeners() throws Exception {
2491         assumeFalse("NotificationListeners do not support visible background users",
2492                 mUserHelper.isVisibleBackgroundUser());
2493         Uri background7Uri = Uri.parse(
2494                 "content://com.android.test.notificationprovider.provider/background7.png");
2495 
2496         try {
2497             // Post #7
2498             performNotificationProviderAction("send-7");
2499             // Don't have access the notification yet, but we can test the URI
2500             assertInaccessible(background7Uri);
2501 
2502             mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
2503             assertNotNull(mListener);
2504 
2505             mNotificationHelper.findPostedNotification(null, 7, SEARCH_TYPE.LISTENER);
2506 
2507             assertEquals(background7Uri, getNotificationBackgroundImageUri(7));
2508             assertAccessible(background7Uri);
2509 
2510         } finally {
2511             // Clean Up -- Cancel #7
2512             performNotificationProviderAction("cancel-7");
2513             Thread.sleep(500);
2514         }
2515     }
2516 
2517     @Test
testNotificationUriPermissionsRevokedFromRemovedListeners()2518     public void testNotificationUriPermissionsRevokedFromRemovedListeners() throws Exception {
2519         assumeFalse("NotificationListeners do not support visible background users",
2520                 mUserHelper.isVisibleBackgroundUser());
2521         Uri background7Uri = Uri.parse(
2522                 "content://com.android.test.notificationprovider.provider/background7.png");
2523 
2524         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
2525         mListener = TestNotificationListener.getInstance();
2526         assertNotNull(mListener);
2527 
2528         try {
2529             // Post #7
2530             performNotificationProviderAction("send-7");
2531             mNotificationHelper.findPostedNotification(null, 7, SEARCH_TYPE.POSTED);
2532 
2533             assertEquals(background7Uri, getNotificationBackgroundImageUri(7));
2534             assertAccessible(background7Uri);
2535 
2536             // Remove the listener to ensure permissions get revoked
2537             mNotificationHelper.disableListener(STUB_PACKAGE_NAME);
2538             Thread.sleep(500); // wait for listener to be disabled
2539 
2540             assertInaccessible(background7Uri);
2541 
2542         } finally {
2543             // Clean Up -- Cancel #7
2544             performNotificationProviderAction("cancel-7");
2545             Thread.sleep(500);
2546         }
2547     }
2548 
2549     private class NotificationListenerConnection implements ServiceConnection {
2550         private final Semaphore mSemaphore = new Semaphore(0);
2551 
2552         @Override
onServiceConnected(ComponentName className, IBinder service)2553         public void onServiceConnected(ComponentName className, IBinder service) {
2554             if (URI_ACCESS_SERVICE.equals(className)) {
2555                 mNotificationUriAccessService = INotificationUriAccessService.Stub.asInterface(
2556                         service);
2557             }
2558             if (NLS_CONTROL_SERVICE.equals(className)) {
2559                 mNLSControlService = INLSControlService.Stub.asInterface(service);
2560             }
2561             mSemaphore.release();
2562         }
2563 
2564         @Override
onServiceDisconnected(ComponentName className)2565         public void onServiceDisconnected(ComponentName className) {
2566             if (URI_ACCESS_SERVICE.equals(className)) {
2567                 mNotificationUriAccessService = null;
2568             }
2569             if (NLS_CONTROL_SERVICE.equals(className)) {
2570                 mNLSControlService = null;
2571             }
2572         }
2573 
waitForService()2574         public void waitForService() {
2575             try {
2576                 if (mSemaphore.tryAcquire(5, TimeUnit.SECONDS)) {
2577                     return;
2578                 }
2579             } catch (InterruptedException e) {
2580             }
2581             fail("failed to connec to service");
2582         }
2583     }
2584 
2585     @Test
testNotificationUriPermissionsRevokedOnlyFromRemovedListeners()2586     public void testNotificationUriPermissionsRevokedOnlyFromRemovedListeners() throws Exception {
2587         assumeFalse("NotificationListeners do not support visible background users",
2588                 mUserHelper.isVisibleBackgroundUser());
2589         Uri background7Uri = Uri.parse(
2590                 "content://com.android.test.notificationprovider.provider/background7.png");
2591 
2592         // Connect to a service in the NotificationListener app which allows us to validate URI
2593         // permissions granted to a second app, so that we show that permissions aren't being
2594         // revoked too broadly.
2595         final Intent intent = new Intent();
2596         intent.setComponent(URI_ACCESS_SERVICE);
2597         NotificationListenerConnection connection = new NotificationListenerConnection();
2598         mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
2599         connection.waitForService();
2600 
2601         // Before starting the test, make sure the service works, that there is no listener, and
2602         // that the URI starts inaccessible to that process.
2603         mNotificationUriAccessService.ensureNotificationListenerServiceConnected(false);
2604         assertFalse(mNotificationUriAccessService.isFileUriAccessible(background7Uri));
2605 
2606         // Give the NotificationListener app access to notifications, and validate that.
2607         toggleExternalListenerAccess(new ComponentName("com.android.test.notificationlistener",
2608                 "com.android.test.notificationlistener.TestNotificationListener"), true);
2609         Thread.sleep(500);
2610         mNotificationUriAccessService.ensureNotificationListenerServiceConnected(true);
2611         assertFalse(mNotificationUriAccessService.isFileUriAccessible(background7Uri));
2612 
2613         // Give the test app access to notifications, and get that listener
2614         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
2615         assertNotNull(mListener);
2616 
2617         try {
2618             try {
2619                 // Post #7
2620                 performNotificationProviderAction("send-7");
2621 
2622                 // Check that both the test app (this code) and the external app have URI access.
2623                 assertEquals(background7Uri, getNotificationBackgroundImageUri(7));
2624                 assertAccessible(background7Uri);
2625                 assertTrue(mNotificationUriAccessService.isFileUriAccessible(background7Uri));
2626 
2627                 // Remove the external listener to ensure permissions get revoked
2628                 toggleExternalListenerAccess(
2629                         new ComponentName("com.android.test.notificationlistener",
2630                                 "com.android.test.notificationlistener.TestNotificationListener"),
2631                         false);
2632                 Thread.sleep(500); // wait for listener to be disabled
2633 
2634                 // Ensure that revoking listener access to this one app does not affect the other:
2635                 // external app no longer has access, this one still does
2636                 assertFalse(mNotificationUriAccessService.isFileUriAccessible(background7Uri));
2637                 assertAccessible(background7Uri);
2638 
2639             } finally {
2640                 // Clean Up -- Cancel #7
2641                 performNotificationProviderAction("cancel-7");
2642                 Thread.sleep(500);
2643             }
2644 
2645             // Finally, cancelling the notification must still revoke those other permissions.
2646             // Double-check first that the notification is actually gone, and then wait for a bit
2647             // longer, as it may take some time for the uri permissions to clear up even after the
2648             // notification is gone.
2649             assertTrue(mNotificationHelper.isNotificationGone(7, SEARCH_TYPE.LISTENER));
2650             Thread.sleep(500);
2651             assertInaccessible(background7Uri);
2652         } finally {
2653             // Clean Up -- Make sure this app has access revoked
2654             mNotificationHelper.disableListener(STUB_PACKAGE_NAME);
2655         }
2656     }
2657 
2658     @Test
testNotificationListenerRequestUnbind()2659     public void testNotificationListenerRequestUnbind() throws Exception {
2660         assumeFalse("NotificationListeners do not support visible background users",
2661                 mUserHelper.isVisibleBackgroundUser());
2662         final Intent intent = new Intent();
2663         intent.setComponent(NLS_CONTROL_SERVICE);
2664         NotificationListenerConnection connection = new NotificationListenerConnection();
2665         mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
2666         connection.waitForService();
2667 
2668         // Give the NotificationListener app access to notifications, and validate that.
2669         toggleExternalListenerAccess(NO_AUTOBIND_NLS, true);
2670         Thread.sleep(500);
2671 
2672         // Give the test app access to notifications, and get that listener
2673         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
2674         assertNotNull(mListener);
2675 
2676         try {
2677             // Check that the listener service is not auto-bound (manifest meta-data)
2678             assertFalse(mNLSControlService.isNotificationListenerConnected());
2679 
2680             // Request bind NLS
2681             mNLSControlService.requestRebindComponent();
2682             Thread.sleep(500);
2683             assertTrue(mNLSControlService.isNotificationListenerConnected());
2684 
2685             // Request unbind NLS
2686             mNLSControlService.requestUnbindComponent();
2687             Thread.sleep(500);
2688             assertFalse(mNLSControlService.isNotificationListenerConnected());
2689         } finally {
2690             // Clean Up -- Make sure the external listener is has access revoked
2691             toggleExternalListenerAccess(NO_AUTOBIND_NLS, false);
2692         }
2693     }
2694 
2695     @Test
testNotificationListenerAutobindMetaData()2696     public void testNotificationListenerAutobindMetaData() throws Exception {
2697         final ServiceInfo info = mPackageManager.getServiceInfo(NO_AUTOBIND_NLS,
2698                 PackageManager.GET_META_DATA
2699                         | PackageManager.MATCH_DIRECT_BOOT_AWARE
2700                         | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
2701 
2702         assertNotNull(info);
2703         assertTrue(info.metaData.containsKey(META_DATA_DEFAULT_AUTOBIND));
2704         assertFalse(info.metaData.getBoolean(META_DATA_DEFAULT_AUTOBIND, true));
2705     }
2706 
assertAccessible(Uri uri)2707     private void assertAccessible(Uri uri)
2708             throws IOException {
2709         ContentResolver contentResolver = mContext.getContentResolver();
2710         for (int tries = 5; tries-- > 0; ) {
2711             try (AssetFileDescriptor fd = contentResolver.openAssetFile(uri, "r", null)) {
2712                 if (fd != null) {
2713                     return;
2714                 }
2715             } catch (SecurityException e) {
2716             }
2717             try {
2718                 Thread.sleep(200);
2719             } catch (InterruptedException ex) {
2720             }
2721         }
2722         fail("Uri " + uri + "is not accessible");
2723     }
2724 
assertInaccessible(Uri uri)2725     private void assertInaccessible(Uri uri)
2726             throws IOException {
2727         ContentResolver contentResolver = mContext.getContentResolver();
2728         for (int tries = 5; tries-- > 0; ) {
2729             try (AssetFileDescriptor fd = contentResolver.openAssetFile(uri, "r", null)) {
2730             } catch (SecurityException e) {
2731                 return;
2732             }
2733             try {
2734                 Thread.sleep(200);
2735             } catch (InterruptedException ex) {
2736             }
2737         }
2738         fail("Uri " + uri + "is still accessible");
2739     }
2740 
2741     @NonNull
getNotificationBackgroundImageUri(int notificationId)2742     private Uri getNotificationBackgroundImageUri(int notificationId) {
2743         StatusBarNotification sbn = mNotificationHelper.findPostedNotification(null,
2744                 notificationId, SEARCH_TYPE.LISTENER);
2745         assertNotNull(sbn);
2746         String imageUriString = sbn.getNotification().extras
2747                 .getString(Notification.EXTRA_BACKGROUND_IMAGE_URI);
2748         assertNotNull(imageUriString);
2749         return Uri.parse(imageUriString);
2750     }
2751 
uncheck(ThrowingSupplier<T> supplier)2752     private <T> T uncheck(ThrowingSupplier<T> supplier) {
2753         try {
2754             return supplier.get();
2755         } catch (Exception e) {
2756             throw new CompletionException(e);
2757         }
2758     }
2759 
2760     @Test
testNotificationListener_setNotificationsShown()2761     public void testNotificationListener_setNotificationsShown() throws Exception {
2762         assumeFalse("NotificationListeners do not support visible background users",
2763                 mUserHelper.isVisibleBackgroundUser());
2764         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
2765         assertNotNull(mListener);
2766         CountDownLatch postedLatch = mListener.setPostedCountDown(2);
2767         final int notificationId1 = 1003;
2768         final int notificationId2 = 1004;
2769 
2770         sendNotification(notificationId1, R.drawable.black);
2771         sendNotification(notificationId2, R.drawable.black);
2772         // wait for notification listener to receive notification
2773         postedLatch.await(500, TimeUnit.MILLISECONDS);
2774 
2775         StatusBarNotification sbn1 = mNotificationHelper.findPostedNotification(
2776                 null, notificationId1, SEARCH_TYPE.LISTENER);
2777         StatusBarNotification sbn2 = mNotificationHelper.findPostedNotification(
2778                 null, notificationId2, SEARCH_TYPE.LISTENER);
2779         mListener.setNotificationsShown(new String[]{sbn1.getKey()});
2780 
2781         mNotificationHelper.disableListener(STUB_PACKAGE_NAME);
2782         Thread.sleep(500); // wait for listener to be disallowed
2783         try {
2784             mListener.setNotificationsShown(new String[]{sbn2.getKey()});
2785             fail("Should not be able to set shown if listener access isn't granted");
2786         } catch (SecurityException e) {
2787             // expected
2788         }
2789     }
2790 
2791     @Test
testNotificationListener_getNotificationChannels()2792     public void testNotificationListener_getNotificationChannels() throws Exception {
2793         assumeFalse("NotificationListeners do not support visible background users",
2794                 mUserHelper.isVisibleBackgroundUser());
2795         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
2796         assertNotNull(mListener);
2797 
2798         try {
2799             mListener.getNotificationChannels(mContext.getPackageName(), mContext.getUser());
2800             fail("Shouldn't be able get channels without CompanionDeviceManager#getAssociations()");
2801         } catch (SecurityException e) {
2802             // expected
2803         }
2804     }
2805 
2806     @Test
testNotificationListener_getNotificationChannelGroups()2807     public void testNotificationListener_getNotificationChannelGroups() throws Exception {
2808         assumeFalse("NotificationListeners do not support visible background users",
2809                 mUserHelper.isVisibleBackgroundUser());
2810         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
2811         assertNotNull(mListener);
2812         try {
2813             mListener.getNotificationChannelGroups(mContext.getPackageName(), mContext.getUser());
2814             fail("Should not be able get groups without CompanionDeviceManager#getAssociations()");
2815         } catch (SecurityException e) {
2816             // expected
2817         }
2818     }
2819 
2820     @Test
testNotificationListener_updateNotificationChannel()2821     public void testNotificationListener_updateNotificationChannel() throws Exception {
2822         assumeFalse("NotificationListeners do not support visible background users",
2823                 mUserHelper.isVisibleBackgroundUser());
2824         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
2825         assertNotNull(mListener);
2826 
2827         NotificationChannel channel = new NotificationChannel(
2828                 NOTIFICATION_CHANNEL_ID, "name", IMPORTANCE_DEFAULT);
2829         try {
2830             mListener.updateNotificationChannel(mContext.getPackageName(), mContext.getUser(),
2831                     channel);
2832             fail("Shouldn't be able to update channel without "
2833                     + "CompanionDeviceManager#getAssociations()");
2834         } catch (SecurityException e) {
2835             // expected
2836         }
2837     }
2838 
2839     @Test
testNotificationListener_getActiveNotifications()2840     public void testNotificationListener_getActiveNotifications() throws Exception {
2841         assumeFalse("NotificationListeners do not support visible background users",
2842                 mUserHelper.isVisibleBackgroundUser());
2843         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
2844         assertNotNull(mListener);
2845         CountDownLatch postedLatch = mListener.setPostedCountDown(2);
2846         final int notificationId1 = 1001;
2847         final int notificationId2 = 1002;
2848 
2849         sendNotification(notificationId1, R.drawable.black);
2850         sendNotification(notificationId2, R.drawable.black);
2851         // wait for notification listener to receive notification
2852         postedLatch.await(500, TimeUnit.MILLISECONDS);
2853 
2854         StatusBarNotification sbn1 = mNotificationHelper.findPostedNotification(
2855                 null, notificationId1, SEARCH_TYPE.LISTENER);
2856         StatusBarNotification sbn2 = mNotificationHelper.findPostedNotification(
2857                 null, notificationId2, SEARCH_TYPE.LISTENER);
2858         StatusBarNotification[] notifs =
2859                 mListener.getActiveNotifications(new String[]{sbn2.getKey(), sbn1.getKey()});
2860         assertEquals(sbn2.getKey(), notifs[0].getKey());
2861         assertEquals(sbn2.getId(), notifs[0].getId());
2862         assertEquals(sbn2.getPackageName(), notifs[0].getPackageName());
2863 
2864         assertEquals(sbn1.getKey(), notifs[1].getKey());
2865         assertEquals(sbn1.getId(), notifs[1].getId());
2866         assertEquals(sbn1.getPackageName(), notifs[1].getPackageName());
2867     }
2868 
2869 
2870     @Test
testNotificationListener_getCurrentRanking()2871     public void testNotificationListener_getCurrentRanking() throws Exception {
2872         assumeFalse("NotificationListeners do not support visible background users",
2873                 mUserHelper.isVisibleBackgroundUser());
2874         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
2875         assertNotNull(mListener);
2876         CountDownLatch rankingUpdateLatch = mListener.setRankingUpdateCountDown(1);
2877 
2878         sendNotification(1, R.drawable.black);
2879         rankingUpdateLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
2880         mNotificationHelper.findPostedNotification(null, 1, SEARCH_TYPE.POSTED);
2881 
2882         assertEquals(mListener.mRankingMap, mListener.getCurrentRanking());
2883     }
2884 
2885     @Test
testNotificationListener_cancelNotifications()2886     public void testNotificationListener_cancelNotifications() throws Exception {
2887         assumeFalse("NotificationListeners do not support visible background users",
2888                 mUserHelper.isVisibleBackgroundUser());
2889         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
2890         assertNotNull(mListener);
2891         final int notificationId = 1006;
2892 
2893         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
2894         sendNotification(notificationId, R.drawable.black);
2895         // wait for notification listener to receive notification
2896         postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
2897 
2898         StatusBarNotification sbn = mNotificationHelper.findPostedNotification(null, notificationId,
2899                 SEARCH_TYPE.LISTENER);
2900 
2901         mListener.cancelNotification(sbn.getPackageName(), sbn.getTag(), sbn.getId());
2902         // Beginning with Lollipop, this cancelNotification signature no longer cancels the
2903         // notification.
2904         if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
2905             assertNotNull(mNotificationHelper.findPostedNotification(null, notificationId,
2906                     SEARCH_TYPE.LISTENER));
2907         } else {
2908             // Tested in LegacyNotificationManager20Test
2909             assertTrue(mNotificationHelper.isNotificationGone(
2910                     notificationId, SEARCH_TYPE.LISTENER));
2911         }
2912 
2913         mListener.cancelNotifications(new String[]{sbn.getKey()});
2914         if (getCancellationReason(sbn.getKey())
2915                 != NotificationListenerService.REASON_LISTENER_CANCEL) {
2916             fail("Failed to cancel notification id=" + notificationId);
2917         }
2918     }
2919 
2920     @Test
testNotificationAssistant_cancelNotifications()2921     public void testNotificationAssistant_cancelNotifications() throws Exception {
2922         mAssistant = mNotificationHelper.enableAssistant(STUB_PACKAGE_NAME);
2923         assertNotNull(mAssistant);
2924         final int notificationId = 1006;
2925 
2926         sendNotification(notificationId, R.drawable.black);
2927         Thread.sleep(500); // wait for notification listener to receive notification
2928 
2929         StatusBarNotification sbn = mNotificationHelper.findPostedNotification(null, notificationId,
2930                 SEARCH_TYPE.APP);
2931 
2932         mAssistant.cancelNotifications(new String[]{sbn.getKey()});
2933         int gotReason = getAssistantCancellationReason(sbn.getKey());
2934         if (gotReason != NotificationListenerService.REASON_ASSISTANT_CANCEL) {
2935             fail("Failed cancellation from assistant, notification id=" + notificationId
2936                     + "; got reason=" + gotReason);
2937         }
2938     }
2939 
2940     @Test
testNotificationManagerPolicy_priorityCategoriesToString()2941     public void testNotificationManagerPolicy_priorityCategoriesToString() {
2942         String zeroString = NotificationManager.Policy.priorityCategoriesToString(0);
2943         assertEquals("priorityCategories of 0 produces empty string", "", zeroString);
2944 
2945         String oneString = NotificationManager.Policy.priorityCategoriesToString(1);
2946         assertNotNull("priorityCategories of 1 returns a string", oneString);
2947         boolean lengthGreaterThanZero = oneString.length() > 0;
2948         assertTrue("priorityCategories of 1 returns a string with length greater than 0",
2949                 lengthGreaterThanZero);
2950 
2951         String badNumberString = NotificationManager.Policy.priorityCategoriesToString(1234567);
2952         assertNotNull("priorityCategories with a non-relevant int returns a string",
2953                 badNumberString);
2954     }
2955 
2956     @Test
testNotificationManagerPolicy_prioritySendersToString()2957     public void testNotificationManagerPolicy_prioritySendersToString() {
2958         String zeroString = NotificationManager.Policy.prioritySendersToString(0);
2959         assertNotNull("prioritySenders of 1 returns a string", zeroString);
2960         boolean lengthGreaterThanZero = zeroString.length() > 0;
2961         assertTrue("prioritySenders of 1 returns a string with length greater than 0",
2962                 lengthGreaterThanZero);
2963 
2964         String badNumberString = NotificationManager.Policy.prioritySendersToString(1234567);
2965         assertNotNull("prioritySenders with a non-relevant int returns a string", badNumberString);
2966     }
2967 
2968     @Test
testNotificationManagerPolicy_suppressedEffectsToString()2969     public void testNotificationManagerPolicy_suppressedEffectsToString() {
2970         String zeroString = NotificationManager.Policy.suppressedEffectsToString(0);
2971         assertEquals("suppressedEffects of 0 produces empty string", "", zeroString);
2972 
2973         String oneString = NotificationManager.Policy.suppressedEffectsToString(1);
2974         assertNotNull("suppressedEffects of 1 returns a string", oneString);
2975         boolean lengthGreaterThanZero = oneString.length() > 0;
2976         assertTrue("suppressedEffects of 1 returns a string with length greater than 0",
2977                 lengthGreaterThanZero);
2978 
2979         String badNumberString = NotificationManager.Policy.suppressedEffectsToString(1234567);
2980         assertNotNull("suppressedEffects with a non-relevant int returns a string",
2981                 badNumberString);
2982     }
2983 
2984     @Test
testOriginalChannelImportance()2985     public void testOriginalChannelImportance() {
2986         NotificationChannel channel = new NotificationChannel(mId, "my channel", IMPORTANCE_HIGH);
2987 
2988         mNotificationManager.createNotificationChannel(channel);
2989 
2990         NotificationChannel actual = mNotificationManager.getNotificationChannel(channel.getId());
2991         assertEquals(IMPORTANCE_HIGH, actual.getImportance());
2992         assertEquals(IMPORTANCE_HIGH, actual.getOriginalImportance());
2993 
2994         // Apps are allowed to downgrade channel importance if the user has not changed any
2995         // fields on this channel yet.
2996         channel.setImportance(IMPORTANCE_DEFAULT);
2997         mNotificationManager.createNotificationChannel(channel);
2998 
2999         actual = mNotificationManager.getNotificationChannel(channel.getId());
3000         assertEquals(IMPORTANCE_DEFAULT, actual.getImportance());
3001         assertEquals(IMPORTANCE_HIGH, actual.getOriginalImportance());
3002     }
3003 
3004     @Test
testCreateConversationChannel()3005     public void testCreateConversationChannel() {
3006         final NotificationChannel channel =
3007                 new NotificationChannel(mId, "Messages", IMPORTANCE_DEFAULT);
3008 
3009         String conversationId = "person a";
3010 
3011         final NotificationChannel conversationChannel =
3012                 new NotificationChannel(mId + "child",
3013                         "Messages from " + conversationId, IMPORTANCE_DEFAULT);
3014         conversationChannel.setConversationId(channel.getId(), conversationId);
3015 
3016         mNotificationManager.createNotificationChannel(channel);
3017         mNotificationManager.createNotificationChannel(conversationChannel);
3018 
3019         compareChannels(conversationChannel,
3020                 mNotificationManager.getNotificationChannel(channel.getId(), conversationId));
3021     }
3022 
3023     @Test
testConversationRankingFields()3024     public void testConversationRankingFields() throws Exception {
3025         assumeFalse("NotificationListeners do not support visible background users",
3026                 mUserHelper.isVisibleBackgroundUser());
3027         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
3028         assertNotNull(mListener);
3029         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
3030 
3031         createDynamicShortcut();
3032         mNotificationManager.notify(177, getConversationNotification().build());
3033 
3034         // wait for notification listener to receive notification
3035         postedLatch.await(500, TimeUnit.MILLISECONDS);
3036         assertNotNull(mNotificationHelper.findPostedNotification(null, 177, SEARCH_TYPE.LISTENER));
3037         assertEquals(1, mListener.mPosted.size());
3038 
3039         NotificationListenerService.RankingMap rankingMap = mListener.mRankingMap;
3040         NotificationListenerService.Ranking outRanking = new NotificationListenerService.Ranking();
3041         for (String key : rankingMap.getOrderedKeys()) {
3042             if (key.contains(mListener.getPackageName())) {
3043                 rankingMap.getRanking(key, outRanking);
3044                 assertTrue(outRanking.isConversation());
3045                 assertEquals(SHARE_SHORTCUT_ID, outRanking.getConversationShortcutInfo().getId());
3046             }
3047         }
3048     }
3049 
3050     @Test
testDemoteConversationChannel()3051     public void testDemoteConversationChannel() {
3052         final NotificationChannel channel =
3053                 new NotificationChannel(mId, "Messages", IMPORTANCE_DEFAULT);
3054 
3055         String conversationId = "person a";
3056 
3057         final NotificationChannel conversationChannel =
3058                 new NotificationChannel(mId + "child",
3059                         "Messages from " + conversationId, IMPORTANCE_DEFAULT);
3060         conversationChannel.setConversationId(channel.getId(), conversationId);
3061 
3062         mNotificationManager.createNotificationChannel(channel);
3063         mNotificationManager.createNotificationChannel(conversationChannel);
3064 
3065         conversationChannel.setDemoted(true);
3066 
3067         SystemUtil.runWithShellPermissionIdentity(() ->
3068                 mNotificationManager.updateNotificationChannel(
3069                         mContext.getPackageName(), android.os.Process.myUid(), channel));
3070 
3071         assertEquals(false, mNotificationManager.getNotificationChannel(
3072                 channel.getId(), conversationId).isDemoted());
3073     }
3074 
3075     @Test
testDeleteConversationChannels()3076     public void testDeleteConversationChannels() throws Exception {
3077         assumeFalse("NotificationListeners do not support visible background users",
3078                 mUserHelper.isVisibleBackgroundUser());
3079         setUpNotifListener();
3080         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
3081 
3082         createDynamicShortcut();
3083 
3084         final NotificationChannel channel =
3085                 new NotificationChannel(mId, "Messages", IMPORTANCE_DEFAULT);
3086 
3087         final NotificationChannel conversationChannel =
3088                 new NotificationChannel(mId + "child",
3089                         "Messages from " + SHARE_SHORTCUT_ID, IMPORTANCE_DEFAULT);
3090         conversationChannel.setConversationId(channel.getId(), SHARE_SHORTCUT_ID);
3091 
3092         mNotificationManager.createNotificationChannel(channel);
3093         mNotificationManager.createNotificationChannel(conversationChannel);
3094 
3095         mNotificationManager.notify(177, getConversationNotification().build());
3096 
3097         // wait for notification listener to receive notification
3098         postedLatch.await(500, TimeUnit.MILLISECONDS);
3099         assertNotNull(mNotificationHelper.findPostedNotification(null, 177, SEARCH_TYPE.LISTENER));
3100         assertEquals(1, mListener.mPosted.size());
3101 
3102         deleteShortcuts();
3103 
3104         Thread.sleep(300); // wait for deletion to propagate
3105 
3106         assertFalse(mNotificationManager.getNotificationChannel(channel.getId(),
3107                 conversationChannel.getConversationId()).isConversation());
3108 
3109     }
3110 
3111     /**
3112      * This method verifies that an app can't bypass background restrictions by retrieving their own
3113      * notification and triggering it.
3114      */
3115     @AsbSecurityTest(cveBugId = 185388103)
3116     @Test
testActivityStartFromRetrievedNotification_isBlocked()3117     public void testActivityStartFromRetrievedNotification_isBlocked() throws Exception {
3118         deactivateGracePeriod();
3119         EventCallback callback = new EventCallback();
3120         int notificationId = 6007;
3121 
3122         // Post notification and fire its pending intent
3123         sendTrampolineMessage(TRAMPOLINE_SERVICE_API_30, MESSAGE_SERVICE_NOTIFICATION,
3124                 notificationId, callback);
3125         PollingCheck.waitFor(TIMEOUT_MS, () -> uncheck(() -> {
3126             sendTrampolineMessage(TRAMPOLINE_SERVICE, MESSAGE_CLICK_NOTIFICATION, notificationId,
3127                     callback);
3128             // timeoutMs = 1ms below because surrounding waitFor already handles retry & timeout.
3129             return callback.waitFor(EventCallback.NOTIFICATION_CLICKED, /* timeoutMs */ 1);
3130         }));
3131 
3132         assertFalse("Activity start should have been blocked",
3133                 callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
3134     }
3135 
3136     @Test
testActivityStartOnBroadcastTrampoline_isBlocked()3137     public void testActivityStartOnBroadcastTrampoline_isBlocked() throws Exception {
3138         assumeFalse("NotificationListeners do not support visible background users",
3139                 mUserHelper.isVisibleBackgroundUser());
3140         deactivateGracePeriod();
3141         setUpNotifListener();
3142         mListener.addTestPackage(TRAMPOLINE_APP);
3143         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
3144         EventCallback callback = new EventCallback();
3145         int notificationId = 6001;
3146 
3147         // Post notification and fire its pending intent
3148         sendTrampolineMessage(TRAMPOLINE_SERVICE, MESSAGE_BROADCAST_NOTIFICATION, notificationId,
3149                 callback);
3150         postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
3151         StatusBarNotification statusBarNotification = mNotificationHelper.findPostedNotification(
3152                 null, notificationId, SEARCH_TYPE.LISTENER);
3153         assertNotNull("Notification not posted on time", statusBarNotification);
3154         statusBarNotification.getNotification().contentIntent.send();
3155 
3156         assertTrue("Broadcast not received on time",
3157                 callback.waitFor(EventCallback.BROADCAST_RECEIVED, TIMEOUT_LONG_MS));
3158         assertFalse("Activity start should have been blocked",
3159                 callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
3160     }
3161 
3162     @Test
testActivityStartOnServiceTrampoline_isBlocked()3163     public void testActivityStartOnServiceTrampoline_isBlocked() throws Exception {
3164         assumeFalse("NotificationListeners do not support visible background users",
3165                 mUserHelper.isVisibleBackgroundUser());
3166         deactivateGracePeriod();
3167         setUpNotifListener();
3168         mListener.addTestPackage(TRAMPOLINE_APP);
3169         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
3170         EventCallback callback = new EventCallback();
3171         int notificationId = 6002;
3172 
3173         // Post notification and fire its pending intent
3174         sendTrampolineMessage(TRAMPOLINE_SERVICE, MESSAGE_SERVICE_NOTIFICATION, notificationId,
3175                 callback);
3176         postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
3177         StatusBarNotification statusBarNotification = mNotificationHelper.findPostedNotification(
3178                 null, notificationId, SEARCH_TYPE.LISTENER);
3179         assertNotNull("Notification not posted on time", statusBarNotification);
3180         statusBarNotification.getNotification().contentIntent.send();
3181 
3182         assertTrue("Service not started on time",
3183                 callback.waitFor(EventCallback.SERVICE_STARTED, TIMEOUT_MS));
3184         assertFalse("Activity start should have been blocked",
3185                 callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
3186     }
3187 
3188     @Test
testActivityStartOnBroadcastTrampoline_whenApi30_isAllowed()3189     public void testActivityStartOnBroadcastTrampoline_whenApi30_isAllowed() throws Exception {
3190         assumeFalse("NotificationListeners do not support visible background users",
3191                 mUserHelper.isVisibleBackgroundUser());
3192         deactivateGracePeriod();
3193         setUpNotifListener();
3194         mListener.addTestPackage(TRAMPOLINE_APP_API_30);
3195         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
3196         EventCallback callback = new EventCallback();
3197         int notificationId = 6003;
3198 
3199         // Post notification and fire its pending intent
3200         sendTrampolineMessage(TRAMPOLINE_SERVICE_API_30, MESSAGE_BROADCAST_NOTIFICATION,
3201                 notificationId, callback);
3202         postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
3203         StatusBarNotification statusBarNotification = mNotificationHelper.findPostedNotification(
3204                 null, notificationId, SEARCH_TYPE.LISTENER);
3205         assertNotNull("Notification not posted on time", statusBarNotification);
3206         statusBarNotification.getNotification().contentIntent.send();
3207 
3208         assertTrue("Broadcast not received on time",
3209                 callback.waitFor(EventCallback.BROADCAST_RECEIVED, TIMEOUT_LONG_MS));
3210         assertTrue("Activity not started",
3211                 callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
3212     }
3213 
3214     @Test
testActivityStartOnServiceTrampoline_whenApi30_isAllowed()3215     public void testActivityStartOnServiceTrampoline_whenApi30_isAllowed() throws Exception {
3216         assumeFalse("NotificationListeners do not support visible background users",
3217                 mUserHelper.isVisibleBackgroundUser());
3218         deactivateGracePeriod();
3219         setUpNotifListener();
3220         mListener.addTestPackage(TRAMPOLINE_APP_API_30);
3221         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
3222         EventCallback callback = new EventCallback();
3223         int notificationId = 6004;
3224 
3225         // Post notification and fire its pending intent
3226         sendTrampolineMessage(TRAMPOLINE_SERVICE_API_30, MESSAGE_SERVICE_NOTIFICATION,
3227                 notificationId, callback);
3228         postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
3229         StatusBarNotification statusBarNotification = mNotificationHelper.findPostedNotification(
3230                 null, notificationId, SEARCH_TYPE.LISTENER);
3231         assertNotNull("Notification not posted on time", statusBarNotification);
3232         statusBarNotification.getNotification().contentIntent.send();
3233 
3234         assertTrue("Service not started on time",
3235                 callback.waitFor(EventCallback.SERVICE_STARTED, TIMEOUT_MS));
3236         assertTrue("Activity not started",
3237                 callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
3238     }
3239 
3240     @Test
testActivityStartOnBroadcastTrampoline_whenDefaultBrowser_isBlocked()3241     public void testActivityStartOnBroadcastTrampoline_whenDefaultBrowser_isBlocked()
3242             throws Exception {
3243         assumeFalse("NotificationListeners do not support visible background users",
3244                 mUserHelper.isVisibleBackgroundUser());
3245         deactivateGracePeriod();
3246         setDefaultBrowser(TRAMPOLINE_APP);
3247         setUpNotifListener();
3248         mListener.addTestPackage(TRAMPOLINE_APP);
3249         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
3250         EventCallback callback = new EventCallback();
3251         int notificationId = 6005;
3252 
3253         // Post notification and fire its pending intent
3254         sendTrampolineMessage(TRAMPOLINE_SERVICE, MESSAGE_BROADCAST_NOTIFICATION, notificationId,
3255                 callback);
3256         postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
3257         StatusBarNotification statusBarNotification = mNotificationHelper.findPostedNotification(
3258                 null, notificationId, SEARCH_TYPE.LISTENER);
3259         assertNotNull("Notification not posted on time", statusBarNotification);
3260         statusBarNotification.getNotification().contentIntent.send();
3261 
3262         assertTrue("Broadcast not received on time",
3263                 callback.waitFor(EventCallback.BROADCAST_RECEIVED, TIMEOUT_LONG_MS));
3264         assertFalse("Activity started",
3265                 callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
3266     }
3267 
3268     @Test
testActivityStartOnBroadcastTrampoline_whenDefaultBrowserApi32_isAllowed()3269     public void testActivityStartOnBroadcastTrampoline_whenDefaultBrowserApi32_isAllowed()
3270             throws Exception {
3271         assumeFalse("NotificationListeners do not support visible background users",
3272                 mUserHelper.isVisibleBackgroundUser());
3273         deactivateGracePeriod();
3274         setDefaultBrowser(TRAMPOLINE_APP_API_32);
3275         setUpNotifListener();
3276         mListener.addTestPackage(TRAMPOLINE_APP_API_32);
3277         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
3278         EventCallback callback = new EventCallback();
3279         int notificationId = 6005;
3280 
3281         // Post notification and fire its pending intent
3282         sendTrampolineMessage(TRAMPOLINE_SERVICE_API_32, MESSAGE_BROADCAST_NOTIFICATION,
3283                 notificationId, callback);
3284         postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
3285         StatusBarNotification statusBarNotification = mNotificationHelper.findPostedNotification(
3286                 null, notificationId, SEARCH_TYPE.LISTENER);
3287         assertNotNull("Notification not posted on time", statusBarNotification);
3288         statusBarNotification.getNotification().contentIntent.send();
3289 
3290         assertTrue("Broadcast not received on time",
3291                 callback.waitFor(EventCallback.BROADCAST_RECEIVED, TIMEOUT_LONG_MS));
3292         assertTrue("Activity not started",
3293                 callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
3294     }
3295 
3296     @Test
testActivityStartOnServiceTrampoline_whenDefaultBrowser_isBlocked()3297     public void testActivityStartOnServiceTrampoline_whenDefaultBrowser_isBlocked()
3298             throws Exception {
3299         assumeFalse("NotificationListeners do not support visible background users",
3300                 mUserHelper.isVisibleBackgroundUser());
3301         deactivateGracePeriod();
3302         setDefaultBrowser(TRAMPOLINE_APP);
3303         setUpNotifListener();
3304         mListener.addTestPackage(TRAMPOLINE_APP);
3305         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
3306         EventCallback callback = new EventCallback();
3307         int notificationId = 6006;
3308 
3309         // Post notification and fire its pending intent
3310         sendTrampolineMessage(TRAMPOLINE_SERVICE, MESSAGE_SERVICE_NOTIFICATION, notificationId,
3311                 callback);
3312         postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
3313         StatusBarNotification statusBarNotification = mNotificationHelper.findPostedNotification(
3314                 null, notificationId, SEARCH_TYPE.LISTENER);
3315         assertNotNull("Notification not posted on time", statusBarNotification);
3316         statusBarNotification.getNotification().contentIntent.send();
3317 
3318         assertTrue("Service not started on time",
3319                 callback.waitFor(EventCallback.SERVICE_STARTED, TIMEOUT_MS));
3320         assertFalse("Activity started",
3321                 callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
3322     }
3323 
3324     @Test
testActivityStartOnServiceTrampoline_whenDefaultBrowserApi32_isAllowed()3325     public void testActivityStartOnServiceTrampoline_whenDefaultBrowserApi32_isAllowed()
3326             throws Exception {
3327         assumeFalse("NotificationListeners do not support visible background users",
3328                 mUserHelper.isVisibleBackgroundUser());
3329         deactivateGracePeriod();
3330         setDefaultBrowser(TRAMPOLINE_APP_API_32);
3331         setUpNotifListener();
3332         mListener.addTestPackage(TRAMPOLINE_APP_API_32);
3333         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
3334         EventCallback callback = new EventCallback();
3335         int notificationId = 6006;
3336 
3337         // Post notification and fire its pending intent
3338         sendTrampolineMessage(TRAMPOLINE_SERVICE_API_32, MESSAGE_SERVICE_NOTIFICATION,
3339                 notificationId, callback);
3340         postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
3341         StatusBarNotification statusBarNotification = mNotificationHelper.findPostedNotification(
3342                 null, notificationId, SEARCH_TYPE.LISTENER);
3343         assertNotNull("Notification not posted on time", statusBarNotification);
3344         statusBarNotification.getNotification().contentIntent.send();
3345 
3346         assertTrue("Service not started on time",
3347                 callback.waitFor(EventCallback.SERVICE_STARTED, TIMEOUT_MS));
3348         assertTrue("Activity not started",
3349                 callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
3350     }
3351 
3352     @Test
testGrantRevokeNotificationManagerApis_works()3353     public void testGrantRevokeNotificationManagerApis_works() {
3354         assumeFalse("NotificationListeners do not support visible background users",
3355                 mUserHelper.isVisibleBackgroundUser());
3356         SystemUtil.runWithShellPermissionIdentity(() -> {
3357             ComponentName componentName =
3358                     new ComponentName(STUB_PACKAGE_NAME, TestNotificationListener.class.getName());
3359             mNotificationManager.setNotificationListenerAccessGranted(
3360                     componentName, true, true);
3361 
3362             assertThat(
3363                     mNotificationManager.getEnabledNotificationListeners(),
3364                     hasItem(componentName));
3365 
3366             mNotificationManager.setNotificationListenerAccessGranted(
3367                     componentName, false, false);
3368 
3369             assertThat(
3370                     "Non-user-set changes should not override user-set",
3371                     mNotificationManager.getEnabledNotificationListeners(),
3372                     hasItem(componentName));
3373         });
3374     }
3375 
3376     @Test
testGrantRevokeNotificationManagerApis_exclusiveToPermissionController()3377     public void testGrantRevokeNotificationManagerApis_exclusiveToPermissionController() {
3378         List<PackageInfo> allPackages = mPackageManager.getInstalledPackages(
3379                 PackageManager.MATCH_DISABLED_COMPONENTS
3380                         | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS);
3381         List<String> allowedPackages = Arrays.asList(
3382                 mPackageManager.getPermissionControllerPackageName(),
3383                 "com.android.shell");
3384         StringBuilder sb = new StringBuilder();
3385         for (PackageInfo pkg : allPackages) {
3386             if (!pkg.applicationInfo.isSystemApp()
3387                     && mPackageManager.checkPermission(
3388                     Manifest.permission.MANAGE_NOTIFICATION_LISTENERS, pkg.packageName)
3389                     == PackageManager.PERMISSION_GRANTED
3390                     && !allowedPackages.contains(pkg.packageName)) {
3391                 sb.append(pkg.packageName + " can't hold "
3392                         + Manifest.permission.MANAGE_NOTIFICATION_LISTENERS + "\n");
3393             }
3394         }
3395         if (sb.length() > 0) {
3396             fail(sb.toString());
3397         }
3398     }
3399 
3400     @Test
testChannelDeletion_cancelReason()3401     public void testChannelDeletion_cancelReason() throws Exception {
3402         assumeFalse("NotificationListeners do not support visible background users",
3403                 mUserHelper.isVisibleBackgroundUser());
3404         setUpNotifListener();
3405         CountDownLatch notificationPostedLatch = mListener.setPostedCountDown(1);
3406 
3407         sendNotification(566, R.drawable.black);
3408         // wait for notification listener to receive notification
3409         notificationPostedLatch.await(500, TimeUnit.MILLISECONDS);
3410         assertEquals(1, mListener.mPosted.size());
3411         String key = mListener.mPosted.get(0).getKey();
3412 
3413         mNotificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
3414 
3415         assertEquals(NotificationListenerService.REASON_CHANNEL_REMOVED,
3416                 getCancellationReason(key));
3417     }
3418 
3419     @Test
testMediaStyle_setNoClearFlag()3420     public void testMediaStyle_setNoClearFlag() {
3421         // Test should only be run for build in V
3422         if (!SdkLevel.isAtLeastV()) {
3423             return;
3424         }
3425         if (!CompatChanges.isChangeEnabled(ENFORCE_NO_CLEAR_FLAG_ON_MEDIA_NOTIFICATION)) {
3426             Log.d(TAG, "Skipping testMediaStyle_setNoClearFlag(), SDK_INT="
3427                     + Build.VERSION.SDK_INT);
3428             return;
3429         }
3430         int id = 99;
3431         final Notification notification =
3432                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
3433                         .setSmallIcon(R.drawable.black)
3434                         .setStyle(new Notification.MediaStyle())
3435                         .build();
3436         mNotificationManager.notify(id, notification);
3437 
3438         StatusBarNotification sbn = mNotificationHelper.findPostedNotification(null, id,
3439                 SEARCH_TYPE.APP);
3440         assertNotNull(sbn);
3441 
3442         assertEquals(FLAG_NO_CLEAR, sbn.getNotification().flags & FLAG_NO_CLEAR);
3443     }
3444 
3445     @Test
testCustomMediaStyle_setNoClearFlag()3446     public void testCustomMediaStyle_setNoClearFlag() {
3447         // Test should only be run for build in V
3448         if (!SdkLevel.isAtLeastV()) {
3449             return;
3450         }
3451         if (!CompatChanges.isChangeEnabled(ENFORCE_NO_CLEAR_FLAG_ON_MEDIA_NOTIFICATION)) {
3452             Log.d(TAG, "Skipping testCustomMediaStyle_setNoClearFlag(), SDK_INT="
3453                     + Build.VERSION.SDK_INT);
3454             return;
3455         }
3456         int id = 99;
3457         final Notification notification =
3458                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
3459                         .setSmallIcon(R.drawable.black)
3460                         .setStyle(new Notification.DecoratedMediaCustomViewStyle())
3461                         .build();
3462         mNotificationManager.notify(id, notification);
3463 
3464         StatusBarNotification sbn = mNotificationHelper.findPostedNotification(null, id,
3465                 SEARCH_TYPE.APP);
3466         assertNotNull(sbn);
3467 
3468         assertEquals(FLAG_NO_CLEAR, sbn.getNotification().flags & FLAG_NO_CLEAR);
3469     }
3470 
3471     @Test
testMediaStyleRemotePlayback_noPermission()3472     public void testMediaStyleRemotePlayback_noPermission() throws Exception {
3473         int id = 99;
3474         final String deviceName = "device name";
3475         final int deviceIcon = 123;
3476         final PendingIntent deviceIntent = getPendingIntent();
3477         final Notification notification =
3478                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
3479                         .setSmallIcon(R.drawable.black)
3480                         .setStyle(new Notification.MediaStyle()
3481                                 .setRemotePlaybackInfo(deviceName, deviceIcon, deviceIntent))
3482                         .build();
3483         mNotificationManager.notify(id, notification);
3484 
3485         StatusBarNotification sbn = mNotificationHelper.findPostedNotification(null, id,
3486                 SEARCH_TYPE.APP);
3487         assertNotNull(sbn);
3488 
3489         assertFalse(sbn.getNotification().extras
3490                 .containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE));
3491         assertFalse(sbn.getNotification().extras
3492                 .containsKey(Notification.EXTRA_MEDIA_REMOTE_ICON));
3493         assertFalse(sbn.getNotification().extras
3494                 .containsKey(Notification.EXTRA_MEDIA_REMOTE_INTENT));
3495     }
3496 
3497     @Test
testMediaStyleRemotePlayback_hasPermission()3498     public void testMediaStyleRemotePlayback_hasPermission() throws Exception {
3499         int id = 99;
3500         final String deviceName = "device name";
3501         final int deviceIcon = 123;
3502         final PendingIntent deviceIntent = getPendingIntent();
3503         final Notification notification =
3504                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
3505                         .setSmallIcon(R.drawable.black)
3506                         .setStyle(new Notification.MediaStyle()
3507                                 .setRemotePlaybackInfo(deviceName, deviceIcon, deviceIntent))
3508                         .build();
3509 
3510         SystemUtil.runWithShellPermissionIdentity(() -> {
3511             mNotificationManager.notify(id, notification);
3512         }, android.Manifest.permission.MEDIA_CONTENT_CONTROL);
3513 
3514         StatusBarNotification sbn = mNotificationHelper.findPostedNotification(
3515                 null, id, SEARCH_TYPE.APP);
3516         assertNotNull(sbn);
3517         assertEquals(deviceName, sbn.getNotification().extras
3518                 .getString(Notification.EXTRA_MEDIA_REMOTE_DEVICE));
3519         assertEquals(deviceIcon, sbn.getNotification().extras
3520                 .getInt(Notification.EXTRA_MEDIA_REMOTE_ICON));
3521         assertEquals(deviceIntent, sbn.getNotification().extras
3522                 .getParcelable(Notification.EXTRA_MEDIA_REMOTE_INTENT));
3523     }
3524 
3525     @Test
testCustomMediaStyleRemotePlayback_noPermission()3526     public void testCustomMediaStyleRemotePlayback_noPermission() throws Exception {
3527         int id = 99;
3528         final String deviceName = "device name";
3529         final int deviceIcon = 123;
3530         final PendingIntent deviceIntent = getPendingIntent();
3531         final Notification notification =
3532                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
3533                         .setSmallIcon(R.drawable.black)
3534                         .setStyle(new Notification.DecoratedMediaCustomViewStyle()
3535                                 .setRemotePlaybackInfo(deviceName, deviceIcon, deviceIntent))
3536                         .build();
3537         mNotificationManager.notify(id, notification);
3538 
3539         StatusBarNotification sbn = mNotificationHelper.findPostedNotification(
3540                 null, id, SEARCH_TYPE.APP);
3541         assertNotNull(sbn);
3542 
3543         assertFalse(sbn.getNotification().extras
3544                 .containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE));
3545         assertFalse(sbn.getNotification().extras
3546                 .containsKey(Notification.EXTRA_MEDIA_REMOTE_ICON));
3547         assertFalse(sbn.getNotification().extras
3548                 .containsKey(Notification.EXTRA_MEDIA_REMOTE_INTENT));
3549     }
3550 
3551     @Test
testCustomMediaStyleRemotePlayback_hasPermission()3552     public void testCustomMediaStyleRemotePlayback_hasPermission() throws Exception {
3553         int id = 99;
3554         final String deviceName = "device name";
3555         final int deviceIcon = 123;
3556         final PendingIntent deviceIntent = getPendingIntent();
3557         final Notification notification =
3558                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
3559                         .setSmallIcon(R.drawable.black)
3560                         .setStyle(new Notification.DecoratedMediaCustomViewStyle()
3561                                 .setRemotePlaybackInfo(deviceName, deviceIcon, deviceIntent))
3562                         .build();
3563 
3564         SystemUtil.runWithShellPermissionIdentity(() -> {
3565             mNotificationManager.notify(id, notification);
3566         }, android.Manifest.permission.MEDIA_CONTENT_CONTROL);
3567 
3568         StatusBarNotification sbn = mNotificationHelper.findPostedNotification(
3569                 null, id, SEARCH_TYPE.APP);
3570         assertNotNull(sbn);
3571         assertEquals(deviceName, sbn.getNotification().extras
3572                 .getString(Notification.EXTRA_MEDIA_REMOTE_DEVICE));
3573         assertEquals(deviceIcon, sbn.getNotification().extras
3574                 .getInt(Notification.EXTRA_MEDIA_REMOTE_ICON));
3575         assertEquals(deviceIntent, sbn.getNotification().extras
3576                 .getParcelable(Notification.EXTRA_MEDIA_REMOTE_INTENT));
3577     }
3578 
3579     @Test
testNoPermission()3580     public void testNoPermission() throws Exception {
3581         assumeFalse(
3582                 "Permission for POST_NOTIFICATIONS is always granted on TV and cannot be revoked",
3583                 mPackageManager.hasSystemFeature(FEATURE_LEANBACK));
3584 
3585         int id = 7;
3586         SystemUtil.runWithShellPermissionIdentity(
3587                 () -> mContext.getSystemService(PermissionManager.class)
3588                         .revokePostNotificationPermissionWithoutKillForTest(
3589                                 mContext.getPackageName(),
3590                                 android.os.Process.myUserHandle().getIdentifier()),
3591                 REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL,
3592                 REVOKE_RUNTIME_PERMISSIONS);
3593 
3594         final Notification notification =
3595                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
3596                         .setSmallIcon(R.drawable.black)
3597                         .build();
3598         mNotificationManager.notify(id, notification);
3599 
3600         assertTrue(mNotificationHelper.isNotificationGone(id, SEARCH_TYPE.APP));
3601     }
3602 
3603     @Test
testIsAmbient()3604     public void testIsAmbient() throws Exception {
3605         assumeFalse("NotificationListeners do not support visible background users",
3606                 mUserHelper.isVisibleBackgroundUser());
3607         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
3608         assertNotNull(mListener);
3609         CountDownLatch postedLatch = mListener.setPostedCountDown(2);
3610 
3611         NotificationChannel lowChannel = new NotificationChannel(
3612                 "testIsAmbientLOW", "testIsAmbientLOW", IMPORTANCE_LOW);
3613         NotificationChannel minChannel = new NotificationChannel(
3614                 "testIsAmbientMIN", "testIsAmbientMIN", IMPORTANCE_MIN);
3615         mNotificationManager.createNotificationChannel(lowChannel);
3616         mNotificationManager.createNotificationChannel(minChannel);
3617 
3618         final Notification lowN =
3619                 new Notification.Builder(mContext, lowChannel.getId())
3620                         .setSmallIcon(R.drawable.black)
3621                         .build();
3622         final Notification minN =
3623                 new Notification.Builder(mContext, minChannel.getId())
3624                         .setSmallIcon(R.drawable.black)
3625                         .build();
3626         mNotificationManager.notify("lowN", 1, lowN);
3627         mNotificationManager.notify("minN", 1, minN);
3628 
3629         postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
3630         StatusBarNotification lowSbn = mNotificationHelper.findPostedNotification("lowN", 1,
3631                 SEARCH_TYPE.POSTED);
3632         StatusBarNotification minSbn = mNotificationHelper.findPostedNotification("minN", 1,
3633                 SEARCH_TYPE.POSTED);
3634 
3635         NotificationListenerService.RankingMap rankingMap = mListener.mRankingMap;
3636         NotificationListenerService.Ranking outRanking = new NotificationListenerService.Ranking();
3637 
3638         rankingMap.getRanking(lowSbn.getKey(), outRanking);
3639         assertFalse(outRanking.isAmbient());
3640 
3641         rankingMap.getRanking(minSbn.getKey(), outRanking);
3642         assertEquals(outRanking.getKey(), IMPORTANCE_MIN, outRanking.getChannel().getImportance());
3643         assertTrue(outRanking.isAmbient());
3644     }
3645 
3646     @Test
testFlagForegroundServiceNeedsRealFgs()3647     public void testFlagForegroundServiceNeedsRealFgs() throws Exception {
3648         assumeFalse("NotificationListeners do not support visible background users",
3649                 mUserHelper.isVisibleBackgroundUser());
3650         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
3651         assertNotNull(mListener);
3652         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
3653 
3654         final Notification n =
3655                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
3656                         .setSmallIcon(R.drawable.black)
3657                         .setFlag(FLAG_FOREGROUND_SERVICE, true)
3658                         .build();
3659         mNotificationManager.notify("testFlagForegroundServiceNeedsRealFgs", 1, n);
3660         postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
3661         StatusBarNotification sbn = mNotificationHelper.findPostedNotification(
3662                 "testFlagForegroundServiceNeedsRealFgs", 1, SEARCH_TYPE.POSTED);
3663 
3664         assertEquals(0, (sbn.getNotification().flags & FLAG_FOREGROUND_SERVICE));
3665     }
3666 
3667     @Test
testFlagUserInitiatedJobNeedsRealUij()3668     public void testFlagUserInitiatedJobNeedsRealUij() throws Exception {
3669         assumeFalse("NotificationListeners do not support visible background users",
3670                 mUserHelper.isVisibleBackgroundUser());
3671         mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
3672         assertNotNull(mListener);
3673         CountDownLatch postedLatch = mListener.setPostedCountDown(1);
3674 
3675         final Notification n =
3676                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
3677                         .setSmallIcon(R.drawable.black)
3678                         .setFlag(FLAG_USER_INITIATED_JOB, true)
3679                         .build();
3680         mNotificationManager.notify("testFlagUserInitiatedJobNeedsRealUij", 1, n);
3681         postedLatch.await(POST_TIMEOUT, TimeUnit.MILLISECONDS);
3682         StatusBarNotification sbn = mNotificationHelper.findPostedNotification(
3683                 "testFlagUserInitiatedJobNeedsRealUij", 1, SEARCH_TYPE.POSTED);
3684 
3685         assertFalse(sbn.getNotification().isUserInitiatedJob());
3686     }
3687 
3688     @Test
3689     @RequiresFlagsEnabled(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
testCallNotificationListener_registerCallback_noInteractAcrossUsersPermission()3690     public void testCallNotificationListener_registerCallback_noInteractAcrossUsersPermission()
3691             throws Exception {
3692         try {
3693             PermissionUtils.revokePermission(mContext.getPackageName(),
3694                     android.Manifest.permission.INTERACT_ACROSS_USERS);
3695 
3696             mNotificationManager.registerCallNotificationEventListener(mContext.getPackageName(),
3697                     UserHandle.SYSTEM, mContext.getMainExecutor(),
3698                     new CallNotificationEventListener() {
3699                     @Override
3700                     public void onCallNotificationPosted(String packageName, UserHandle user) {
3701                     }
3702                     @Override
3703                     public void onCallNotificationRemoved(String packageName, UserHandle user) {
3704                     }
3705                 });
3706             fail("registerCallNotificationListener should not succeed - privileged call");
3707         } catch (SecurityException e) {
3708             // Expected SecurityException
3709         } finally {
3710             PermissionUtils.grantPermission(mContext.getPackageName(),
3711                     android.Manifest.permission.INTERACT_ACROSS_USERS);
3712         }
3713     }
3714 
3715     @Test
3716     @RequiresFlagsEnabled(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
testCallNotificationListener_registerCallback_noAccessNotificationsPermission()3717     public void testCallNotificationListener_registerCallback_noAccessNotificationsPermission()
3718             throws Exception {
3719         try {
3720             PermissionUtils.setAppOp(mContext.getPackageName(),
3721                     android.Manifest.permission.ACCESS_NOTIFICATIONS, MODE_ERRORED);
3722 
3723             mNotificationManager.registerCallNotificationEventListener(mContext.getPackageName(),
3724                     UserHandle.SYSTEM, mContext.getMainExecutor(),
3725                     new CallNotificationEventListener() {
3726                     @Override
3727                     public void onCallNotificationPosted(String packageName, UserHandle user) {
3728                     }
3729                     @Override
3730                     public void onCallNotificationRemoved(String packageName, UserHandle user) {
3731                     }
3732                 });
3733             fail("registerCallNotificationListener should not succeed - privileged call");
3734         } catch (SecurityException e) {
3735             // Expected SecurityException
3736         } finally {
3737             PermissionUtils.setAppOp(mContext.getPackageName(),
3738                     android.Manifest.permission.ACCESS_NOTIFICATIONS, MODE_DEFAULT);
3739         }
3740     }
3741     @Test
3742     @RequiresFlagsEnabled(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
testCallNotificationListener_registerCallback_withPermission()3743     public void testCallNotificationListener_registerCallback_withPermission()
3744             throws Exception {
3745         try {
3746             mNotificationManager.registerCallNotificationEventListener(mContext.getPackageName(),
3747                     UserHandle.SYSTEM, mContext.getMainExecutor(),
3748                     new CallNotificationEventListener() {
3749                         @Override
3750                         public void onCallNotificationPosted(@NonNull String packageName,
3751                                 UserHandle user) {
3752                         }
3753                         @Override
3754                         public void onCallNotificationRemoved(@NonNull String packageName,
3755                                 UserHandle user) {
3756                         }
3757                     });
3758         } catch (SecurityException e) {
3759             fail("registerCallNotificationListener should succeed " + e);
3760         }
3761     }
3762 
3763     @Test
3764     @RequiresFlagsEnabled(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
testCallNotificationListener_callstyleNotificationPosted()3765     public void testCallNotificationListener_callstyleNotificationPosted() throws Exception {
3766         mNotificationManager.cancelAll();
3767         final Semaphore semaphore = new Semaphore(0);
3768         try {
3769             mNotificationManager.registerCallNotificationEventListener(mContext.getPackageName(),
3770                     mContext.getUser(), mContext.getMainExecutor(),
3771                     new CallNotificationEventListener() {
3772                     @Override
3773                     public void onCallNotificationPosted(String packageName, UserHandle userH) {
3774                         semaphore.release();
3775                     }
3776                     @Override
3777                     public void onCallNotificationRemoved(String packageName, UserHandle user) {
3778                         semaphore.release();
3779                     }
3780                 });
3781         } catch (SecurityException e) {
3782             fail("registerCallNotificationListener should succeed " + e);
3783         }
3784 
3785         // Post a CallStyle notification
3786         final int id = 4242;
3787         mNotificationManager.notify(id, getCallStyleNotification(id)
3788                 .setFullScreenIntent(getPendingIntent(), false).build());
3789 
3790         // Check that onCallNotificationPosted is called
3791         try {
3792             if (!semaphore.tryAcquire(5, TimeUnit.SECONDS)) {
3793                 fail("onCallNotificationPosted notification callback failed");
3794             }
3795         } catch (InterruptedException e) {
3796             fail("notification callback failed " + e);
3797         }
3798 
3799         // Check that onCallNotificationRemoved is called
3800         semaphore.release();
3801         mNotificationManager.cancel(id);
3802         try {
3803             if (!semaphore.tryAcquire(5, TimeUnit.SECONDS)) {
3804                 fail("onCallNotificationRemoved notification callback failed");
3805             }
3806         } catch (InterruptedException e) {
3807             fail("notification callback failed " + e);
3808         }
3809     }
3810 
3811     @Test
3812     @RequiresFlagsEnabled(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
testCallNotificationListener_nonCallStyleNotificationPosted()3813     public void testCallNotificationListener_nonCallStyleNotificationPosted()
3814             throws Exception {
3815         final Semaphore semaphore = new Semaphore(0);
3816         try {
3817             mNotificationManager.registerCallNotificationEventListener(mContext.getPackageName(),
3818                     mContext.getUser(), mContext.getMainExecutor(),
3819                     new CallNotificationEventListener() {
3820                     @Override
3821                         public void onCallNotificationPosted(String packageName, UserHandle user) {
3822                             semaphore.release();
3823                         }
3824                         @Override
3825                         public void onCallNotificationRemoved(String packageName, UserHandle user) {
3826                         }
3827                     });
3828         } catch (SecurityException e) {
3829             fail("registerCallNotificationListener should succeed " + e);
3830         }
3831 
3832         // Post a non-CallStyle (conversation) notification
3833         final int id = 4242;
3834         mNotificationManager.notify(id, getConversationNotification().build());
3835 
3836         // Check that CallNotificationListener is not called
3837         try {
3838             if (semaphore.tryAcquire(5, TimeUnit.SECONDS)) {
3839                 fail("notification callback should fail!");
3840             }
3841         } catch (InterruptedException e) {
3842         }
3843     }
3844 
3845     @Test
3846     @RequiresFlagsEnabled(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
testCallNotificationListener_unregisterListener()3847     public void testCallNotificationListener_unregisterListener() throws Exception {
3848         final Semaphore semaphore = new Semaphore(0);
3849         final CallNotificationEventListener listener = new CallNotificationEventListener() {
3850                 @Override
3851                 public void onCallNotificationPosted(String packageName, UserHandle user) {
3852                     semaphore.release();
3853                 }
3854                 @Override
3855                 public void onCallNotificationRemoved(String packageName, UserHandle user) {
3856                 }
3857         };
3858 
3859         try {
3860             mNotificationManager.registerCallNotificationEventListener(mContext.getPackageName(),
3861                     mContext.getUser(), mContext.getMainExecutor(), listener);
3862         } catch (SecurityException e) {
3863             fail("registerCallNotificationListener should succeed " + e);
3864         }
3865 
3866         try {
3867             mNotificationManager.unregisterCallNotificationEventListener(listener);
3868         } catch (SecurityException e) {
3869             fail("unregisterCallNotificationListener should succeed " + e);
3870         }
3871 
3872         // Post a CallStyle notification
3873         final int id = 4242;
3874         mNotificationManager.notify(id, getCallStyleNotification(id)
3875                 .setFullScreenIntent(getPendingIntent(), false).build());
3876 
3877         // Check that onCallNotificationPosted is not called
3878         try {
3879             if (semaphore.tryAcquire(5, TimeUnit.SECONDS)) {
3880                 fail("notification callback should fail!");
3881             }
3882         } catch (InterruptedException e) {
3883         }
3884     }
3885 
3886     @Test
3887     @Ignore("b/409850649")
3888     @RequiresFlagsEnabled(android.app.Flags.FLAG_API_RICH_ONGOING)
testCanPostPromotedNotifications()3889     public void testCanPostPromotedNotifications() {
3890         boolean initialValue = mNotificationManager.canPostPromotedNotifications();
3891 
3892         try {
3893             SystemUtil.runWithShellPermissionIdentity(() -> {
3894                 mNotificationManager.setCanPostPromotedNotifications(
3895                         mContext.getPackageName(), android.os.Process.myUid(), true);
3896             });
3897 
3898             assertThat(mNotificationManager.canPostPromotedNotifications()).isTrue();
3899 
3900             SystemUtil.runWithShellPermissionIdentity(() -> {
3901                 mNotificationManager.setCanPostPromotedNotifications(
3902                         mContext.getPackageName(), android.os.Process.myUid(), false);
3903             });
3904 
3905             assertThat(mNotificationManager.canPostPromotedNotifications()).isFalse();
3906 
3907         } finally {
3908             SystemUtil.runWithShellPermissionIdentity(() -> {
3909                 mNotificationManager.setCanPostPromotedNotifications(
3910                         mContext.getPackageName(), android.os.Process.myUid(), initialValue);
3911             });
3912         }
3913     }
3914 
3915     private static class EventCallback extends Handler {
3916         private static final int BROADCAST_RECEIVED = 1;
3917         private static final int SERVICE_STARTED = 2;
3918         private static final int ACTIVITY_STARTED = 3;
3919         private static final int NOTIFICATION_CLICKED = 4;
3920 
3921         private final Map<Integer, CompletableFuture<Integer>> mEvents =
3922                 Collections.synchronizedMap(new ArrayMap<>());
3923 
EventCallback()3924         private EventCallback() {
3925             super(Looper.getMainLooper());
3926         }
3927 
3928         @Override
handleMessage(Message message)3929         public void handleMessage(Message message) {
3930             mEvents.computeIfAbsent(message.what, e -> new CompletableFuture<>()).obtrudeValue(
3931                     message.arg1);
3932         }
3933 
waitFor(int event, long timeoutMs)3934         public boolean waitFor(int event, long timeoutMs) {
3935             try {
3936                 return mEvents.computeIfAbsent(event, e -> new CompletableFuture<>()).get(timeoutMs,
3937                         TimeUnit.MILLISECONDS) == 0;
3938             } catch (InterruptedException | ExecutionException | TimeoutException e) {
3939                 return false;
3940             }
3941         }
3942     }
3943 }
3944