1 /* 2 * Copyright (C) 2020 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 package com.android.server.notification; 17 18 import static android.Manifest.permission.RECEIVE_SENSITIVE_NOTIFICATIONS; 19 import static android.content.pm.PackageManager.MATCH_ANY_USER; 20 import static android.permission.PermissionManager.PERMISSION_GRANTED; 21 import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS; 22 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; 23 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS; 24 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING; 25 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_SILENT; 26 27 import static com.android.server.notification.NotificationManagerService.NotificationListeners.TAG_REQUESTED_LISTENERS; 28 29 import static com.google.common.truth.Truth.assertThat; 30 31 import static junit.framework.Assert.assertFalse; 32 import static junit.framework.Assert.assertTrue; 33 34 import static org.junit.Assert.fail; 35 import static org.mockito.ArgumentMatchers.any; 36 import static org.mockito.ArgumentMatchers.anyInt; 37 import static org.mockito.ArgumentMatchers.anyString; 38 import static org.mockito.ArgumentMatchers.eq; 39 import static org.mockito.ArgumentMatchers.intThat; 40 import static org.mockito.ArgumentMatchers.nullable; 41 import static org.mockito.Mockito.atLeast; 42 import static org.mockito.Mockito.doAnswer; 43 import static org.mockito.Mockito.doNothing; 44 import static org.mockito.Mockito.doReturn; 45 import static org.mockito.Mockito.mock; 46 import static org.mockito.Mockito.never; 47 import static org.mockito.Mockito.reset; 48 import static org.mockito.Mockito.spy; 49 import static org.mockito.Mockito.times; 50 import static org.mockito.Mockito.verify; 51 import static org.mockito.Mockito.when; 52 53 import android.annotation.SuppressLint; 54 import android.app.INotificationManager; 55 import android.app.Notification; 56 import android.app.NotificationChannel; 57 import android.app.NotificationChannelGroup; 58 import android.app.NotificationManager; 59 import android.companion.AssociationInfo; 60 import android.companion.ICompanionDeviceManager; 61 import android.content.ComponentName; 62 import android.content.pm.IPackageManager; 63 import android.content.pm.PackageManager; 64 import android.content.pm.ServiceInfo; 65 import android.content.pm.VersionedPackage; 66 import android.content.res.Resources; 67 import android.os.Bundle; 68 import android.os.Parcel; 69 import android.os.RemoteException; 70 import android.os.UserHandle; 71 import android.platform.test.flag.junit.SetFlagsRule; 72 import android.service.notification.INotificationListener; 73 import android.service.notification.IStatusBarNotificationHolder; 74 import android.service.notification.NotificationListenerFilter; 75 import android.service.notification.NotificationListenerService; 76 import android.service.notification.NotificationRankingUpdate; 77 import android.service.notification.NotificationStats; 78 import android.service.notification.StatusBarNotification; 79 import android.testing.TestableContext; 80 import android.util.ArraySet; 81 import android.util.Pair; 82 import android.util.Xml; 83 84 import com.android.modules.utils.TypedXmlPullParser; 85 import com.android.modules.utils.TypedXmlSerializer; 86 import com.android.server.UiServiceTestCase; 87 import com.android.server.pm.pkg.PackageStateInternal; 88 89 import com.google.common.collect.ImmutableList; 90 91 import org.junit.Before; 92 import org.junit.Rule; 93 import org.junit.Test; 94 import org.mockito.ArgumentCaptor; 95 import org.mockito.ArgumentMatcher; 96 import org.mockito.Mock; 97 import org.mockito.MockitoAnnotations; 98 import org.mockito.internal.util.reflection.FieldSetter; 99 100 import java.io.BufferedInputStream; 101 import java.io.BufferedOutputStream; 102 import java.io.ByteArrayInputStream; 103 import java.io.ByteArrayOutputStream; 104 import java.util.ArrayList; 105 import java.util.Arrays; 106 import java.util.List; 107 import java.util.concurrent.CountDownLatch; 108 109 @SuppressLint("GuardedBy") 110 public class NotificationListenersTest extends UiServiceTestCase { 111 112 @Rule 113 public SetFlagsRule mSetFlagsRule = new SetFlagsRule(); 114 115 @Mock 116 private PackageManager mPm; 117 @Mock 118 private IPackageManager miPm; 119 @Mock 120 private Resources mResources; 121 122 // mNm is going to be a spy, so it must use doReturn.when, not when.thenReturn, as 123 // when.thenReturn will result in the real method being called 124 NotificationManagerService mNm; 125 @Mock 126 private INotificationManager mINm; 127 private TestableContext mContext = spy(getContext()); 128 129 NotificationManagerService.NotificationListeners mListeners; 130 131 private int mUid1 = 98989; 132 private ComponentName mCn1 = new ComponentName("pkg", "pkg.cmp"); 133 private ComponentName mCn2 = new ComponentName("pkg2", "pkg2.cmp2"); 134 private ComponentName mUninstalledComponent = new ComponentName("pkg3", 135 "pkg3.NotificationListenerService"); 136 137 @Before setUp()138 public void setUp() throws Exception { 139 mNm = spy(new NotificationManagerService(mContext)); 140 MockitoAnnotations.initMocks(this); 141 getContext().setMockPackageManager(mPm); 142 doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any()); 143 144 doReturn(true).when(mNm).isInteractionVisibleToListener(any(), anyInt()); 145 146 mListeners = spy(mNm.new NotificationListeners( 147 mContext, new Object(), mock(ManagedServices.UserProfiles.class), miPm)); 148 when(mNm.getBinderService()).thenReturn(mINm); 149 mNm.mPackageManager = mock(IPackageManager.class); 150 PackageStateInternal psi = mock(PackageStateInternal.class); 151 mNm.mPackageManagerInternal = mPmi; 152 when(psi.getAppId()).thenReturn(mUid1); 153 when(mNm.mPackageManagerInternal.getPackageStateInternal(any())).thenReturn(psi); 154 mNm.mCompanionManager = mock(ICompanionDeviceManager.class); 155 when(mNm.mCompanionManager.getAllAssociationsForUser(anyInt())) 156 .thenReturn(new ArrayList<>()); 157 mNm.mHandler = mock(NotificationManagerService.WorkerHandler.class); 158 mNm.mAssistants = mock(NotificationManagerService.NotificationAssistants.class); 159 FieldSetter.setField(mNm, 160 NotificationManagerService.class.getDeclaredField("mListeners"), 161 mListeners); 162 doReturn(android.service.notification.NotificationListenerService.TRIM_FULL) 163 .when(mListeners).getOnNotificationPostedTrim(any()); 164 } 165 166 @Test testReadExtraTag()167 public void testReadExtraTag() throws Exception { 168 String xml = "<" + TAG_REQUESTED_LISTENERS + ">" 169 + "<listener component=\"" + mCn1.flattenToString() + "\" user=\"0\">" 170 + "<allowed types=\"7\" />" 171 + "</listener>" 172 + "<listener component=\"" + mCn2.flattenToString() + "\" user=\"10\">" 173 + "<allowed types=\"4\" />" 174 + "<disallowed pkg=\"pkg1\" uid=\"243\"/>" 175 + "</listener>" 176 + "</" + TAG_REQUESTED_LISTENERS + ">"; 177 178 TypedXmlPullParser parser = Xml.newFastPullParser(); 179 parser.setInput(new BufferedInputStream( 180 new ByteArrayInputStream(xml.getBytes())), null); 181 parser.nextTag(); 182 mListeners.readExtraTag(TAG_REQUESTED_LISTENERS, parser); 183 184 validateListenersFromXml(); 185 } 186 187 @Test loadDefaultsFromConfig_forHeadlessSystemUser_loadUninstalled()188 public void loadDefaultsFromConfig_forHeadlessSystemUser_loadUninstalled() throws Exception { 189 // setup with headless system user mode 190 mListeners = spy(mNm.new NotificationListeners( 191 mContext, new Object(), mock(ManagedServices.UserProfiles.class), miPm, 192 /* isHeadlessSystemUserMode= */ true)); 193 mockDefaultListenerConfigForUninstalledComponent(mUninstalledComponent); 194 195 mListeners.loadDefaultsFromConfig(); 196 197 assertThat(mListeners.getDefaultComponents()).contains(mUninstalledComponent); 198 } 199 200 @Test loadDefaultsFromConfig_forNonHeadlessSystemUser_ignoreUninstalled()201 public void loadDefaultsFromConfig_forNonHeadlessSystemUser_ignoreUninstalled() 202 throws Exception { 203 // setup without headless system user mode 204 mListeners = spy(mNm.new NotificationListeners( 205 mContext, new Object(), mock(ManagedServices.UserProfiles.class), miPm, 206 /* isHeadlessSystemUserMode= */ false)); 207 mockDefaultListenerConfigForUninstalledComponent(mUninstalledComponent); 208 209 mListeners.loadDefaultsFromConfig(); 210 211 assertThat(mListeners.getDefaultComponents()).doesNotContain(mUninstalledComponent); 212 } 213 mockDefaultListenerConfigForUninstalledComponent(ComponentName componentName)214 private void mockDefaultListenerConfigForUninstalledComponent(ComponentName componentName) { 215 ArraySet<ComponentName> components = new ArraySet<>(Arrays.asList(componentName)); 216 when(mResources 217 .getString( 218 com.android.internal.R.string.config_defaultListenerAccessPackages)) 219 .thenReturn(componentName.getPackageName()); 220 when(mContext.getResources()).thenReturn(mResources); 221 doReturn(components).when(mListeners).queryPackageForServices( 222 eq(componentName.getPackageName()), 223 intThat(hasIntBitFlag(MATCH_ANY_USER)), 224 anyInt()); 225 } 226 hasIntBitFlag(int flag)227 public static ArgumentMatcher<Integer> hasIntBitFlag(int flag) { 228 return arg -> arg != null && ((arg & flag) == flag); 229 } 230 231 @Test testWriteExtraTag()232 public void testWriteExtraTag() throws Exception { 233 NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>()); 234 VersionedPackage a1 = new VersionedPackage("pkg1", 243); 235 NotificationListenerFilter nlf2 = 236 new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[]{a1})); 237 mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf); 238 mListeners.setNotificationListenerFilter(Pair.create(mCn2, 10), nlf2); 239 240 TypedXmlSerializer serializer = Xml.newFastSerializer(); 241 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 242 serializer.setOutput(new BufferedOutputStream(baos), "utf-8"); 243 serializer.startDocument(null, true); 244 mListeners.writeExtraXmlTags(serializer); 245 serializer.endDocument(); 246 serializer.flush(); 247 248 TypedXmlPullParser parser = Xml.newFastPullParser(); 249 parser.setInput(new BufferedInputStream( 250 new ByteArrayInputStream(baos.toByteArray())), null); 251 parser.nextTag(); 252 mListeners.readExtraTag("req_listeners", parser); 253 254 validateListenersFromXml(); 255 } 256 validateListenersFromXml()257 private void validateListenersFromXml() { 258 assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn1, 0)).getTypes()) 259 .isEqualTo(7); 260 assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn1, 0)) 261 .getDisallowedPackages()) 262 .isEmpty(); 263 264 assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 10)).getTypes()) 265 .isEqualTo(4); 266 VersionedPackage a1 = new VersionedPackage("pkg1", 243); 267 assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 10)) 268 .getDisallowedPackages()) 269 .contains(a1); 270 } 271 272 @Test testOnUserRemoved()273 public void testOnUserRemoved() { 274 NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>()); 275 VersionedPackage a1 = new VersionedPackage("pkg1", 243); 276 NotificationListenerFilter nlf2 = 277 new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1})); 278 mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf); 279 mListeners.setNotificationListenerFilter(Pair.create(mCn2, 10), nlf2); 280 281 mListeners.onUserRemoved(0); 282 283 assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn1, 0))).isNull(); 284 assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 10)).getTypes()) 285 .isEqualTo(4); 286 } 287 288 @Test testEnsureFilters_newServiceNoMetadata()289 public void testEnsureFilters_newServiceNoMetadata() { 290 ServiceInfo si = new ServiceInfo(); 291 si.packageName = "new2"; 292 si.name = "comp2"; 293 294 mListeners.ensureFilters(si, 0); 295 296 assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0))).isNull(); 297 } 298 299 @Test testEnsureFilters_preExisting()300 public void testEnsureFilters_preExisting() { 301 // one exists already, say from xml 302 VersionedPackage a1 = new VersionedPackage("pkg1", 243); 303 NotificationListenerFilter nlf = 304 new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1})); 305 mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf); 306 ServiceInfo siOld = new ServiceInfo(); 307 siOld.packageName = mCn2.getPackageName(); 308 siOld.name = mCn2.getClassName(); 309 310 mListeners.ensureFilters(siOld, 0); 311 312 assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0))).isEqualTo(nlf); 313 } 314 315 @Test testEnsureFilters_newServiceWithMetadata()316 public void testEnsureFilters_newServiceWithMetadata() { 317 ServiceInfo si = new ServiceInfo(); 318 si.packageName = "new"; 319 si.name = "comp"; 320 si.metaData = new Bundle(); 321 si.metaData.putString(NotificationListenerService.META_DATA_DEFAULT_FILTER_TYPES, "1|2"); 322 323 mListeners.ensureFilters(si, 0); 324 325 assertThat(mListeners.getNotificationListenerFilter( 326 Pair.create(si.getComponentName(), 0)).getTypes()) 327 .isEqualTo(FLAG_FILTER_TYPE_CONVERSATIONS | FLAG_FILTER_TYPE_ALERTING); 328 } 329 330 @Test testEnsureFilters_newServiceWithMetadata_namesNotNumbers()331 public void testEnsureFilters_newServiceWithMetadata_namesNotNumbers() { 332 ServiceInfo si = new ServiceInfo(); 333 si.packageName = "new"; 334 si.name = "comp"; 335 si.metaData = new Bundle(); 336 si.metaData.putString(NotificationListenerService.META_DATA_DEFAULT_FILTER_TYPES, 337 "conversations|ALERTING"); 338 339 mListeners.ensureFilters(si, 0); 340 341 assertThat(mListeners.getNotificationListenerFilter( 342 Pair.create(si.getComponentName(), 0)).getTypes()) 343 .isEqualTo(FLAG_FILTER_TYPE_CONVERSATIONS | FLAG_FILTER_TYPE_ALERTING); 344 } 345 346 @Test testEnsureFilters_newServiceWithMetadata_onlyOneListed()347 public void testEnsureFilters_newServiceWithMetadata_onlyOneListed() { 348 ServiceInfo si = new ServiceInfo(); 349 si.packageName = "new"; 350 si.name = "comp"; 351 si.metaData = new Bundle(); 352 si.metaData.putInt(NotificationListenerService.META_DATA_DEFAULT_FILTER_TYPES, 2); 353 354 mListeners.ensureFilters(si, 0); 355 356 assertThat(mListeners.getNotificationListenerFilter( 357 Pair.create(si.getComponentName(), 0)).getTypes()) 358 .isEqualTo(FLAG_FILTER_TYPE_ALERTING); 359 } 360 361 @Test testEnsureFilters_newServiceWithMetadata_disabledTypes()362 public void testEnsureFilters_newServiceWithMetadata_disabledTypes() { 363 ServiceInfo si = new ServiceInfo(); 364 si.packageName = "new"; 365 si.name = "comp"; 366 si.metaData = new Bundle(); 367 si.metaData.putString(NotificationListenerService.META_DATA_DISABLED_FILTER_TYPES, "1|2"); 368 369 mListeners.ensureFilters(si, 0); 370 371 assertThat(mListeners.getNotificationListenerFilter( 372 Pair.create(si.getComponentName(), 0)).getTypes()) 373 .isEqualTo(FLAG_FILTER_TYPE_SILENT | FLAG_FILTER_TYPE_ONGOING); 374 } 375 376 @Test testEnsureFilters_newServiceWithMetadata_disabledTypes_mixedText()377 public void testEnsureFilters_newServiceWithMetadata_disabledTypes_mixedText() { 378 ServiceInfo si = new ServiceInfo(); 379 si.packageName = "new"; 380 si.name = "comp"; 381 si.metaData = new Bundle(); 382 si.metaData.putString(NotificationListenerService.META_DATA_DISABLED_FILTER_TYPES, 383 "1|alerting"); 384 385 mListeners.ensureFilters(si, 0); 386 387 assertThat(mListeners.getNotificationListenerFilter( 388 Pair.create(si.getComponentName(), 0)).getTypes()) 389 .isEqualTo(FLAG_FILTER_TYPE_SILENT | FLAG_FILTER_TYPE_ONGOING); 390 } 391 392 @Test testEnsureFilters_newServiceWithMetadata_metaDataDisagrees()393 public void testEnsureFilters_newServiceWithMetadata_metaDataDisagrees() { 394 ServiceInfo si = new ServiceInfo(); 395 si.packageName = "new"; 396 si.name = "comp"; 397 si.metaData = new Bundle(); 398 si.metaData.putString(NotificationListenerService.META_DATA_DEFAULT_FILTER_TYPES, "1|2"); 399 si.metaData.putInt(NotificationListenerService.META_DATA_DISABLED_FILTER_TYPES, 1); 400 401 mListeners.ensureFilters(si, 0); 402 403 assertThat(mListeners.getNotificationListenerFilter( 404 Pair.create(si.getComponentName(), 0)).getTypes()) 405 .isEqualTo(FLAG_FILTER_TYPE_ALERTING); 406 } 407 408 @Test testEnsureFilters_newServiceWithEmptyMetadata()409 public void testEnsureFilters_newServiceWithEmptyMetadata() { 410 ServiceInfo si = new ServiceInfo(); 411 si.packageName = "new"; 412 si.name = "comp"; 413 si.metaData = new Bundle(); 414 si.metaData.putString(NotificationListenerService.META_DATA_DEFAULT_FILTER_TYPES, ""); 415 416 mListeners.ensureFilters(si, 0); 417 418 assertThat(mListeners.getNotificationListenerFilter( 419 Pair.create(si.getComponentName(), 0)).getTypes()) 420 .isEqualTo(0); 421 } 422 423 @Test testOnPackageChanged()424 public void testOnPackageChanged() { 425 NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>()); 426 VersionedPackage a1 = new VersionedPackage("pkg1", 243); 427 NotificationListenerFilter nlf2 = 428 new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1})); 429 mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf); 430 mListeners.setNotificationListenerFilter(Pair.create(mCn2, 10), nlf2); 431 432 String[] pkgs = new String[] {mCn1.getPackageName()}; 433 int[] uids = new int[] {1}; 434 mListeners.onPackagesChanged(false, pkgs, uids); 435 436 // not removing; no change 437 assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn1, 0)).getTypes()) 438 .isEqualTo(7); 439 assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 10)).getTypes()) 440 .isEqualTo(4); 441 } 442 443 @Test testOnPackageChanged_removing()444 public void testOnPackageChanged_removing() { 445 NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>()); 446 VersionedPackage a1 = new VersionedPackage("pkg1", 243); 447 NotificationListenerFilter nlf2 = 448 new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1})); 449 mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf); 450 mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf2); 451 452 String[] pkgs = new String[] {mCn1.getPackageName()}; 453 int[] uids = new int[] {1}; 454 mListeners.onPackagesChanged(true, pkgs, uids); 455 456 // only mCn1 removed 457 assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn1, 0))).isNull(); 458 assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0)).getTypes()) 459 .isEqualTo(4); 460 } 461 462 @Test testOnPackageChanged_removingPackage_removeFromDisallowed()463 public void testOnPackageChanged_removingPackage_removeFromDisallowed() { 464 NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>()); 465 VersionedPackage a1 = new VersionedPackage("pkg1", 243); 466 NotificationListenerFilter nlf2 = 467 new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1})); 468 mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf); 469 mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf2); 470 471 String[] pkgs = new String[] {"pkg1"}; 472 int[] uids = new int[] {243}; 473 mListeners.onPackagesChanged(true, pkgs, uids); 474 475 assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn1, 0)) 476 .getDisallowedPackages()).isEmpty(); 477 assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0)) 478 .getDisallowedPackages()).isEmpty(); 479 } 480 481 @Test testOnPackageChanged_notRemovingPackage_staysInDisallowed()482 public void testOnPackageChanged_notRemovingPackage_staysInDisallowed() { 483 NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>()); 484 VersionedPackage a1 = new VersionedPackage("pkg1", 243); 485 NotificationListenerFilter nlf2 = 486 new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1})); 487 mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf); 488 mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf2); 489 490 String[] pkgs = new String[] {"pkg1"}; 491 int[] uids = new int[] {243}; 492 mListeners.onPackagesChanged(false, pkgs, uids); 493 494 assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0)) 495 .getDisallowedPackages()).contains(a1); 496 } 497 498 @Test testHasAllowedListener()499 public void testHasAllowedListener() { 500 final int uid1 = 1, uid2 = 2; 501 // enable mCn1 but not mCn2 for uid1 502 mListeners.addApprovedList(mCn1.flattenToString(), uid1, true); 503 504 // verify that: 505 // the package for mCn1 has an allowed listener for uid1 and not uid2 506 assertTrue(mListeners.hasAllowedListener(mCn1.getPackageName(), uid1)); 507 assertFalse(mListeners.hasAllowedListener(mCn1.getPackageName(), uid2)); 508 509 // and that mCn2 has no allowed listeners for either user id 510 assertFalse(mListeners.hasAllowedListener(mCn2.getPackageName(), uid1)); 511 assertFalse(mListeners.hasAllowedListener(mCn2.getPackageName(), uid2)); 512 } 513 514 @Test testBroadcastUsers()515 public void testBroadcastUsers() { 516 int userId = 0; 517 mListeners.setPackageOrComponentEnabled(mCn1.flattenToString(), userId, true, false, true); 518 519 verify(mContext).sendBroadcastAsUser( 520 any(), eq(UserHandle.of(userId)), nullable(String.class)); 521 } 522 523 @Test testNotifyPostedLockedInLockdownMode()524 public void testNotifyPostedLockedInLockdownMode() { 525 NotificationRecord r0 = mock(NotificationRecord.class); 526 NotificationRecord old0 = mock(NotificationRecord.class); 527 UserHandle uh0 = mock(UserHandle.class); 528 529 NotificationRecord r1 = mock(NotificationRecord.class); 530 NotificationRecord old1 = mock(NotificationRecord.class); 531 UserHandle uh1 = mock(UserHandle.class); 532 533 // Neither user0 and user1 is in the lockdown mode 534 when(r0.getUser()).thenReturn(uh0); 535 when(uh0.getIdentifier()).thenReturn(0); 536 doReturn(false).when(mNm).isInLockDownMode(0); 537 538 when(r1.getUser()).thenReturn(uh1); 539 when(uh1.getIdentifier()).thenReturn(1); 540 doReturn(false).when(mNm).isInLockDownMode(1); 541 542 mListeners.notifyPostedLocked(r0, old0, true); 543 mListeners.notifyPostedLocked(r0, old0, false); 544 verify(r0, atLeast(2)).getSbn(); 545 546 mListeners.notifyPostedLocked(r1, old1, true); 547 mListeners.notifyPostedLocked(r1, old1, false); 548 verify(r1, atLeast(2)).getSbn(); 549 550 // Reset 551 reset(r0); 552 reset(old0); 553 reset(r1); 554 reset(old1); 555 556 // Only user 0 is in the lockdown mode 557 when(r0.getUser()).thenReturn(uh0); 558 when(uh0.getIdentifier()).thenReturn(0); 559 when(mNm.isInLockDownMode(0)).thenReturn(true); 560 561 when(r1.getUser()).thenReturn(uh1); 562 when(uh1.getIdentifier()).thenReturn(1); 563 when(mNm.isInLockDownMode(1)).thenReturn(false); 564 565 mListeners.notifyPostedLocked(r0, old0, true); 566 mListeners.notifyPostedLocked(r0, old0, false); 567 verify(r0, never()).getSbn(); 568 569 mListeners.notifyPostedLocked(r1, old1, true); 570 mListeners.notifyPostedLocked(r1, old1, false); 571 verify(r1, atLeast(2)).getSbn(); 572 } 573 574 @Test testNotifyRemovedLockedInLockdownMode()575 public void testNotifyRemovedLockedInLockdownMode() throws NoSuchFieldException { 576 NotificationRecord r0 = mock(NotificationRecord.class); 577 NotificationStats rs0 = mock(NotificationStats.class); 578 UserHandle uh0 = mock(UserHandle.class); 579 580 NotificationRecord r1 = mock(NotificationRecord.class); 581 NotificationStats rs1 = mock(NotificationStats.class); 582 UserHandle uh1 = mock(UserHandle.class); 583 584 StatusBarNotification sbn = mock(StatusBarNotification.class); 585 FieldSetter.setField(mNm, 586 NotificationManagerService.class.getDeclaredField("mHandler"), 587 mock(NotificationManagerService.WorkerHandler.class)); 588 589 // Neither user0 and user1 is in the lockdown mode 590 when(r0.getUser()).thenReturn(uh0); 591 when(uh0.getIdentifier()).thenReturn(0); 592 doReturn(false).when(mNm).isInLockDownMode(0); 593 when(r0.getSbn()).thenReturn(sbn); 594 595 when(r1.getUser()).thenReturn(uh1); 596 when(uh1.getIdentifier()).thenReturn(1); 597 doReturn(false).when(mNm).isInLockDownMode(1); 598 when(r1.getSbn()).thenReturn(sbn); 599 600 mListeners.notifyRemovedLocked(r0, 0, rs0); 601 mListeners.notifyRemovedLocked(r0, 0, rs0); 602 verify(r0, atLeast(2)).getSbn(); 603 604 mListeners.notifyRemovedLocked(r1, 0, rs1); 605 mListeners.notifyRemovedLocked(r1, 0, rs1); 606 verify(r1, atLeast(2)).getSbn(); 607 608 // Reset 609 reset(r0); 610 reset(rs0); 611 reset(r1); 612 reset(rs1); 613 614 // Only user 0 is in the lockdown mode 615 when(r0.getUser()).thenReturn(uh0); 616 when(uh0.getIdentifier()).thenReturn(0); 617 when(mNm.isInLockDownMode(0)).thenReturn(true); 618 when(r0.getSbn()).thenReturn(sbn); 619 620 when(r1.getUser()).thenReturn(uh1); 621 when(uh1.getIdentifier()).thenReturn(1); 622 when(mNm.isInLockDownMode(1)).thenReturn(false); 623 when(r1.getSbn()).thenReturn(sbn); 624 625 mListeners.notifyRemovedLocked(r0, 0, rs0); 626 mListeners.notifyRemovedLocked(r0, 0, rs0); 627 verify(r0, never()).getSbn(); 628 629 mListeners.notifyRemovedLocked(r1, 0, rs1); 630 mListeners.notifyRemovedLocked(r1, 0, rs1); 631 verify(r1, atLeast(2)).getSbn(); 632 } 633 634 @Test testImplicitGrant()635 public void testImplicitGrant() { 636 String pkg = "pkg"; 637 int uid = 9; 638 NotificationChannel channel = new NotificationChannel("id", "name", 639 NotificationManager.IMPORTANCE_HIGH); 640 Notification.Builder nb = new Notification.Builder(mContext, channel.getId()) 641 .setContentTitle("foo") 642 .setSmallIcon(android.R.drawable.sym_def_app_icon) 643 .setTimeoutAfter(1); 644 645 StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0, 646 nb.build(), UserHandle.getUserHandleForUid(uid), null, 0); 647 NotificationRecord r = new NotificationRecord(mContext, sbn, channel); 648 649 ManagedServices.ManagedServiceInfo info = mListeners.new ManagedServiceInfo( 650 null, new ComponentName("a", "a"), sbn.getUserId(), false, null, 33, 33); 651 List<ManagedServices.ManagedServiceInfo> services = ImmutableList.of(info); 652 when(mListeners.getServices()).thenReturn(services); 653 654 doReturn(true).when(mNm).isVisibleToListener(any(), anyInt(), any()); 655 doReturn(mock(NotificationRankingUpdate.class)).when(mNm).makeRankingUpdateLocked(info); 656 doReturn(false).when(mNm).isInLockDownMode(anyInt()); 657 doNothing().when(mNm).updateUriPermissions(any(), any(), any(), anyInt()); 658 659 mListeners.notifyPostedLocked(r, null); 660 661 verify(mPmi).grantImplicitAccess(sbn.getUserId(), null, UserHandle.getAppId(33), 662 sbn.getUid(), false, false); 663 } 664 665 @Test testUpdateGroup_notifyTwoListeners()666 public void testUpdateGroup_notifyTwoListeners() throws Exception { 667 final NotificationChannelGroup updated = new NotificationChannelGroup("id", "name"); 668 updated.setChannels(ImmutableList.of( 669 new NotificationChannel("a", "a", 1), new NotificationChannel("b", "b", 2))); 670 updated.setBlocked(true); 671 672 ManagedServices.ManagedServiceInfo i1 = getParcelingListener(updated); 673 ManagedServices.ManagedServiceInfo i2= getParcelingListener(updated); 674 when(mListeners.getServices()).thenReturn(ImmutableList.of(i1, i2)); 675 NotificationChannelGroup existing = new NotificationChannelGroup("id", "name"); 676 677 mListeners.notifyNotificationChannelGroupChanged("pkg", UserHandle.of(0), updated, 0); 678 Thread.sleep(500); 679 680 verify(((INotificationListener) i1.getService()), times(1)) 681 .onNotificationChannelGroupModification(anyString(), any(), any(), anyInt()); 682 } 683 684 @Test testNotificationListenerFilter_threadSafety()685 public void testNotificationListenerFilter_threadSafety() throws Exception { 686 testThreadSafety(() -> { 687 mListeners.setNotificationListenerFilter( 688 new Pair<>(new ComponentName("pkg1", "cls1"), 0), 689 new NotificationListenerFilter()); 690 mListeners.setNotificationListenerFilter( 691 new Pair<>(new ComponentName("pkg2", "cls2"), 10), 692 new NotificationListenerFilter()); 693 mListeners.setNotificationListenerFilter( 694 new Pair<>(new ComponentName("pkg3", "cls3"), 11), 695 new NotificationListenerFilter()); 696 697 mListeners.onUserRemoved(10); 698 mListeners.onPackagesChanged(true, new String[]{"pkg1", "pkg2"}, new int[]{0, 0}); 699 }, 20, 50); 700 } 701 702 @Test testListenerTrusted_withPermission()703 public void testListenerTrusted_withPermission() throws RemoteException { 704 mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS); 705 when(mNm.mPackageManager.checkUidPermission(RECEIVE_SENSITIVE_NOTIFICATIONS, mUid1)) 706 .thenReturn(PERMISSION_GRANTED); 707 ManagedServices.ManagedServiceInfo info = getMockServiceInfo(); 708 mListeners.onServiceAdded(info); 709 assertTrue(mListeners.isUidTrusted(mUid1)); 710 } 711 712 @Test testListenerTrusted_withSystemSignature()713 public void testListenerTrusted_withSystemSignature() { 714 mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS); 715 when(mNm.mPackageManagerInternal.isPlatformSigned(mCn1.getPackageName())).thenReturn(true); 716 ManagedServices.ManagedServiceInfo info = getMockServiceInfo(); 717 mListeners.onServiceAdded(info); 718 assertTrue(mListeners.isUidTrusted(mUid1)); 719 } 720 721 @Test testListenerTrusted_withCdmAssociation()722 public void testListenerTrusted_withCdmAssociation() throws Exception { 723 mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS); 724 mNm.mCompanionManager = mock(ICompanionDeviceManager.class); 725 AssociationInfo assocInfo = mock(AssociationInfo.class); 726 when(assocInfo.isRevoked()).thenReturn(false); 727 when(assocInfo.getPackageName()).thenReturn(mCn1.getPackageName()); 728 when(assocInfo.getUserId()).thenReturn(UserHandle.getUserId(mUid1)); 729 ArrayList<AssociationInfo> infos = new ArrayList<>(); 730 infos.add(assocInfo); 731 when(mNm.mCompanionManager.getAllAssociationsForUser(anyInt())).thenReturn(infos); 732 ManagedServices.ManagedServiceInfo info = getMockServiceInfo(); 733 mListeners.onServiceAdded(info); 734 assertTrue(mListeners.isUidTrusted(mUid1)); 735 } 736 737 @Test testListenerTrusted_ifFlagDisabled()738 public void testListenerTrusted_ifFlagDisabled() { 739 mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS); 740 ManagedServices.ManagedServiceInfo info = getMockServiceInfo(); 741 mListeners.onServiceAdded(info); 742 assertTrue(mListeners.isUidTrusted(mUid1)); 743 } 744 745 @Test testRedaction_whenPosted()746 public void testRedaction_whenPosted() { 747 mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS); 748 ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>(); 749 infos.add(getMockServiceInfo()); 750 doReturn(infos).when(mListeners).getServices(); 751 doReturn(mock(StatusBarNotification.class)) 752 .when(mListeners).redactStatusBarNotification(any()); 753 doReturn(false).when(mNm).isInLockDownMode(anyInt()); 754 doReturn(true).when(mNm).isVisibleToListener(any(), anyInt(), any()); 755 NotificationRecord r = mock(NotificationRecord.class); 756 when(r.getUser()).thenReturn(UserHandle.of(0)); 757 StatusBarNotification sbn = getSbn(0); 758 NotificationRecord old = mock(NotificationRecord.class); 759 when(old.getUser()).thenReturn(UserHandle.of(0)); 760 StatusBarNotification oldSbn = getSbn(1); 761 when(r.getSbn()).thenReturn(sbn); 762 when(r.hasSensitiveContent()).thenReturn(true); 763 when(old.getSbn()).thenReturn(oldSbn); 764 when(old.hasSensitiveContent()).thenReturn(true); 765 766 mListeners.notifyPostedLocked(r, old); 767 verify(mListeners, atLeast(1)).redactStatusBarNotification(eq(sbn)); 768 verify(mListeners, never()).redactStatusBarNotification(eq(oldSbn)); 769 } 770 771 @Test testRedaction_whenPosted_oldRemoved()772 public void testRedaction_whenPosted_oldRemoved() { 773 mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS); 774 ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>(); 775 infos.add(getMockServiceInfo()); 776 doReturn(infos).when(mListeners).getServices(); 777 doReturn(mock(StatusBarNotification.class)) 778 .when(mListeners).redactStatusBarNotification(any()); 779 doReturn(false).when(mNm).isInLockDownMode(anyInt()); 780 doReturn(true).when(mNm).isVisibleToListener(any(), anyInt(), any()); 781 NotificationRecord r = mock(NotificationRecord.class); 782 when(r.getUser()).thenReturn(UserHandle.of(0)); 783 StatusBarNotification sbn = getSbn(0); 784 NotificationRecord old = mock(NotificationRecord.class); 785 when(old.getUser()).thenReturn(UserHandle.of(0)); 786 StatusBarNotification oldSbn = getSbn(1); 787 when(r.getSbn()).thenReturn(sbn); 788 when(r.hasSensitiveContent()).thenReturn(true); 789 when(old.getSbn()).thenReturn(oldSbn); 790 when(old.hasSensitiveContent()).thenReturn(true); 791 792 doReturn(true).when(mNm).isVisibleToListener(eq(oldSbn), anyInt(), any()); 793 doReturn(false).when(mNm).isVisibleToListener(eq(sbn), anyInt(), any()); 794 mListeners.notifyPostedLocked(r, old); 795 // When the old sbn is removed, the old should be redacted 796 verify(mListeners, atLeast(1)).redactStatusBarNotification(eq(oldSbn)); 797 } 798 799 @Test testRedaction_whenRemoved()800 public void testRedaction_whenRemoved() { 801 mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS); 802 doReturn(mock(StatusBarNotification.class)) 803 .when(mListeners).redactStatusBarNotification(any()); 804 ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>(); 805 infos.add(getMockServiceInfo()); 806 doReturn(infos).when(mListeners).getServices(); 807 doReturn(false).when(mNm).isInLockDownMode(anyInt()); 808 doReturn(true).when(mNm).isVisibleToListener(any(), anyInt(), any()); 809 NotificationRecord r = mock(NotificationRecord.class); 810 when(r.getUser()).thenReturn(UserHandle.of(0)); 811 StatusBarNotification sbn = getSbn(0); 812 when(r.getSbn()).thenReturn(sbn); 813 when(r.hasSensitiveContent()).thenReturn(true); 814 mNm.mAssistants = mock(NotificationManagerService.NotificationAssistants.class); 815 816 mListeners.notifyRemovedLocked(r, 0, mock(NotificationStats.class)); 817 verify(mListeners, atLeast(1)).redactStatusBarNotification(any()); 818 } 819 820 @Test testRedaction_noneIfFlagDisabled()821 public void testRedaction_noneIfFlagDisabled() { 822 mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS); 823 ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>(); 824 infos.add(getMockServiceInfo()); 825 doReturn(infos).when(mListeners).getServices(); 826 doReturn(false).when(mNm).isInLockDownMode(anyInt()); 827 doReturn(true).when(mNm).isVisibleToListener(any(), anyInt(), any()); 828 NotificationRecord r = mock(NotificationRecord.class); 829 when(r.getUser()).thenReturn(UserHandle.of(0)); 830 StatusBarNotification sbn = getSbn(0); 831 when(r.getSbn()).thenReturn(sbn); 832 when(r.hasSensitiveContent()).thenReturn(true); 833 mListeners.notifyRemovedLocked(r, 0, mock(NotificationStats.class)); 834 verify(mListeners, never()).redactStatusBarNotification(eq(sbn)); 835 } 836 837 @Test testListenerPostLifetimeExtended_UpdatesOnlySysui()838 public void testListenerPostLifetimeExtended_UpdatesOnlySysui() throws Exception { 839 mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR); 840 841 // Create original notification, with FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY. 842 String pkg = "pkg"; 843 int uid = 9; 844 UserHandle userHandle = UserHandle.getUserHandleForUid(uid); 845 NotificationChannel channel = new NotificationChannel("id", "name", 846 NotificationManager.IMPORTANCE_HIGH); 847 Notification.Builder nb = new Notification.Builder(mContext, channel.getId()) 848 .setContentTitle("foo") 849 .setSmallIcon(android.R.drawable.sym_def_app_icon) 850 .setFlag(Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true); 851 StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0, 852 nb.build(), userHandle, null, 0); 853 NotificationRecord old = new NotificationRecord(mContext, sbn, channel); 854 855 // Creates updated notification (without FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) 856 Notification.Builder nb2 = new Notification.Builder(mContext, channel.getId()) 857 .setContentTitle("new title") 858 .setSmallIcon(android.R.drawable.sym_def_app_icon) 859 .setFlag(Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true); 860 StatusBarNotification sbn2 = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0, 861 nb2.build(), userHandle, null, 0); 862 NotificationRecord toPost = new NotificationRecord(mContext, sbn2, channel); 863 864 // Create system ui-like service. 865 ManagedServices.ManagedServiceInfo sysuiInfo = mListeners.new ManagedServiceInfo( 866 null, new ComponentName("a", "a"), sbn2.getUserId(), false, null, 33, 33); 867 sysuiInfo.isSystemUi = true; 868 INotificationListener sysuiListener = mock(INotificationListener.class); 869 sysuiInfo.service = sysuiListener; 870 871 // Create two non-system ui-like services. 872 ManagedServices.ManagedServiceInfo otherInfo1 = mListeners.new ManagedServiceInfo( 873 null, new ComponentName("b", "b"), sbn2.getUserId(), false, null, 33, 33); 874 otherInfo1.isSystemUi = false; 875 INotificationListener otherListener1 = mock(INotificationListener.class); 876 otherInfo1.service = otherListener1; 877 878 ManagedServices.ManagedServiceInfo otherInfo2 = mListeners.new ManagedServiceInfo( 879 null, new ComponentName("c", "c"), sbn2.getUserId(), false, null, 33, 33); 880 otherInfo2.isSystemUi = false; 881 INotificationListener otherListener2 = mock(INotificationListener.class); 882 otherInfo2.service = otherListener2; 883 884 List<ManagedServices.ManagedServiceInfo> services = ImmutableList.of(otherInfo1, sysuiInfo, 885 otherInfo2); 886 when(mListeners.getServices()).thenReturn(services); 887 888 FieldSetter.setField(mNm, 889 NotificationManagerService.class.getDeclaredField("mHandler"), 890 mock(NotificationManagerService.WorkerHandler.class)); 891 doReturn(true).when(mNm).isVisibleToListener(any(), anyInt(), any()); 892 doReturn(mock(NotificationRankingUpdate.class)).when(mNm) 893 .makeRankingUpdateLocked(sysuiInfo); 894 doReturn(mock(NotificationRankingUpdate.class)).when(mNm) 895 .makeRankingUpdateLocked(otherInfo1); 896 doReturn(mock(NotificationRankingUpdate.class)).when(mNm) 897 .makeRankingUpdateLocked(otherInfo2); 898 doReturn(false).when(mNm).isInLockDownMode(anyInt()); 899 doNothing().when(mNm).updateUriPermissions(any(), any(), any(), anyInt()); 900 doReturn(sbn2).when(mListeners).redactStatusBarNotification(sbn2); 901 doReturn(sbn2).when(mListeners).redactStatusBarNotification(any()); 902 903 // Post notification change to the service listeners. 904 mListeners.notifyPostedLocked(toPost, old); 905 906 // Verify that the post occcurs with the updated notification value. 907 ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); 908 verify(mNm.mHandler, times(1)).post(runnableCaptor.capture()); 909 runnableCaptor.getValue().run(); 910 StatusBarNotification sbnResult = null; 911 if (android.app.Flags.noSbnholder()) { 912 ArgumentCaptor<StatusBarNotification> sbnCaptor = 913 ArgumentCaptor.forClass(StatusBarNotification.class); 914 verify(sysuiListener, times(1)).onNotificationPostedFull(sbnCaptor.capture(), any()); 915 sbnResult = sbnCaptor.getValue(); 916 } else { 917 ArgumentCaptor<IStatusBarNotificationHolder> sbnCaptor = 918 ArgumentCaptor.forClass(IStatusBarNotificationHolder.class); 919 verify(sysuiListener, times(1)).onNotificationPosted(sbnCaptor.capture(), any()); 920 sbnResult = sbnCaptor.getValue().get(); 921 } 922 assertThat(sbnResult.getNotification() 923 .extras.getCharSequence(Notification.EXTRA_TITLE).toString()) 924 .isEqualTo("new title"); 925 926 verify(otherListener1, never()).onNotificationPosted(any(), any()); 927 verify(otherListener2, never()).onNotificationPosted(any(), any()); 928 } 929 930 @Test testListenerPostLifetimeExtension_postsToAppropriateListeners()931 public void testListenerPostLifetimeExtension_postsToAppropriateListeners() throws Exception { 932 mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR); 933 934 // Create original notification, with FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY. 935 String pkg = "pkg"; 936 int uid = 9; 937 UserHandle userHandle = UserHandle.getUserHandleForUid(uid); 938 NotificationChannel channel = new NotificationChannel("id", "name", 939 NotificationManager.IMPORTANCE_HIGH); 940 Notification.Builder nb = new Notification.Builder(mContext, channel.getId()) 941 .setContentTitle("foo") 942 .setSmallIcon(android.R.drawable.sym_def_app_icon) 943 .setFlag(Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true); 944 StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0, 945 nb.build(), userHandle, null, 0); 946 NotificationRecord leRecord = new NotificationRecord(mContext, sbn, channel); 947 948 // Creates updated notification (without FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) 949 Notification.Builder nb2 = new Notification.Builder(mContext, channel.getId()) 950 .setContentTitle("new title") 951 .setSmallIcon(android.R.drawable.sym_def_app_icon) 952 .setFlag(Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, false); 953 StatusBarNotification sbn2 = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0, 954 nb2.build(), userHandle, null, 0); 955 NotificationRecord nonLeRecord = new NotificationRecord(mContext, sbn2, channel); 956 957 // Create system ui-like service. 958 ManagedServices.ManagedServiceInfo sysuiInfo = mListeners.new ManagedServiceInfo( 959 null, new ComponentName("a", "a"), sbn2.getUserId(), false, null, 33, 33); 960 sysuiInfo.isSystemUi = true; 961 INotificationListener sysuiListener = mock(INotificationListener.class); 962 sysuiInfo.service = sysuiListener; 963 964 // Create two non-system ui-like services. 965 ManagedServices.ManagedServiceInfo otherInfo1 = mListeners.new ManagedServiceInfo( 966 null, new ComponentName("b", "b"), sbn2.getUserId(), false, null, 33, 33); 967 otherInfo1.isSystemUi = false; 968 INotificationListener otherListener1 = mock(INotificationListener.class); 969 otherInfo1.service = otherListener1; 970 971 ManagedServices.ManagedServiceInfo otherInfo2 = mListeners.new ManagedServiceInfo( 972 null, new ComponentName("c", "c"), sbn2.getUserId(), false, null, 33, 33); 973 otherInfo2.isSystemUi = false; 974 INotificationListener otherListener2 = mock(INotificationListener.class); 975 otherInfo2.service = otherListener2; 976 977 List<ManagedServices.ManagedServiceInfo> services = ImmutableList.of(otherInfo1, sysuiInfo, 978 otherInfo2); 979 when(mListeners.getServices()).thenReturn(services); 980 981 FieldSetter.setField(mNm, 982 NotificationManagerService.class.getDeclaredField("mHandler"), 983 mock(NotificationManagerService.WorkerHandler.class)); 984 doReturn(true).when(mNm).isVisibleToListener(any(), anyInt(), any()); 985 doReturn(mock(NotificationRankingUpdate.class)).when(mNm) 986 .makeRankingUpdateLocked(sysuiInfo); 987 doReturn(mock(NotificationRankingUpdate.class)).when(mNm) 988 .makeRankingUpdateLocked(otherInfo1); 989 doReturn(mock(NotificationRankingUpdate.class)).when(mNm) 990 .makeRankingUpdateLocked(otherInfo2); 991 doReturn(false).when(mNm).isInLockDownMode(anyInt()); 992 doNothing().when(mNm).updateUriPermissions(any(), any(), any(), anyInt()); 993 doReturn(sbn2).when(mListeners).redactStatusBarNotification(sbn2); 994 doReturn(sbn2).when(mListeners).redactStatusBarNotification(any()); 995 996 // The notification change is posted to the service listener. 997 // NonLE to LE should never happen, as LE can't be set in an update by the app. 998 // So we just want to test LE to NonLE. 999 mListeners.notifyPostedLocked(nonLeRecord /*=toPost*/, leRecord /*=old*/); 1000 1001 // Verify that the post occcurs with the updated notification value. 1002 ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); 1003 verify(mNm.mHandler, times(3)).post(runnableCaptor.capture()); 1004 List<Runnable> capturedRunnable = runnableCaptor.getAllValues(); 1005 for (Runnable r : capturedRunnable) { 1006 r.run(); 1007 } 1008 1009 StatusBarNotification sbnResult = null; 1010 if (android.app.Flags.noSbnholder()) { 1011 ArgumentCaptor<StatusBarNotification> sbnCaptor = 1012 ArgumentCaptor.forClass(StatusBarNotification.class); 1013 verify(sysuiListener, times(1)).onNotificationPostedFull(sbnCaptor.capture(), any()); 1014 sbnResult = sbnCaptor.getValue(); 1015 } else { 1016 ArgumentCaptor<IStatusBarNotificationHolder> sbnCaptor = 1017 ArgumentCaptor.forClass(IStatusBarNotificationHolder.class); 1018 verify(sysuiListener, times(1)).onNotificationPosted(sbnCaptor.capture(), any()); 1019 sbnResult = sbnCaptor.getValue().get(); 1020 } 1021 assertThat(sbnResult.getNotification() 1022 .extras.getCharSequence(Notification.EXTRA_TITLE).toString()) 1023 .isEqualTo("new title"); 1024 1025 if (android.app.Flags.noSbnholder()) { 1026 verify(otherListener1, times(1)).onNotificationPostedFull(any(), any()); 1027 verify(otherListener2, times(1)).onNotificationPostedFull(any(), any()); 1028 } else { 1029 verify(otherListener1, times(1)).onNotificationPosted(any(), any()); 1030 verify(otherListener2, times(1)).onNotificationPosted(any(), any()); 1031 } 1032 } 1033 1034 @Test testNotifyPostedLocked_postsToAppropriateListeners()1035 public void testNotifyPostedLocked_postsToAppropriateListeners() throws Exception { 1036 // Create original notification 1037 String pkg = "pkg"; 1038 int uid = 9; 1039 UserHandle userHandle = UserHandle.getUserHandleForUid(uid); 1040 NotificationChannel channel = new NotificationChannel("id", "name", 1041 NotificationManager.IMPORTANCE_HIGH); 1042 Notification.Builder nb = new Notification.Builder(mContext, channel.getId()) 1043 .setContentTitle("foo") 1044 .setSmallIcon(android.R.drawable.sym_def_app_icon); 1045 StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0, 1046 nb.build(), userHandle, null, 0); 1047 NotificationRecord oldRecord = new NotificationRecord(mContext, sbn, channel); 1048 1049 // Creates updated notification 1050 Notification.Builder nb2 = new Notification.Builder(mContext, channel.getId()) 1051 .setContentTitle("new title") 1052 .setSmallIcon(android.R.drawable.sym_def_app_icon); 1053 StatusBarNotification sbn2 = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0, 1054 nb2.build(), userHandle, null, 0); 1055 NotificationRecord newRecord = new NotificationRecord(mContext, sbn2, channel); 1056 1057 // Create system ui-like service. 1058 ManagedServices.ManagedServiceInfo sysuiInfo = mListeners.new ManagedServiceInfo( 1059 null, new ComponentName("a", "a"), sbn2.getUserId(), false, null, 33, 33); 1060 sysuiInfo.isSystemUi = true; 1061 INotificationListener sysuiListener = mock(INotificationListener.class); 1062 sysuiInfo.service = sysuiListener; 1063 1064 // Create two non-system ui-like services. 1065 ManagedServices.ManagedServiceInfo otherInfo1 = mListeners.new ManagedServiceInfo( 1066 null, new ComponentName("b", "b"), sbn2.getUserId(), false, null, 33, 33); 1067 otherInfo1.isSystemUi = false; 1068 INotificationListener otherListener1 = mock(INotificationListener.class); 1069 otherInfo1.service = otherListener1; 1070 1071 ManagedServices.ManagedServiceInfo otherInfo2 = mListeners.new ManagedServiceInfo( 1072 null, new ComponentName("c", "c"), sbn2.getUserId(), false, null, 33, 33); 1073 otherInfo2.isSystemUi = false; 1074 INotificationListener otherListener2 = mock(INotificationListener.class); 1075 otherInfo2.service = otherListener2; 1076 1077 List<ManagedServices.ManagedServiceInfo> services = ImmutableList.of(otherInfo1, sysuiInfo, 1078 otherInfo2); 1079 when(mListeners.getServices()).thenReturn(services); 1080 1081 FieldSetter.setField(mNm, 1082 NotificationManagerService.class.getDeclaredField("mHandler"), 1083 mock(NotificationManagerService.WorkerHandler.class)); 1084 doReturn(true).when(mNm).isVisibleToListener(any(), anyInt(), any()); 1085 doReturn(mock(NotificationRankingUpdate.class)).when(mNm) 1086 .makeRankingUpdateLocked(sysuiInfo); 1087 doReturn(mock(NotificationRankingUpdate.class)).when(mNm) 1088 .makeRankingUpdateLocked(otherInfo1); 1089 doReturn(mock(NotificationRankingUpdate.class)).when(mNm) 1090 .makeRankingUpdateLocked(otherInfo2); 1091 doReturn(false).when(mNm).isInLockDownMode(anyInt()); 1092 doNothing().when(mNm).updateUriPermissions(any(), any(), any(), anyInt()); 1093 doReturn(sbn2).when(mListeners).redactStatusBarNotification(sbn2); 1094 doReturn(sbn2).when(mListeners).redactStatusBarNotification(any()); 1095 1096 // The notification change is posted to the service listeners. 1097 mListeners.notifyPostedLocked(newRecord, oldRecord); 1098 1099 // Verify that the post occcurs with the updated notification value. 1100 ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); 1101 verify(mNm.mHandler, times(3)).post(runnableCaptor.capture()); 1102 List<Runnable> capturedRunnable = runnableCaptor.getAllValues(); 1103 for (Runnable r : capturedRunnable) { 1104 r.run(); 1105 } 1106 1107 StatusBarNotification sbnResult = null; 1108 if (android.app.Flags.noSbnholder()) { 1109 ArgumentCaptor<StatusBarNotification> sbnCaptor = 1110 ArgumentCaptor.forClass(StatusBarNotification.class); 1111 verify(sysuiListener, times(1)).onNotificationPostedFull(sbnCaptor.capture(), any()); 1112 sbnResult = sbnCaptor.getValue(); 1113 } else { 1114 ArgumentCaptor<IStatusBarNotificationHolder> sbnCaptor = 1115 ArgumentCaptor.forClass(IStatusBarNotificationHolder.class); 1116 verify(sysuiListener, times(1)).onNotificationPosted(sbnCaptor.capture(), any()); 1117 sbnResult = sbnCaptor.getValue().get(); 1118 } 1119 assertThat(sbnResult.getNotification() 1120 .extras.getCharSequence(Notification.EXTRA_TITLE).toString()) 1121 .isEqualTo("new title"); 1122 1123 if (android.app.Flags.noSbnholder()) { 1124 verify(otherListener1, times(1)).onNotificationPostedFull(any(), any()); 1125 verify(otherListener2, times(1)).onNotificationPostedFull(any(), any()); 1126 } else { 1127 verify(otherListener1, times(1)).onNotificationPosted(any(), any()); 1128 verify(otherListener2, times(1)).onNotificationPosted(any(), any()); 1129 } 1130 } 1131 1132 /** 1133 * Helper method to test the thread safety of some operations. 1134 * 1135 * <p>Runs the supplied {@code operationToTest}, {@code nRunsPerThread} times, 1136 * concurrently using {@code nThreads} threads, and waits for all of them to finish. 1137 */ testThreadSafety(Runnable operationToTest, int nThreads, int nRunsPerThread)1138 private static void testThreadSafety(Runnable operationToTest, int nThreads, 1139 int nRunsPerThread) throws InterruptedException { 1140 final CountDownLatch startLatch = new CountDownLatch(1); 1141 final CountDownLatch doneLatch = new CountDownLatch(nThreads); 1142 1143 for (int i = 0; i < nThreads; i++) { 1144 Runnable threadRunnable = () -> { 1145 try { 1146 startLatch.await(); 1147 for (int j = 0; j < nRunsPerThread; j++) { 1148 operationToTest.run(); 1149 } 1150 } catch (InterruptedException e) { 1151 e.printStackTrace(); 1152 } finally { 1153 doneLatch.countDown(); 1154 } 1155 }; 1156 new Thread(threadRunnable, "Test Thread #" + i).start(); 1157 } 1158 1159 // Ready set go 1160 startLatch.countDown(); 1161 1162 // Wait for all test threads to be done. 1163 doneLatch.await(); 1164 } 1165 getParcelingListener( final NotificationChannelGroup toParcel)1166 private ManagedServices.ManagedServiceInfo getParcelingListener( 1167 final NotificationChannelGroup toParcel) 1168 throws RemoteException { 1169 ManagedServices.ManagedServiceInfo i1 = getMockServiceInfo(); 1170 INotificationListener l1 = (INotificationListener) i1.getService(); 1171 doAnswer(invocationOnMock -> { 1172 try { 1173 toParcel.writeToParcel(Parcel.obtain(), 0); 1174 } catch (Exception e) { 1175 fail("Failed to parcel group to listener"); 1176 return e; 1177 1178 } 1179 return null; 1180 }).when(l1).onNotificationChannelGroupModification(anyString(), any(), any(), anyInt()); 1181 return i1; 1182 } 1183 getMockServiceInfo()1184 private ManagedServices.ManagedServiceInfo getMockServiceInfo() { 1185 ManagedServices.ManagedServiceInfo i1 = mock(ManagedServices.ManagedServiceInfo.class); 1186 when(i1.isSystem()).thenReturn(true); 1187 INotificationListener l1 = mock(INotificationListener.class); 1188 when(i1.enabledAndUserMatches(anyInt())).thenReturn(true); 1189 when(i1.getService()).thenReturn(l1); 1190 i1.service = l1; 1191 i1.uid = mUid1; 1192 i1.component = mCn1; 1193 return i1; 1194 } 1195 getSbn(int id)1196 private StatusBarNotification getSbn(int id) { 1197 return new StatusBarNotification("pkg1", "pkg1", id, "", mUid1, 0, 1198 mock(Notification.class), UserHandle.of(0), "", 0); 1199 1200 } 1201 } 1202