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