1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.wmshell; 18 19 import static android.app.Notification.FLAG_BUBBLE; 20 import static android.app.PendingIntent.FLAG_MUTABLE; 21 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; 22 import static android.service.notification.NotificationListenerService.REASON_CANCEL; 23 import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL; 24 import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED; 25 26 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; 27 28 import static com.google.common.truth.Truth.assertThat; 29 30 import static org.junit.Assert.assertEquals; 31 import static org.junit.Assert.assertFalse; 32 import static org.junit.Assert.assertNotNull; 33 import static org.junit.Assert.assertNull; 34 import static org.junit.Assert.assertTrue; 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.Mockito.atLeastOnce; 40 import static org.mockito.Mockito.mock; 41 import static org.mockito.Mockito.never; 42 import static org.mockito.Mockito.times; 43 import static org.mockito.Mockito.verify; 44 import static org.mockito.Mockito.when; 45 46 import android.app.IActivityManager; 47 import android.app.INotificationManager; 48 import android.app.Notification; 49 import android.app.PendingIntent; 50 import android.content.Context; 51 import android.content.Intent; 52 import android.content.pm.ApplicationInfo; 53 import android.content.pm.LauncherApps; 54 import android.content.pm.PackageManager; 55 import android.graphics.Rect; 56 import android.graphics.drawable.Drawable; 57 import android.graphics.drawable.Icon; 58 import android.hardware.display.AmbientDisplayConfiguration; 59 import android.hardware.face.FaceManager; 60 import android.os.Handler; 61 import android.os.PowerManager; 62 import android.os.UserHandle; 63 import android.service.dreams.IDreamManager; 64 import android.service.notification.NotificationListenerService; 65 import android.service.notification.ZenModeConfig; 66 import android.testing.AndroidTestingRunner; 67 import android.testing.TestableLooper; 68 import android.view.WindowManager; 69 70 import androidx.test.filters.SmallTest; 71 72 import com.android.internal.colorextraction.ColorExtractor; 73 import com.android.internal.statusbar.IStatusBarService; 74 import com.android.systemui.SysuiTestCase; 75 import com.android.systemui.biometrics.AuthController; 76 import com.android.systemui.colorextraction.SysuiColorExtractor; 77 import com.android.systemui.dump.DumpManager; 78 import com.android.systemui.keyguard.KeyguardViewMediator; 79 import com.android.systemui.model.SysUiState; 80 import com.android.systemui.plugins.statusbar.StatusBarStateController; 81 import com.android.systemui.shared.system.QuickStepContract; 82 import com.android.systemui.statusbar.FeatureFlags; 83 import com.android.systemui.statusbar.NotificationLockscreenUserManager; 84 import com.android.systemui.statusbar.NotificationRemoveInterceptor; 85 import com.android.systemui.statusbar.RankingBuilder; 86 import com.android.systemui.statusbar.SysuiStatusBarStateController; 87 import com.android.systemui.statusbar.notification.NotificationEntryListener; 88 import com.android.systemui.statusbar.notification.NotificationEntryManager; 89 import com.android.systemui.statusbar.notification.NotificationFilter; 90 import com.android.systemui.statusbar.notification.collection.NotifPipeline; 91 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 92 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; 93 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy; 94 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; 95 import com.android.systemui.statusbar.notification.row.NotificationTestHelper; 96 import com.android.systemui.statusbar.phone.DozeParameters; 97 import com.android.systemui.statusbar.phone.KeyguardBypassController; 98 import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl; 99 import com.android.systemui.statusbar.phone.NotificationShadeWindowView; 100 import com.android.systemui.statusbar.phone.ShadeController; 101 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; 102 import com.android.systemui.statusbar.policy.BatteryController; 103 import com.android.systemui.statusbar.policy.ConfigurationController; 104 import com.android.systemui.statusbar.policy.HeadsUpManager; 105 import com.android.systemui.statusbar.policy.KeyguardStateController; 106 import com.android.systemui.statusbar.policy.ZenModeController; 107 import com.android.wm.shell.R; 108 import com.android.wm.shell.ShellTaskOrganizer; 109 import com.android.wm.shell.WindowManagerShellWrapper; 110 import com.android.wm.shell.bubbles.Bubble; 111 import com.android.wm.shell.bubbles.BubbleData; 112 import com.android.wm.shell.bubbles.BubbleDataRepository; 113 import com.android.wm.shell.bubbles.BubbleEntry; 114 import com.android.wm.shell.bubbles.BubbleIconFactory; 115 import com.android.wm.shell.bubbles.BubbleLogger; 116 import com.android.wm.shell.bubbles.BubbleOverflow; 117 import com.android.wm.shell.bubbles.BubbleStackView; 118 import com.android.wm.shell.bubbles.BubbleViewInfoTask; 119 import com.android.wm.shell.bubbles.Bubbles; 120 import com.android.wm.shell.common.DisplayController; 121 import com.android.wm.shell.common.FloatingContentCoordinator; 122 import com.android.wm.shell.common.ShellExecutor; 123 import com.android.wm.shell.common.TaskStackListenerImpl; 124 125 import com.google.common.collect.ImmutableList; 126 127 import org.junit.Before; 128 import org.junit.Ignore; 129 import org.junit.Test; 130 import org.junit.runner.RunWith; 131 import org.mockito.ArgumentCaptor; 132 import org.mockito.Captor; 133 import org.mockito.Mock; 134 import org.mockito.MockitoAnnotations; 135 136 import java.util.List; 137 138 /** 139 * Tests the NotificationEntryManager setup with BubbleController. 140 * The {@link NotifPipeline} setup with BubbleController is tested in 141 * {@link NewNotifPipelineBubblesTest}. 142 */ 143 @SmallTest 144 @RunWith(AndroidTestingRunner.class) 145 @TestableLooper.RunWithLooper(setAsMainLooper = true) 146 public class BubblesTest extends SysuiTestCase { 147 @Mock 148 private NotificationEntryManager mNotificationEntryManager; 149 @Mock 150 private NotificationGroupManagerLegacy mNotificationGroupManager; 151 @Mock 152 private WindowManager mWindowManager; 153 @Mock 154 private IActivityManager mActivityManager; 155 @Mock 156 private DozeParameters mDozeParameters; 157 @Mock 158 private ConfigurationController mConfigurationController; 159 @Mock 160 private ZenModeController mZenModeController; 161 @Mock 162 private ZenModeConfig mZenModeConfig; 163 @Mock 164 private FaceManager mFaceManager; 165 @Mock 166 private NotificationLockscreenUserManager mLockscreenUserManager; 167 @Mock 168 private SysuiStatusBarStateController mStatusBarStateController; 169 @Mock 170 private KeyguardViewMediator mKeyguardViewMediator; 171 @Mock 172 private KeyguardBypassController mKeyguardBypassController; 173 @Mock 174 private FloatingContentCoordinator mFloatingContentCoordinator; 175 @Mock 176 private BubbleDataRepository mDataRepository; 177 178 private SysUiState mSysUiState; 179 private boolean mSysUiStateBubblesExpanded; 180 181 @Captor 182 private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor; 183 @Captor 184 private ArgumentCaptor<NotificationRemoveInterceptor> mRemoveInterceptorCaptor; 185 186 private BubblesManager mBubblesManager; 187 // TODO(178618782): Move tests on the controller directly to the shell 188 private TestableBubbleController mBubbleController; 189 private NotificationShadeWindowControllerImpl mNotificationShadeWindowController; 190 private NotificationEntryListener mEntryListener; 191 private NotificationRemoveInterceptor mRemoveInterceptor; 192 193 private NotificationTestHelper mNotificationTestHelper; 194 private NotificationEntry mRow; 195 private NotificationEntry mRow2; 196 private NotificationEntry mRow3; 197 private ExpandableNotificationRow mNonBubbleNotifRow; 198 private BubbleEntry mBubbleEntry; 199 private BubbleEntry mBubbleEntry2; 200 private BubbleEntry mBubbleEntry3; 201 202 private BubbleEntry mBubbleEntryUser11; 203 private BubbleEntry mBubbleEntry2User11; 204 205 @Mock 206 private Bubbles.BubbleExpandListener mBubbleExpandListener; 207 @Mock 208 private PendingIntent mDeleteIntent; 209 @Mock 210 private SysuiColorExtractor mColorExtractor; 211 @Mock 212 ColorExtractor.GradientColors mGradientColors; 213 @Mock 214 private ShadeController mShadeController; 215 @Mock 216 private NotifPipeline mNotifPipeline; 217 @Mock 218 private FeatureFlags mFeatureFlagsOldPipeline; 219 @Mock 220 private DumpManager mDumpManager; 221 @Mock 222 private NotificationShadeWindowView mNotificationShadeWindowView; 223 @Mock 224 private IStatusBarService mStatusBarService; 225 @Mock 226 private LauncherApps mLauncherApps; 227 @Mock 228 private WindowManagerShellWrapper mWindowManagerShellWrapper; 229 @Mock 230 private BubbleLogger mBubbleLogger; 231 @Mock 232 private TaskStackListenerImpl mTaskStackListener; 233 @Mock 234 private ShellTaskOrganizer mShellTaskOrganizer; 235 @Mock 236 private KeyguardStateController mKeyguardStateController; 237 @Mock 238 private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; 239 @Mock 240 private AuthController mAuthController; 241 242 private TestableBubblePositioner mPositioner; 243 244 private BubbleData mBubbleData; 245 246 private TestableLooper mTestableLooper; 247 248 @Before setUp()249 public void setUp() throws Exception { 250 MockitoAnnotations.initMocks(this); 251 252 mTestableLooper = TestableLooper.get(this); 253 254 // For the purposes of this test, just run everything synchronously 255 ShellExecutor syncExecutor = new SyncExecutor(); 256 257 mContext.addMockSystemService(FaceManager.class, mFaceManager); 258 when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors); 259 260 mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext, 261 mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController, 262 mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController, 263 mColorExtractor, mDumpManager, mKeyguardStateController, 264 mUnlockedScreenOffAnimationController, mAuthController); 265 mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView); 266 mNotificationShadeWindowController.attach(); 267 268 // Need notifications for bubbles 269 mNotificationTestHelper = new NotificationTestHelper( 270 mContext, 271 mDependency, 272 TestableLooper.get(this)); 273 mRow = mNotificationTestHelper.createBubble(mDeleteIntent); 274 mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent); 275 mRow3 = mNotificationTestHelper.createBubble(mDeleteIntent); 276 mNonBubbleNotifRow = mNotificationTestHelper.createRow(); 277 mBubbleEntry = BubblesManager.notifToBubbleEntry(mRow); 278 mBubbleEntry2 = BubblesManager.notifToBubbleEntry(mRow2); 279 mBubbleEntry3 = BubblesManager.notifToBubbleEntry(mRow3); 280 281 UserHandle handle = mock(UserHandle.class); 282 when(handle.getIdentifier()).thenReturn(11); 283 mBubbleEntryUser11 = BubblesManager.notifToBubbleEntry( 284 mNotificationTestHelper.createBubble(handle)); 285 mBubbleEntry2User11 = BubblesManager.notifToBubbleEntry( 286 mNotificationTestHelper.createBubble(handle)); 287 288 // Return non-null notification data from the NEM 289 when(mNotificationEntryManager 290 .getActiveNotificationUnfiltered(mRow.getKey())).thenReturn(mRow); 291 292 mZenModeConfig.suppressedVisualEffects = 0; 293 when(mZenModeController.getConfig()).thenReturn(mZenModeConfig); 294 295 mSysUiState = new SysUiState(); 296 mSysUiState.addCallback(sysUiFlags -> 297 mSysUiStateBubblesExpanded = 298 (sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0); 299 300 // TODO: Fix 301 mPositioner = new TestableBubblePositioner(mContext, mWindowManager); 302 mPositioner.setMaxBubbles(5); 303 mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, syncExecutor); 304 305 TestableNotificationInterruptStateProviderImpl interruptionStateProvider = 306 new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(), 307 mock(PowerManager.class), 308 mock(IDreamManager.class), 309 mock(AmbientDisplayConfiguration.class), 310 mock(NotificationFilter.class), 311 mock(StatusBarStateController.class), 312 mock(BatteryController.class), 313 mock(HeadsUpManager.class), 314 mock(Handler.class) 315 ); 316 317 when(mFeatureFlagsOldPipeline.isNewNotifPipelineRenderingEnabled()).thenReturn(false); 318 when(mShellTaskOrganizer.getExecutor()).thenReturn(syncExecutor); 319 mBubbleController = new TestableBubbleController( 320 mContext, 321 mBubbleData, 322 mFloatingContentCoordinator, 323 mDataRepository, 324 mStatusBarService, 325 mWindowManager, 326 mWindowManagerShellWrapper, 327 mLauncherApps, 328 mBubbleLogger, 329 mTaskStackListener, 330 mShellTaskOrganizer, 331 mPositioner, 332 mock(DisplayController.class), 333 syncExecutor, 334 mock(Handler.class)); 335 mBubbleController.setExpandListener(mBubbleExpandListener); 336 spyOn(mBubbleController); 337 338 mBubblesManager = new BubblesManager( 339 mContext, 340 mBubbleController.asBubbles(), 341 mNotificationShadeWindowController, 342 mStatusBarStateController, 343 mShadeController, 344 mConfigurationController, 345 mStatusBarService, 346 mock(INotificationManager.class), 347 interruptionStateProvider, 348 mZenModeController, 349 mLockscreenUserManager, 350 mNotificationGroupManager, 351 mNotificationEntryManager, 352 mNotifPipeline, 353 mSysUiState, 354 mFeatureFlagsOldPipeline, 355 mDumpManager, 356 syncExecutor); 357 358 // Get a reference to the BubbleController's entry listener 359 verify(mNotificationEntryManager, atLeastOnce()) 360 .addNotificationEntryListener(mEntryListenerCaptor.capture()); 361 mEntryListener = mEntryListenerCaptor.getValue(); 362 // And the remove interceptor 363 verify(mNotificationEntryManager, atLeastOnce()) 364 .addNotificationRemoveInterceptor(mRemoveInterceptorCaptor.capture()); 365 mRemoveInterceptor = mRemoveInterceptorCaptor.getValue(); 366 } 367 368 @Test testAddBubble()369 public void testAddBubble() { 370 mBubbleController.updateBubble(mBubbleEntry); 371 assertTrue(mBubbleController.hasBubbles()); 372 373 assertFalse(mSysUiStateBubblesExpanded); 374 } 375 376 @Test testHasBubbles()377 public void testHasBubbles() { 378 assertFalse(mBubbleController.hasBubbles()); 379 mBubbleController.updateBubble(mBubbleEntry); 380 assertTrue(mBubbleController.hasBubbles()); 381 assertFalse(mSysUiStateBubblesExpanded); 382 } 383 384 @Test testRemoveBubble()385 public void testRemoveBubble() { 386 mBubbleController.updateBubble(mBubbleEntry); 387 assertNotNull(mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey())); 388 assertTrue(mBubbleController.hasBubbles()); 389 verify(mNotificationEntryManager).updateNotifications(any()); 390 391 mBubbleController.removeBubble( 392 mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); 393 assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey())); 394 verify(mNotificationEntryManager, times(2)).updateNotifications(anyString()); 395 396 assertFalse(mSysUiStateBubblesExpanded); 397 } 398 399 @Test testPromoteBubble_autoExpand()400 public void testPromoteBubble_autoExpand() throws Exception { 401 mBubbleController.updateBubble(mBubbleEntry2); 402 mBubbleController.updateBubble(mBubbleEntry); 403 when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getKey())) 404 .thenReturn(mRow); 405 when(mNotificationEntryManager.getPendingOrActiveNotif(mRow2.getKey())) 406 .thenReturn(mRow2); 407 mBubbleController.removeBubble( 408 mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); 409 410 Bubble b = mBubbleData.getOverflowBubbleWithKey(mRow.getKey()); 411 assertThat(mBubbleData.getOverflowBubbles()).isEqualTo(ImmutableList.of(b)); 412 verify(mNotificationEntryManager, never()).performRemoveNotification( 413 eq(mRow.getSbn()), any(), anyInt()); 414 assertThat(mRow.isBubble()).isFalse(); 415 416 Bubble b2 = mBubbleData.getBubbleInStackWithKey(mRow2.getKey()); 417 assertThat(mBubbleData.getSelectedBubble()).isEqualTo(b2); 418 419 mBubbleController.promoteBubbleFromOverflow(b); 420 421 assertThat(b.isBubble()).isTrue(); 422 assertThat(b.shouldAutoExpand()).isTrue(); 423 int flags = Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE 424 | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; 425 verify(mStatusBarService, times(1)).onNotificationBubbleChanged( 426 eq(b.getKey()), eq(true), eq(flags)); 427 } 428 429 @Test testCancelOverflowBubble()430 public void testCancelOverflowBubble() { 431 mBubbleController.updateBubble(mBubbleEntry2); 432 mBubbleController.updateBubble(mBubbleEntry, /* suppressFlyout */ 433 false, /* showInShade */ true); 434 when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getKey())) 435 .thenReturn(mRow); 436 when(mNotificationEntryManager.getPendingOrActiveNotif(mRow2.getKey())) 437 .thenReturn(mRow2); 438 mBubbleController.removeBubble( 439 mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); 440 441 mBubbleController.removeBubble( 442 mRow.getKey(), Bubbles.DISMISS_NOTIF_CANCEL); 443 verify(mNotificationEntryManager, times(1)).performRemoveNotification( 444 eq(mRow.getSbn()), any(), anyInt()); 445 assertThat(mBubbleData.getOverflowBubbles()).isEmpty(); 446 assertFalse(mRow.isBubble()); 447 } 448 449 @Test testUserChange_doesNotRemoveNotif()450 public void testUserChange_doesNotRemoveNotif() { 451 mBubbleController.updateBubble(mBubbleEntry); 452 assertTrue(mBubbleController.hasBubbles()); 453 454 mBubbleController.removeBubble( 455 mRow.getKey(), Bubbles.DISMISS_USER_CHANGED); 456 verify(mNotificationEntryManager, never()).performRemoveNotification( 457 eq(mRow.getSbn()), any(), anyInt()); 458 assertFalse(mBubbleController.hasBubbles()); 459 assertFalse(mSysUiStateBubblesExpanded); 460 assertTrue(mRow.isBubble()); 461 } 462 463 @Test testDismissStack()464 public void testDismissStack() { 465 mBubbleController.updateBubble(mBubbleEntry); 466 verify(mNotificationEntryManager, times(1)).updateNotifications(any()); 467 assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey())); 468 mBubbleController.updateBubble(mBubbleEntry2); 469 verify(mNotificationEntryManager, times(2)).updateNotifications(any()); 470 assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey())); 471 assertTrue(mBubbleController.hasBubbles()); 472 473 mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE); 474 verify(mNotificationEntryManager, times(3)).updateNotifications(any()); 475 assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey())); 476 assertNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey())); 477 478 assertFalse(mSysUiStateBubblesExpanded); 479 } 480 481 @Test testExpandCollapseStack()482 public void testExpandCollapseStack() { 483 assertStackCollapsed(); 484 485 // Mark it as a bubble and add it explicitly 486 mEntryListener.onPendingEntryAdded(mRow); 487 mBubbleController.updateBubble(mBubbleEntry); 488 489 // We should have bubbles & their notifs should not be suppressed 490 assertTrue(mBubbleController.hasBubbles()); 491 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 492 493 // Expand the stack 494 BubbleStackView stackView = mBubbleController.getStackView(); 495 mBubbleData.setExpanded(true); 496 assertStackExpanded(); 497 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); 498 499 assertTrue(mSysUiStateBubblesExpanded); 500 501 // Make sure the notif is suppressed 502 assertBubbleNotificationSuppressedFromShade(mBubbleEntry); 503 504 // Collapse 505 mBubbleController.collapseStack(); 506 verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey()); 507 assertStackCollapsed(); 508 509 assertFalse(mSysUiStateBubblesExpanded); 510 } 511 512 @Test 513 @Ignore("Currently broken.") testCollapseAfterChangingExpandedBubble()514 public void testCollapseAfterChangingExpandedBubble() { 515 // Mark it as a bubble and add it explicitly 516 mEntryListener.onPendingEntryAdded(mRow); 517 mEntryListener.onPendingEntryAdded(mRow2); 518 mBubbleController.updateBubble(mBubbleEntry); 519 mBubbleController.updateBubble(mBubbleEntry2); 520 521 // We should have bubbles & their notifs should not be suppressed 522 assertTrue(mBubbleController.hasBubbles()); 523 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 524 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry2); 525 526 // Expand 527 BubbleStackView stackView = mBubbleController.getStackView(); 528 mBubbleData.setExpanded(true); 529 assertStackExpanded(); 530 verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged( 531 true, mRow2.getKey()); 532 533 assertTrue(mSysUiStateBubblesExpanded); 534 535 // Last added is the one that is expanded 536 assertEquals(mRow2.getKey(), mBubbleData.getSelectedBubble().getKey()); 537 assertBubbleNotificationSuppressedFromShade(mBubbleEntry2); 538 539 // Switch which bubble is expanded 540 mBubbleData.setSelectedBubble(mBubbleData.getBubbleInStackWithKey( 541 mRow.getKey())); 542 mBubbleData.setExpanded(true); 543 assertEquals(mRow.getKey(), mBubbleData.getBubbleInStackWithKey( 544 stackView.getExpandedBubble().getKey()).getKey()); 545 assertBubbleNotificationSuppressedFromShade(mBubbleEntry); 546 547 // collapse for previous bubble 548 verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged( 549 false, mRow2.getKey()); 550 // expand for selected bubble 551 verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged( 552 true, mRow.getKey()); 553 554 // Collapse 555 mBubbleController.collapseStack(); 556 assertStackCollapsed(); 557 558 assertFalse(mSysUiStateBubblesExpanded); 559 } 560 561 @Test testExpansionRemovesShowInShadeAndDot()562 public void testExpansionRemovesShowInShadeAndDot() { 563 // Mark it as a bubble and add it explicitly 564 mEntryListener.onPendingEntryAdded(mRow); 565 mBubbleController.updateBubble(mBubbleEntry); 566 567 // We should have bubbles & their notifs should not be suppressed 568 assertTrue(mBubbleController.hasBubbles()); 569 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 570 571 mTestableLooper.processAllMessages(); 572 assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); 573 574 // Expand 575 mBubbleData.setExpanded(true); 576 assertStackExpanded(); 577 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); 578 579 assertTrue(mSysUiStateBubblesExpanded); 580 581 // Notif is suppressed after expansion 582 assertBubbleNotificationSuppressedFromShade(mBubbleEntry); 583 // Notif shouldn't show dot after expansion 584 assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); 585 } 586 587 @Test testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot()588 public void testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot() { 589 // Mark it as a bubble and add it explicitly 590 mEntryListener.onPendingEntryAdded(mRow); 591 mBubbleController.updateBubble(mBubbleEntry); 592 593 // We should have bubbles & their notifs should not be suppressed 594 assertTrue(mBubbleController.hasBubbles()); 595 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 596 597 mTestableLooper.processAllMessages(); 598 assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); 599 600 // Expand 601 mBubbleData.setExpanded(true); 602 assertStackExpanded(); 603 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); 604 605 assertTrue(mSysUiStateBubblesExpanded); 606 607 // Notif is suppressed after expansion 608 assertBubbleNotificationSuppressedFromShade(mBubbleEntry); 609 // Notif shouldn't show dot after expansion 610 assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); 611 612 // Send update 613 mEntryListener.onPreEntryUpdated(mRow); 614 615 // Nothing should have changed 616 // Notif is suppressed after expansion 617 assertBubbleNotificationSuppressedFromShade(mBubbleEntry); 618 // Notif shouldn't show dot after expansion 619 assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); 620 } 621 622 @Test testRemoveLastExpanded_selectsOverflow()623 public void testRemoveLastExpanded_selectsOverflow() { 624 // Mark it as a bubble and add it explicitly 625 mEntryListener.onPendingEntryAdded(mRow); 626 mEntryListener.onPendingEntryAdded(mRow2); 627 mBubbleController.updateBubble(mBubbleEntry); 628 mBubbleController.updateBubble(mBubbleEntry2); 629 630 // Expand 631 BubbleStackView stackView = mBubbleController.getStackView(); 632 mBubbleData.setExpanded(true); 633 634 assertTrue(mSysUiStateBubblesExpanded); 635 636 assertStackExpanded(); 637 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getKey()); 638 639 // Last added is the one that is expanded 640 assertEquals(mRow2.getKey(), mBubbleData.getBubbleInStackWithKey( 641 stackView.getExpandedBubble().getKey()).getKey()); 642 assertBubbleNotificationSuppressedFromShade(mBubbleEntry2); 643 644 // Dismiss currently expanded 645 mBubbleController.removeBubble( 646 mBubbleData.getBubbleInStackWithKey( 647 stackView.getExpandedBubble().getKey()).getKey(), 648 Bubbles.DISMISS_USER_GESTURE); 649 verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getKey()); 650 651 // Make sure first bubble is selected 652 assertEquals(mRow.getKey(), mBubbleData.getBubbleInStackWithKey( 653 stackView.getExpandedBubble().getKey()).getKey()); 654 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); 655 656 // Dismiss that one 657 mBubbleController.removeBubble( 658 mBubbleData.getBubbleInStackWithKey( 659 stackView.getExpandedBubble().getKey()).getKey(), 660 Bubbles.DISMISS_USER_GESTURE); 661 662 // Overflow should be selected 663 assertEquals(mBubbleData.getSelectedBubble().getKey(), BubbleOverflow.KEY); 664 verify(mBubbleExpandListener).onBubbleExpandChanged(true, BubbleOverflow.KEY); 665 assertTrue(mBubbleController.hasBubbles()); 666 assertTrue(mSysUiStateBubblesExpanded); 667 } 668 669 @Test testRemoveLastExpandedEmptyOverflow_collapses()670 public void testRemoveLastExpandedEmptyOverflow_collapses() { 671 // Mark it as a bubble and add it explicitly 672 mEntryListener.onPendingEntryAdded(mRow); 673 mBubbleController.updateBubble(mBubbleEntry); 674 675 // Expand 676 BubbleStackView stackView = mBubbleController.getStackView(); 677 mBubbleData.setExpanded(true); 678 679 assertTrue(mSysUiStateBubblesExpanded); 680 assertStackExpanded(); 681 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); 682 683 // Block the bubble so it won't be in the overflow 684 mBubbleController.removeBubble( 685 mBubbleData.getBubbleInStackWithKey( 686 stackView.getExpandedBubble().getKey()).getKey(), 687 Bubbles.DISMISS_BLOCKED); 688 689 verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey()); 690 691 // We should be collapsed 692 verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey()); 693 assertFalse(mBubbleController.hasBubbles()); 694 assertFalse(mSysUiStateBubblesExpanded); 695 } 696 697 @Test testAutoExpand_fails_noFlag()698 public void testAutoExpand_fails_noFlag() { 699 assertStackCollapsed(); 700 setMetadataFlags(mRow, 701 Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, false /* enableFlag */); 702 703 // Add the auto expand bubble 704 mEntryListener.onPendingEntryAdded(mRow); 705 mBubbleController.updateBubble(mBubbleEntry); 706 707 // Expansion shouldn't change 708 verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */, 709 mRow.getKey()); 710 assertStackCollapsed(); 711 712 assertFalse(mSysUiStateBubblesExpanded); 713 } 714 715 @Test testAutoExpand_succeeds_withFlag()716 public void testAutoExpand_succeeds_withFlag() { 717 setMetadataFlags(mRow, 718 Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */); 719 720 // Add the auto expand bubble 721 mEntryListener.onPendingEntryAdded(mRow); 722 mBubbleController.updateBubble(mBubbleEntry); 723 724 // Expansion should change 725 verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */, 726 mRow.getKey()); 727 assertStackExpanded(); 728 729 assertTrue(mSysUiStateBubblesExpanded); 730 } 731 732 @Test testSuppressNotif_onInitialNotif()733 public void testSuppressNotif_onInitialNotif() { 734 setMetadataFlags(mRow, 735 Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */); 736 737 // Add the suppress notif bubble 738 mEntryListener.onPendingEntryAdded(mRow); 739 mBubbleController.updateBubble(mBubbleEntry); 740 741 // Notif should be suppressed because we were foreground 742 assertBubbleNotificationSuppressedFromShade(mBubbleEntry); 743 // Dot + flyout is hidden because notif is suppressed 744 assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); 745 assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout()); 746 747 assertFalse(mSysUiStateBubblesExpanded); 748 } 749 750 @Test testSuppressNotif_onUpdateNotif()751 public void testSuppressNotif_onUpdateNotif() { 752 mBubbleController.updateBubble(mBubbleEntry); 753 754 // Should not be suppressed 755 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 756 // Should show dot 757 assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); 758 759 // Update to suppress notif 760 setMetadataFlags(mRow, 761 Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */); 762 mBubbleController.updateBubble(mBubbleEntry); 763 764 // Notif should be suppressed 765 assertBubbleNotificationSuppressedFromShade(mBubbleEntry); 766 // Dot + flyout is hidden because notif is suppressed 767 assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); 768 assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout()); 769 770 assertFalse(mSysUiStateBubblesExpanded); 771 } 772 773 @Test testExpandStackAndSelectBubble_removedFirst()774 public void testExpandStackAndSelectBubble_removedFirst() { 775 mEntryListener.onPendingEntryAdded(mRow); 776 mBubbleController.updateBubble(mBubbleEntry); 777 778 // Simulate notification cancellation. 779 mRemoveInterceptor.onNotificationRemoveRequested( 780 mRow.getKey(), mRow, REASON_APP_CANCEL); 781 782 mBubbleController.expandStackAndSelectBubble(mBubbleEntry); 783 784 assertTrue(mSysUiStateBubblesExpanded); 785 } 786 787 @Test testMarkNewNotificationAsShowInShade()788 public void testMarkNewNotificationAsShowInShade() { 789 mEntryListener.onPendingEntryAdded(mRow); 790 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 791 792 mTestableLooper.processAllMessages(); 793 assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); 794 } 795 796 @Test testAddNotif_notBubble()797 public void testAddNotif_notBubble() { 798 mEntryListener.onPendingEntryAdded(mNonBubbleNotifRow.getEntry()); 799 mEntryListener.onPreEntryUpdated(mNonBubbleNotifRow.getEntry()); 800 801 assertThat(mBubbleController.hasBubbles()).isFalse(); 802 } 803 804 @Test testDeleteIntent_removeBubble_aged()805 public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException { 806 mBubbleController.updateBubble(mBubbleEntry); 807 mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_AGED); 808 verify(mDeleteIntent, never()).send(); 809 } 810 811 @Test testDeleteIntent_removeBubble_user()812 public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException { 813 mBubbleController.updateBubble(mBubbleEntry); 814 mBubbleController.removeBubble( 815 mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); 816 verify(mDeleteIntent, times(1)).send(); 817 } 818 819 @Test testDeleteIntent_dismissStack()820 public void testDeleteIntent_dismissStack() throws PendingIntent.CanceledException { 821 mBubbleController.updateBubble(mBubbleEntry); 822 mBubbleController.updateBubble(mBubbleEntry2); 823 mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE); 824 verify(mDeleteIntent, times(2)).send(); 825 } 826 827 @Test testRemoveBubble_noLongerBubbleAfterUpdate()828 public void testRemoveBubble_noLongerBubbleAfterUpdate() 829 throws PendingIntent.CanceledException { 830 mBubbleController.updateBubble(mBubbleEntry); 831 assertTrue(mBubbleController.hasBubbles()); 832 833 mRow.getSbn().getNotification().flags &= ~FLAG_BUBBLE; 834 NotificationListenerService.Ranking ranking = new RankingBuilder( 835 mRow.getRanking()).setCanBubble(false).build(); 836 mRow.setRanking(ranking); 837 mEntryListener.onPreEntryUpdated(mRow); 838 839 assertFalse(mBubbleController.hasBubbles()); 840 verify(mDeleteIntent, never()).send(); 841 } 842 843 @Test testRemoveBubble_succeeds_appCancel()844 public void testRemoveBubble_succeeds_appCancel() { 845 mEntryListener.onPendingEntryAdded(mRow); 846 mBubbleController.updateBubble(mBubbleEntry); 847 848 assertTrue(mBubbleController.hasBubbles()); 849 850 boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested( 851 mRow.getKey(), mRow, REASON_APP_CANCEL); 852 853 // Cancels always remove so no need to intercept 854 assertFalse(intercepted); 855 } 856 857 @Test testRemoveBubble_entryListenerRemove()858 public void testRemoveBubble_entryListenerRemove() { 859 mEntryListener.onPendingEntryAdded(mRow); 860 mBubbleController.updateBubble(mBubbleEntry); 861 862 assertTrue(mBubbleController.hasBubbles()); 863 864 // Removes the notification 865 mEntryListener.onEntryRemoved(mRow, null, false, REASON_APP_CANCEL); 866 assertFalse(mBubbleController.hasBubbles()); 867 } 868 869 @Test removeBubble_clearAllIntercepted()870 public void removeBubble_clearAllIntercepted() { 871 mEntryListener.onPendingEntryAdded(mRow); 872 mBubbleController.updateBubble(mBubbleEntry); 873 874 assertTrue(mBubbleController.hasBubbles()); 875 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 876 877 boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested( 878 mRow.getKey(), mRow, REASON_CANCEL_ALL); 879 880 // Intercept! 881 assertTrue(intercepted); 882 // Should update show in shade state 883 assertBubbleNotificationSuppressedFromShade(mBubbleEntry); 884 } 885 886 @Test removeBubble_userDismissNotifIntercepted()887 public void removeBubble_userDismissNotifIntercepted() { 888 mEntryListener.onPendingEntryAdded(mRow); 889 mBubbleController.updateBubble(mBubbleEntry); 890 891 assertTrue(mBubbleController.hasBubbles()); 892 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 893 894 boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested( 895 mRow.getKey(), mRow, REASON_CANCEL); 896 897 // Intercept! 898 assertTrue(intercepted); 899 // Should update show in shade state 900 assertBubbleNotificationSuppressedFromShade(mBubbleEntry); 901 } 902 903 @Test removeNotif_inOverflow_intercepted()904 public void removeNotif_inOverflow_intercepted() { 905 // Get bubble with notif in shade. 906 mEntryListener.onPendingEntryAdded(mRow); 907 mBubbleController.updateBubble(mBubbleEntry); 908 assertTrue(mBubbleController.hasBubbles()); 909 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 910 911 // Dismiss the bubble into overflow. 912 mBubbleController.removeBubble( 913 mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); 914 assertFalse(mBubbleController.hasBubbles()); 915 916 boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested( 917 mRow.getKey(), mRow, REASON_CANCEL); 918 919 // Notif is no longer a bubble, but still in overflow, so we intercept removal. 920 assertTrue(intercepted); 921 } 922 923 @Test removeNotif_notInOverflow_notIntercepted()924 public void removeNotif_notInOverflow_notIntercepted() { 925 // Get bubble with notif in shade. 926 mEntryListener.onPendingEntryAdded(mRow); 927 mBubbleController.updateBubble(mBubbleEntry); 928 929 assertTrue(mBubbleController.hasBubbles()); 930 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 931 932 mBubbleController.removeBubble( 933 mRow.getKey(), Bubbles.DISMISS_NO_LONGER_BUBBLE); 934 assertFalse(mBubbleController.hasBubbles()); 935 936 boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested( 937 mRow.getKey(), mRow, REASON_CANCEL); 938 939 // Notif is no longer a bubble, so we should not intercept removal. 940 assertFalse(intercepted); 941 } 942 943 @Test testOverflowBubble_maxReached_notInShade_bubbleRemoved()944 public void testOverflowBubble_maxReached_notInShade_bubbleRemoved() { 945 mBubbleController.updateBubble( 946 mBubbleEntry, /* suppressFlyout */ false, /* showInShade */ false); 947 mBubbleController.updateBubble( 948 mBubbleEntry2, /* suppressFlyout */ false, /* showInShade */ false); 949 mBubbleController.updateBubble( 950 mBubbleEntry3, /* suppressFlyout */ false, /* showInShade */ false); 951 when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getKey())) 952 .thenReturn(mRow); 953 when(mNotificationEntryManager.getPendingOrActiveNotif(mRow2.getKey())) 954 .thenReturn(mRow2); 955 when(mNotificationEntryManager.getPendingOrActiveNotif(mRow3.getKey())) 956 .thenReturn(mRow3); 957 assertEquals(mBubbleData.getBubbles().size(), 3); 958 959 mBubbleData.setMaxOverflowBubbles(1); 960 mBubbleController.removeBubble( 961 mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); 962 assertEquals(mBubbleData.getBubbles().size(), 2); 963 assertEquals(mBubbleData.getOverflowBubbles().size(), 1); 964 965 mBubbleController.removeBubble( 966 mRow2.getKey(), Bubbles.DISMISS_USER_GESTURE); 967 // Overflow max of 1 is reached; mRow is oldest, so it gets removed 968 verify(mNotificationEntryManager, times(1)).performRemoveNotification( 969 eq(mRow.getSbn()), any(), eq(REASON_CANCEL)); 970 assertEquals(mBubbleData.getBubbles().size(), 1); 971 assertEquals(mBubbleData.getOverflowBubbles().size(), 1); 972 } 973 974 @Test testNotifyShadeSuppressionChange_notificationDismiss()975 public void testNotifyShadeSuppressionChange_notificationDismiss() { 976 mEntryListener.onPendingEntryAdded(mRow); 977 978 assertTrue(mBubbleController.hasBubbles()); 979 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 980 981 mRemoveInterceptor.onNotificationRemoveRequested( 982 mRow.getKey(), mRow, REASON_CANCEL); 983 984 // Should update show in shade state 985 assertBubbleNotificationSuppressedFromShade(mBubbleEntry); 986 987 // Should notify delegate that shade state changed 988 verify(mBubbleController).onBubbleNotificationSuppressionChanged( 989 mBubbleData.getBubbleInStackWithKey(mRow.getKey())); 990 } 991 992 @Test testNotifyShadeSuppressionChange_bubbleExpanded()993 public void testNotifyShadeSuppressionChange_bubbleExpanded() { 994 mEntryListener.onPendingEntryAdded(mRow); 995 996 assertTrue(mBubbleController.hasBubbles()); 997 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 998 999 mBubbleData.setExpanded(true); 1000 1001 // Once a bubble is expanded the notif is suppressed 1002 assertBubbleNotificationSuppressedFromShade(mBubbleEntry); 1003 1004 // Should notify delegate that shade state changed 1005 verify(mBubbleController).onBubbleNotificationSuppressionChanged( 1006 mBubbleData.getBubbleInStackWithKey(mRow.getKey())); 1007 } 1008 1009 @Test testBubbleSummaryDismissal_suppressesSummaryAndBubbleFromShade()1010 public void testBubbleSummaryDismissal_suppressesSummaryAndBubbleFromShade() throws Exception { 1011 // GIVEN a group summary with a bubble child 1012 ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); 1013 ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); 1014 when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) 1015 .thenReturn(groupedBubble.getEntry()); 1016 mEntryListener.onPendingEntryAdded(groupedBubble.getEntry()); 1017 groupSummary.addChildNotification(groupedBubble); 1018 assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey())); 1019 1020 // WHEN the summary is dismissed 1021 mBubblesManager.handleDismissalInterception(groupSummary.getEntry()); 1022 1023 // THEN the summary and bubbled child are suppressed from the shade 1024 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( 1025 groupedBubble.getEntry().getKey(), 1026 groupSummary.getEntry().getSbn().getGroupKey())); 1027 assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade( 1028 groupedBubble.getEntry().getKey(), 1029 groupSummary.getEntry().getSbn().getGroupKey())); 1030 assertTrue(mBubbleData.isSummarySuppressed(groupSummary.getEntry().getSbn().getGroupKey())); 1031 } 1032 1033 @Test testAppRemovesSummary_removesAllBubbleChildren()1034 public void testAppRemovesSummary_removesAllBubbleChildren() throws Exception { 1035 // GIVEN a group summary with a bubble child 1036 ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); 1037 ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); 1038 mEntryListener.onPendingEntryAdded(groupedBubble.getEntry()); 1039 when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) 1040 .thenReturn(groupedBubble.getEntry()); 1041 groupSummary.addChildNotification(groupedBubble); 1042 assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey())); 1043 1044 // GIVEN the summary is dismissed 1045 mBubblesManager.handleDismissalInterception(groupSummary.getEntry()); 1046 1047 // WHEN the summary is cancelled by the app 1048 mEntryListener.onEntryRemoved(groupSummary.getEntry(), null, false, REASON_APP_CANCEL); 1049 1050 // THEN the summary and its children are removed from bubble data 1051 assertFalse(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey())); 1052 assertFalse(mBubbleData.isSummarySuppressed( 1053 groupSummary.getEntry().getSbn().getGroupKey())); 1054 } 1055 1056 @Test testSummaryDismissal_marksBubblesHiddenFromShadeAndDismissesNonBubbledChildren()1057 public void testSummaryDismissal_marksBubblesHiddenFromShadeAndDismissesNonBubbledChildren() 1058 throws Exception { 1059 // GIVEN a group summary with two (non-bubble) children and one bubble child 1060 ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2); 1061 ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); 1062 when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) 1063 .thenReturn(groupedBubble.getEntry()); 1064 mEntryListener.onPendingEntryAdded(groupedBubble.getEntry()); 1065 groupSummary.addChildNotification(groupedBubble); 1066 1067 // WHEN the summary is dismissed 1068 mBubblesManager.handleDismissalInterception(groupSummary.getEntry()); 1069 1070 // THEN only the NON-bubble children are dismissed 1071 List<ExpandableNotificationRow> childrenRows = groupSummary.getAttachedChildren(); 1072 verify(mNotificationEntryManager, times(1)).performRemoveNotification( 1073 eq(childrenRows.get(0).getEntry().getSbn()), any(), 1074 eq(REASON_GROUP_SUMMARY_CANCELED)); 1075 verify(mNotificationEntryManager, times(1)).performRemoveNotification( 1076 eq(childrenRows.get(1).getEntry().getSbn()), any(), 1077 eq(REASON_GROUP_SUMMARY_CANCELED)); 1078 verify(mNotificationEntryManager, never()).performRemoveNotification( 1079 eq(groupedBubble.getEntry().getSbn()), any(), anyInt()); 1080 1081 // THEN the bubble child is suppressed from the shade 1082 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( 1083 groupedBubble.getEntry().getKey(), 1084 groupedBubble.getEntry().getSbn().getGroupKey())); 1085 assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade( 1086 groupedBubble.getEntry().getKey(), 1087 groupedBubble.getEntry().getSbn().getGroupKey())); 1088 1089 // THEN the summary is removed from GroupManager 1090 verify(mNotificationGroupManager, times(1)).onEntryRemoved(groupSummary.getEntry()); 1091 } 1092 1093 1094 /** 1095 * Verifies that when a non visually interruptive update occurs for a bubble in the overflow, 1096 * the that bubble does not get promoted from the overflow. 1097 */ 1098 @Test test_notVisuallyInterruptive_updateOverflowBubble_notAdded()1099 public void test_notVisuallyInterruptive_updateOverflowBubble_notAdded() { 1100 // Setup 1101 mBubbleController.updateBubble(mBubbleEntry); 1102 mBubbleController.updateBubble(mBubbleEntry2); 1103 assertTrue(mBubbleController.hasBubbles()); 1104 1105 // Overflow it 1106 mBubbleData.dismissBubbleWithKey(mRow.getKey(), 1107 Bubbles.DISMISS_USER_GESTURE); 1108 assertThat(mBubbleData.hasBubbleInStackWithKey(mRow.getKey())).isFalse(); 1109 assertThat(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey())).isTrue(); 1110 1111 // Test 1112 mBubbleController.updateBubble(mBubbleEntry); 1113 assertThat(mBubbleData.hasBubbleInStackWithKey(mRow.getKey())).isFalse(); 1114 } 1115 1116 /** 1117 * Verifies that when the user changes, the bubbles in the overflow list is cleared. Doesn't 1118 * test the loading from the repository which would be a nice thing to add. 1119 */ 1120 @Test testOnUserChanged_overflowState()1121 public void testOnUserChanged_overflowState() { 1122 int firstUserId = mBubbleEntry.getStatusBarNotification().getUser().getIdentifier(); 1123 int secondUserId = mBubbleEntryUser11.getStatusBarNotification().getUser().getIdentifier(); 1124 1125 mBubbleController.updateBubble(mBubbleEntry); 1126 mBubbleController.updateBubble(mBubbleEntry2); 1127 assertTrue(mBubbleController.hasBubbles()); 1128 mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE); 1129 1130 // Verify these are in the overflow 1131 assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry.getKey())).isNotNull(); 1132 assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry2.getKey())).isNotNull(); 1133 1134 // Switch users 1135 mBubbleController.onUserChanged(secondUserId); 1136 assertThat(mBubbleData.getOverflowBubbles()).isEmpty(); 1137 1138 // Give this user some bubbles 1139 mBubbleController.updateBubble(mBubbleEntryUser11); 1140 mBubbleController.updateBubble(mBubbleEntry2User11); 1141 assertTrue(mBubbleController.hasBubbles()); 1142 mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE); 1143 1144 // Verify these are in the overflow 1145 assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntryUser11.getKey())).isNotNull(); 1146 assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry2User11.getKey())).isNotNull(); 1147 } 1148 1149 1150 /** 1151 * Verifies that the package manager for the user is used when loading info for the bubble. 1152 */ 1153 @Test test_bubbleViewInfoGetPackageForUser()1154 public void test_bubbleViewInfoGetPackageForUser() throws Exception { 1155 final int workProfileUserId = 10; 1156 final UserHandle workUser = new UserHandle(workProfileUserId); 1157 final String workPkg = "work.pkg"; 1158 1159 final Bubble bubble = createBubble(workProfileUserId, workPkg); 1160 assertEquals(workProfileUserId, bubble.getUser().getIdentifier()); 1161 1162 final Context context = setUpContextWithPackageManager(workPkg, null /* AppInfo */); 1163 when(context.getResources()).thenReturn(mContext.getResources()); 1164 final Context userContext = setUpContextWithPackageManager(workPkg, 1165 mock(ApplicationInfo.class)); 1166 1167 // If things are working correctly, StatusBar.getPackageManagerForUser will call this 1168 when(context.createPackageContextAsUser(eq(workPkg), anyInt(), eq(workUser))) 1169 .thenReturn(userContext); 1170 1171 BubbleViewInfoTask.BubbleViewInfo info = BubbleViewInfoTask.BubbleViewInfo.populate(context, 1172 mBubbleController, 1173 mBubbleController.getStackView(), 1174 new BubbleIconFactory(mContext), 1175 bubble, 1176 true /* skipInflation */); 1177 verify(userContext, times(1)).getPackageManager(); 1178 verify(context, times(1)).createPackageContextAsUser(eq(workPkg), 1179 eq(Context.CONTEXT_RESTRICTED), 1180 eq(workUser)); 1181 assertNotNull(info); 1182 } 1183 1184 /** Creates a bubble using the userId and package. */ createBubble(int userId, String pkg)1185 private Bubble createBubble(int userId, String pkg) { 1186 final UserHandle userHandle = new UserHandle(userId); 1187 NotificationEntry workEntry = new NotificationEntryBuilder() 1188 .setPkg(pkg) 1189 .setUser(userHandle) 1190 .build(); 1191 workEntry.setBubbleMetadata(getMetadata()); 1192 workEntry.setFlagBubble(true); 1193 1194 return new Bubble(BubblesManager.notifToBubbleEntry(workEntry), 1195 null, 1196 mock(Bubbles.PendingIntentCanceledListener.class), new SyncExecutor()); 1197 } 1198 1199 /** Creates a context that will return a PackageManager with specific AppInfo. */ setUpContextWithPackageManager(String pkg, ApplicationInfo info)1200 private Context setUpContextWithPackageManager(String pkg, ApplicationInfo info) 1201 throws Exception { 1202 final PackageManager pm = mock(PackageManager.class); 1203 when(pm.getApplicationInfo(eq(pkg), anyInt())).thenReturn(info); 1204 1205 if (info != null) { 1206 Drawable d = mock(Drawable.class); 1207 when(d.getBounds()).thenReturn(new Rect()); 1208 when(pm.getApplicationIcon(anyString())).thenReturn(d); 1209 when(pm.getUserBadgedIcon(any(), any())).thenReturn(d); 1210 } 1211 1212 final Context context = mock(Context.class); 1213 when(context.getPackageName()).thenReturn(pkg); 1214 when(context.getPackageManager()).thenReturn(pm); 1215 return context; 1216 } 1217 1218 /** 1219 * Sets the bubble metadata flags for this entry. These ]flags are normally set by 1220 * NotificationManagerService when the notification is sent, however, these tests do not 1221 * go through that path so we set them explicitly when testing. 1222 */ setMetadataFlags(NotificationEntry entry, int flag, boolean enableFlag)1223 private void setMetadataFlags(NotificationEntry entry, int flag, boolean enableFlag) { 1224 Notification.BubbleMetadata bubbleMetadata = 1225 entry.getSbn().getNotification().getBubbleMetadata(); 1226 int flags = bubbleMetadata.getFlags(); 1227 if (enableFlag) { 1228 flags |= flag; 1229 } else { 1230 flags &= ~flag; 1231 } 1232 bubbleMetadata.setFlags(flags); 1233 } 1234 getMetadata()1235 private Notification.BubbleMetadata getMetadata() { 1236 Intent target = new Intent(mContext, BubblesTestActivity.class); 1237 PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, target, FLAG_MUTABLE); 1238 1239 return new Notification.BubbleMetadata.Builder(bubbleIntent, 1240 Icon.createWithResource(mContext, R.drawable.bubble_ic_create_bubble)) 1241 .build(); 1242 } 1243 1244 /** 1245 * Asserts that the bubble stack is expanded and also validates the cached state is updated. 1246 */ assertStackExpanded()1247 private void assertStackExpanded() { 1248 assertTrue(mBubbleController.isStackExpanded()); 1249 assertTrue(mBubbleController.getImplCachedState().isStackExpanded()); 1250 } 1251 1252 /** 1253 * Asserts that the bubble stack is collapsed and also validates the cached state is updated. 1254 */ assertStackCollapsed()1255 private void assertStackCollapsed() { 1256 assertFalse(mBubbleController.isStackExpanded()); 1257 assertFalse(mBubbleController.getImplCachedState().isStackExpanded()); 1258 } 1259 1260 /** 1261 * Asserts that a bubble notification is suppressed from the shade and also validates the cached 1262 * state is updated. 1263 */ assertBubbleNotificationSuppressedFromShade(BubbleEntry entry)1264 private void assertBubbleNotificationSuppressedFromShade(BubbleEntry entry) { 1265 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( 1266 entry.getKey(), entry.getGroupKey())); 1267 assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade( 1268 entry.getKey(), entry.getGroupKey())); 1269 } 1270 1271 /** 1272 * Asserts that a bubble notification is not suppressed from the shade and also validates the 1273 * cached state is updated. 1274 */ assertBubbleNotificationNotSuppressedFromShade(BubbleEntry entry)1275 private void assertBubbleNotificationNotSuppressedFromShade(BubbleEntry entry) { 1276 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( 1277 entry.getKey(), entry.getGroupKey())); 1278 assertFalse(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade( 1279 entry.getKey(), entry.getGroupKey())); 1280 } 1281 } 1282