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 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.NOTIFICATION_CHANNEL_OR_GROUP_DELETED; 22 import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; 23 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; 24 import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED; 25 26 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; 27 import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE; 28 29 import static com.google.common.truth.Truth.assertThat; 30 31 import static org.junit.Assert.assertEquals; 32 import static org.junit.Assert.assertFalse; 33 import static org.junit.Assert.assertNotNull; 34 import static org.junit.Assert.assertNull; 35 import static org.junit.Assert.assertTrue; 36 import static org.mockito.ArgumentMatchers.any; 37 import static org.mockito.ArgumentMatchers.anyBoolean; 38 import static org.mockito.ArgumentMatchers.anyInt; 39 import static org.mockito.ArgumentMatchers.anyString; 40 import static org.mockito.ArgumentMatchers.eq; 41 import static org.mockito.Mockito.atLeastOnce; 42 import static org.mockito.Mockito.mock; 43 import static org.mockito.Mockito.never; 44 import static org.mockito.Mockito.times; 45 import static org.mockito.Mockito.verify; 46 import static org.mockito.Mockito.when; 47 48 import android.app.ActivityManager; 49 import android.app.IActivityManager; 50 import android.app.INotificationManager; 51 import android.app.Notification; 52 import android.app.PendingIntent; 53 import android.content.BroadcastReceiver; 54 import android.content.Context; 55 import android.content.Intent; 56 import android.content.IntentFilter; 57 import android.content.pm.ApplicationInfo; 58 import android.content.pm.LauncherApps; 59 import android.content.pm.PackageManager; 60 import android.content.pm.UserInfo; 61 import android.graphics.Rect; 62 import android.graphics.drawable.Drawable; 63 import android.graphics.drawable.Icon; 64 import android.hardware.display.AmbientDisplayConfiguration; 65 import android.os.Handler; 66 import android.os.PowerManager; 67 import android.os.RemoteException; 68 import android.os.UserHandle; 69 import android.os.UserManager; 70 import android.service.dreams.IDreamManager; 71 import android.service.notification.NotificationListenerService; 72 import android.service.notification.ZenModeConfig; 73 import android.testing.AndroidTestingRunner; 74 import android.testing.TestableLooper; 75 import android.util.Pair; 76 import android.util.SparseArray; 77 import android.view.View; 78 import android.view.ViewTreeObserver; 79 import android.view.WindowManager; 80 81 import androidx.test.filters.SmallTest; 82 83 import com.android.internal.colorextraction.ColorExtractor; 84 import com.android.internal.logging.UiEventLogger; 85 import com.android.internal.statusbar.IStatusBarService; 86 import com.android.systemui.SysuiTestCase; 87 import com.android.systemui.biometrics.AuthController; 88 import com.android.systemui.colorextraction.SysuiColorExtractor; 89 import com.android.systemui.dump.DumpManager; 90 import com.android.systemui.flags.FeatureFlags; 91 import com.android.systemui.keyguard.KeyguardViewMediator; 92 import com.android.systemui.model.SysUiState; 93 import com.android.systemui.plugins.statusbar.StatusBarStateController; 94 import com.android.systemui.settings.FakeDisplayTracker; 95 import com.android.systemui.settings.UserTracker; 96 import com.android.systemui.shade.NotificationShadeWindowControllerImpl; 97 import com.android.systemui.shade.NotificationShadeWindowView; 98 import com.android.systemui.shade.ShadeController; 99 import com.android.systemui.shade.ShadeExpansionStateManager; 100 import com.android.systemui.shade.ShadeWindowLogger; 101 import com.android.systemui.shared.system.QuickStepContract; 102 import com.android.systemui.statusbar.NotificationLockscreenUserManager; 103 import com.android.systemui.statusbar.RankingBuilder; 104 import com.android.systemui.statusbar.SysuiStatusBarStateController; 105 import com.android.systemui.statusbar.notification.NotifPipelineFlags; 106 import com.android.systemui.statusbar.notification.collection.NotifPipeline; 107 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 108 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; 109 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; 110 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; 111 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; 112 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider; 113 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger; 114 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; 115 import com.android.systemui.statusbar.notification.row.NotificationTestHelper; 116 import com.android.systemui.statusbar.phone.DozeParameters; 117 import com.android.systemui.statusbar.phone.KeyguardBypassController; 118 import com.android.systemui.statusbar.phone.ScreenOffAnimationController; 119 import com.android.systemui.statusbar.policy.BatteryController; 120 import com.android.systemui.statusbar.policy.ConfigurationController; 121 import com.android.systemui.statusbar.policy.HeadsUpManager; 122 import com.android.systemui.statusbar.policy.KeyguardStateController; 123 import com.android.systemui.statusbar.policy.ZenModeController; 124 import com.android.wm.shell.ShellTaskOrganizer; 125 import com.android.wm.shell.TaskViewTransitions; 126 import com.android.wm.shell.WindowManagerShellWrapper; 127 import com.android.wm.shell.bubbles.Bubble; 128 import com.android.wm.shell.bubbles.BubbleBadgeIconFactory; 129 import com.android.wm.shell.bubbles.BubbleData; 130 import com.android.wm.shell.bubbles.BubbleDataRepository; 131 import com.android.wm.shell.bubbles.BubbleEntry; 132 import com.android.wm.shell.bubbles.BubbleIconFactory; 133 import com.android.wm.shell.bubbles.BubbleLogger; 134 import com.android.wm.shell.bubbles.BubbleStackView; 135 import com.android.wm.shell.bubbles.BubbleViewInfoTask; 136 import com.android.wm.shell.bubbles.Bubbles; 137 import com.android.wm.shell.common.DisplayController; 138 import com.android.wm.shell.common.FloatingContentCoordinator; 139 import com.android.wm.shell.common.ShellExecutor; 140 import com.android.wm.shell.common.SyncTransactionQueue; 141 import com.android.wm.shell.common.TaskStackListenerImpl; 142 import com.android.wm.shell.draganddrop.DragAndDropController; 143 import com.android.wm.shell.onehanded.OneHandedController; 144 import com.android.wm.shell.sysui.ShellCommandHandler; 145 import com.android.wm.shell.sysui.ShellController; 146 import com.android.wm.shell.sysui.ShellInit; 147 148 import org.junit.After; 149 import org.junit.Before; 150 import org.junit.Ignore; 151 import org.junit.Test; 152 import org.junit.runner.RunWith; 153 import org.mockito.ArgumentCaptor; 154 import org.mockito.Captor; 155 import org.mockito.Mock; 156 import org.mockito.MockitoAnnotations; 157 import org.mockito.stubbing.Answer; 158 159 import java.util.ArrayList; 160 import java.util.Arrays; 161 import java.util.Collections; 162 import java.util.HashMap; 163 import java.util.List; 164 import java.util.Optional; 165 166 @SmallTest 167 @RunWith(AndroidTestingRunner.class) 168 @TestableLooper.RunWithLooper(setAsMainLooper = true) 169 public class BubblesTest extends SysuiTestCase { 170 @Mock 171 private CommonNotifCollection mCommonNotifCollection; 172 @Mock 173 private BubblesManager.NotifCallback mNotifCallback; 174 @Mock 175 private WindowManager mWindowManager; 176 @Mock 177 private IActivityManager mActivityManager; 178 @Mock 179 private DozeParameters mDozeParameters; 180 @Mock 181 private ConfigurationController mConfigurationController; 182 @Mock 183 private ZenModeController mZenModeController; 184 @Mock 185 private ZenModeConfig mZenModeConfig; 186 @Mock 187 private NotificationLockscreenUserManager mLockscreenUserManager; 188 @Mock 189 private SysuiStatusBarStateController mStatusBarStateController; 190 @Mock 191 private KeyguardViewMediator mKeyguardViewMediator; 192 @Mock 193 private KeyguardBypassController mKeyguardBypassController; 194 @Mock 195 private FloatingContentCoordinator mFloatingContentCoordinator; 196 @Mock 197 private BubbleDataRepository mDataRepository; 198 @Mock 199 private NotificationShadeWindowView mNotificationShadeWindowView; 200 @Mock 201 private AuthController mAuthController; 202 @Mock 203 private ShadeExpansionStateManager mShadeExpansionStateManager; 204 205 private SysUiState mSysUiState; 206 private boolean mSysUiStateBubblesExpanded; 207 private boolean mSysUiStateBubblesManageMenuExpanded; 208 209 @Captor 210 private ArgumentCaptor<NotifCollectionListener> mNotifListenerCaptor; 211 @Captor 212 private ArgumentCaptor<List<Bubble>> mBubbleListCaptor; 213 @Captor 214 private ArgumentCaptor<IntentFilter> mFilterArgumentCaptor; 215 @Captor 216 private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverArgumentCaptor; 217 @Captor 218 private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallbackCaptor; 219 220 private BubblesManager mBubblesManager; 221 private TestableBubbleController mBubbleController; 222 private NotificationShadeWindowControllerImpl mNotificationShadeWindowController; 223 private NotifCollectionListener mEntryListener; 224 private NotificationTestHelper mNotificationTestHelper; 225 private NotificationEntry mRow; 226 private NotificationEntry mRow2; 227 private ExpandableNotificationRow mNonBubbleNotifRow; 228 private BubbleEntry mBubbleEntry; 229 private BubbleEntry mBubbleEntry2; 230 231 private BubbleEntry mBubbleEntryUser11; 232 private BubbleEntry mBubbleEntry2User11; 233 234 private Intent mAppBubbleIntent; 235 236 @Mock 237 private ShellInit mShellInit; 238 @Mock 239 private ShellCommandHandler mShellCommandHandler; 240 @Mock 241 private ShellController mShellController; 242 @Mock 243 private Bubbles.BubbleExpandListener mBubbleExpandListener; 244 @Mock 245 private PendingIntent mDeleteIntent; 246 @Mock 247 private SysuiColorExtractor mColorExtractor; 248 @Mock 249 ColorExtractor.GradientColors mGradientColors; 250 @Mock 251 private ShadeController mShadeController; 252 @Mock 253 private NotifPipeline mNotifPipeline; 254 @Mock 255 private DumpManager mDumpManager; 256 @Mock 257 private IStatusBarService mStatusBarService; 258 @Mock 259 private IDreamManager mIDreamManager; 260 @Mock 261 private NotificationVisibilityProvider mVisibilityProvider; 262 @Mock 263 private LauncherApps mLauncherApps; 264 @Mock 265 private WindowManagerShellWrapper mWindowManagerShellWrapper; 266 @Mock 267 private BubbleLogger mBubbleLogger; 268 @Mock 269 private TaskStackListenerImpl mTaskStackListener; 270 @Mock 271 private ShellTaskOrganizer mShellTaskOrganizer; 272 @Mock 273 private KeyguardStateController mKeyguardStateController; 274 @Mock 275 private ScreenOffAnimationController mScreenOffAnimationController; 276 @Mock 277 private TaskViewTransitions mTaskViewTransitions; 278 @Mock 279 private Optional<OneHandedController> mOneHandedOptional; 280 @Mock 281 private UserManager mUserManager; 282 @Mock 283 private ShadeWindowLogger mShadeWindowLogger; 284 285 private TestableBubblePositioner mPositioner; 286 287 private BubbleData mBubbleData; 288 289 private TestableLooper mTestableLooper; 290 291 private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); 292 293 @Before setUp()294 public void setUp() throws Exception { 295 MockitoAnnotations.initMocks(this); 296 297 mTestableLooper = TestableLooper.get(this); 298 299 // For the purposes of this test, just run everything synchronously 300 ShellExecutor syncExecutor = new SyncExecutor(); 301 302 when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors); 303 when(mNotificationShadeWindowView.getViewTreeObserver()) 304 .thenReturn(mock(ViewTreeObserver.class)); 305 306 mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext, 307 mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController, 308 mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController, 309 mColorExtractor, mDumpManager, mKeyguardStateController, 310 mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager, 311 mShadeWindowLogger); 312 mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView); 313 mNotificationShadeWindowController.attach(); 314 315 // Need notifications for bubbles 316 mNotificationTestHelper = new NotificationTestHelper( 317 mContext, 318 mDependency, 319 TestableLooper.get(this)); 320 mRow = mNotificationTestHelper.createBubble(mDeleteIntent); 321 mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent); 322 mNonBubbleNotifRow = mNotificationTestHelper.createRow(); 323 mBubbleEntry = BubblesManager.notifToBubbleEntry(mRow); 324 mBubbleEntry2 = BubblesManager.notifToBubbleEntry(mRow2); 325 326 UserHandle handle = mock(UserHandle.class); 327 when(handle.getIdentifier()).thenReturn(11); 328 mBubbleEntryUser11 = BubblesManager.notifToBubbleEntry( 329 mNotificationTestHelper.createBubble(handle)); 330 mBubbleEntry2User11 = BubblesManager.notifToBubbleEntry( 331 mNotificationTestHelper.createBubble(handle)); 332 333 mAppBubbleIntent = new Intent(mContext, BubblesTestActivity.class); 334 mAppBubbleIntent.setPackage(mContext.getPackageName()); 335 336 mZenModeConfig.suppressedVisualEffects = 0; 337 when(mZenModeController.getConfig()).thenReturn(mZenModeConfig); 338 339 mSysUiState = new SysUiState(mDisplayTracker); 340 mSysUiState.addCallback(sysUiFlags -> { 341 mSysUiStateBubblesManageMenuExpanded = 342 (sysUiFlags 343 & QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0; 344 mSysUiStateBubblesExpanded = 345 (sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0; 346 }); 347 348 mPositioner = new TestableBubblePositioner(mContext, mWindowManager); 349 mPositioner.setMaxBubbles(5); 350 mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, syncExecutor); 351 352 when(mUserManager.getProfiles(ActivityManager.getCurrentUser())).thenReturn( 353 Collections.singletonList(mock(UserInfo.class))); 354 355 TestableNotificationInterruptStateProviderImpl interruptionStateProvider = 356 new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(), 357 mock(PowerManager.class), 358 mock(AmbientDisplayConfiguration.class), 359 mock(StatusBarStateController.class), 360 mock(KeyguardStateController.class), 361 mock(BatteryController.class), 362 mock(HeadsUpManager.class), 363 mock(NotificationInterruptLogger.class), 364 mock(Handler.class), 365 mock(NotifPipelineFlags.class), 366 mock(KeyguardNotificationVisibilityProvider.class), 367 mock(UiEventLogger.class), 368 mock(UserTracker.class) 369 ); 370 when(mShellTaskOrganizer.getExecutor()).thenReturn(syncExecutor); 371 mBubbleController = new TestableBubbleController( 372 mContext, 373 mShellInit, 374 mShellCommandHandler, 375 mShellController, 376 mBubbleData, 377 mFloatingContentCoordinator, 378 mDataRepository, 379 mStatusBarService, 380 mWindowManager, 381 mWindowManagerShellWrapper, 382 mUserManager, 383 mLauncherApps, 384 mBubbleLogger, 385 mTaskStackListener, 386 mShellTaskOrganizer, 387 mPositioner, 388 mock(DisplayController.class), 389 mOneHandedOptional, 390 mock(DragAndDropController.class), 391 syncExecutor, 392 mock(Handler.class), 393 mTaskViewTransitions, 394 mock(SyncTransactionQueue.class)); 395 mBubbleController.setExpandListener(mBubbleExpandListener); 396 spyOn(mBubbleController); 397 398 mBubblesManager = new BubblesManager( 399 mContext, 400 mBubbleController.asBubbles(), 401 mNotificationShadeWindowController, 402 mKeyguardStateController, 403 mShadeController, 404 mStatusBarService, 405 mock(INotificationManager.class), 406 mIDreamManager, 407 mVisibilityProvider, 408 interruptionStateProvider, 409 mZenModeController, 410 mLockscreenUserManager, 411 mCommonNotifCollection, 412 mNotifPipeline, 413 mSysUiState, 414 mock(FeatureFlags.class), 415 syncExecutor); 416 mBubblesManager.addNotifCallback(mNotifCallback); 417 418 // Get a reference to the BubbleController's entry listener 419 verify(mNotifPipeline, atLeastOnce()) 420 .addCollectionListener(mNotifListenerCaptor.capture()); 421 mEntryListener = mNotifListenerCaptor.getValue(); 422 423 // Get a reference to KeyguardStateController.Callback 424 verify(mKeyguardStateController, atLeastOnce()) 425 .addCallback(mKeyguardStateControllerCallbackCaptor.capture()); 426 } 427 428 @After tearDown()429 public void tearDown() { 430 ArrayList<Bubble> bubbles = new ArrayList<>(mBubbleData.getBubbles()); 431 for (int i = 0; i < bubbles.size(); i++) { 432 mBubbleController.removeBubble(bubbles.get(i).getKey(), 433 Bubbles.DISMISS_NO_LONGER_BUBBLE); 434 } 435 } 436 437 @Test dreamingHidesBubbles()438 public void dreamingHidesBubbles() throws RemoteException { 439 mBubbleController.updateBubble(mBubbleEntry); 440 assertTrue(mBubbleController.hasBubbles()); 441 assertThat(mBubbleController.getStackView().getVisibility()).isEqualTo(View.VISIBLE); 442 443 when(mIDreamManager.isDreamingOrInPreview()).thenReturn(true); // dreaming is happening 444 when(mKeyguardStateController.isShowing()).thenReturn(false); // device is unlocked 445 KeyguardStateController.Callback callback = 446 mKeyguardStateControllerCallbackCaptor.getValue(); 447 callback.onKeyguardShowingChanged(); 448 449 assertThat(mBubbleController.getStackView().getVisibility()).isEqualTo(View.INVISIBLE); 450 } 451 452 @Test instantiateController_addInitCallback()453 public void instantiateController_addInitCallback() { 454 verify(mShellInit, times(1)).addInitCallback(any(), any()); 455 } 456 457 @Test instantiateController_registerConfigChangeListener()458 public void instantiateController_registerConfigChangeListener() { 459 verify(mShellController, times(1)).addConfigurationChangeListener(any()); 460 } 461 462 @Test testAddBubble()463 public void testAddBubble() { 464 mBubbleController.updateBubble(mBubbleEntry); 465 assertTrue(mBubbleController.hasBubbles()); 466 assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); 467 } 468 469 @Test testHasBubbles()470 public void testHasBubbles() { 471 assertFalse(mBubbleController.hasBubbles()); 472 mBubbleController.updateBubble(mBubbleEntry); 473 assertTrue(mBubbleController.hasBubbles()); 474 assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); 475 } 476 477 @Test testRemoveBubble()478 public void testRemoveBubble() { 479 mBubbleController.updateBubble(mBubbleEntry); 480 assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey())); 481 assertTrue(mBubbleController.hasBubbles()); 482 verify(mNotifCallback, times(1)).invalidateNotifications(anyString()); 483 484 mBubbleController.removeBubble( 485 mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); 486 assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey())); 487 verify(mNotifCallback, times(2)).invalidateNotifications(anyString()); 488 489 assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); 490 } 491 492 @Test testRemoveBubble_withDismissedNotif_inOverflow()493 public void testRemoveBubble_withDismissedNotif_inOverflow() { 494 mEntryListener.onEntryAdded(mRow); 495 mBubbleController.updateBubble(mBubbleEntry); 496 497 assertTrue(mBubbleController.hasBubbles()); 498 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 499 500 // Make it look like dismissed notif 501 mBubbleData.getBubbleInStackWithKey(mRow.getKey()).setSuppressNotification(true); 502 503 // Now remove the bubble 504 mBubbleController.removeBubble( 505 mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); 506 assertTrue(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey())); 507 508 // We don't remove the notification since the bubble is still in overflow. 509 verify(mNotifCallback, never()).removeNotification(eq(mRow), any(), anyInt()); 510 assertFalse(mBubbleController.hasBubbles()); 511 } 512 513 @Test testRemoveBubble_withDismissedNotif_notInOverflow()514 public void testRemoveBubble_withDismissedNotif_notInOverflow() { 515 mEntryListener.onEntryAdded(mRow); 516 mBubbleController.updateBubble(mBubbleEntry); 517 when(mCommonNotifCollection.getEntry(mRow.getKey())).thenReturn(mRow); 518 519 assertTrue(mBubbleController.hasBubbles()); 520 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 521 522 // Make it look like dismissed notif 523 mBubbleData.getBubbleInStackWithKey(mRow.getKey()).setSuppressNotification(true); 524 525 // Now remove the bubble 526 mBubbleController.removeBubble( 527 mRow.getKey(), Bubbles.DISMISS_NOTIF_CANCEL); 528 assertFalse(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey())); 529 530 // Since the notif is dismissed and not in overflow, once the bubble is removed, 531 // removeNotification gets called to really remove the notif 532 verify(mNotifCallback, times(1)).removeNotification(eq(mRow), 533 any(), anyInt()); 534 assertFalse(mBubbleController.hasBubbles()); 535 } 536 537 @Test testDismissStack()538 public void testDismissStack() { 539 mBubbleController.updateBubble(mBubbleEntry); 540 verify(mNotifCallback, times(1)).invalidateNotifications(anyString()); 541 assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey())); 542 mBubbleController.updateBubble(mBubbleEntry2); 543 verify(mNotifCallback, times(2)).invalidateNotifications(anyString()); 544 assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey())); 545 assertTrue(mBubbleController.hasBubbles()); 546 547 mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE); 548 verify(mNotifCallback, times(3)).invalidateNotifications(anyString()); 549 assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey())); 550 assertNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey())); 551 552 assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); 553 } 554 555 @Test testExpandCollapseStack()556 public void testExpandCollapseStack() { 557 assertStackCollapsed(); 558 559 // Mark it as a bubble and add it explicitly 560 mEntryListener.onEntryAdded(mRow); 561 mBubbleController.updateBubble(mBubbleEntry); 562 563 // We should have bubbles & their notifs should not be suppressed 564 assertTrue(mBubbleController.hasBubbles()); 565 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 566 567 // Expand the stack 568 mBubbleData.setExpanded(true); 569 assertStackExpanded(); 570 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); 571 assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); 572 573 // Make sure the notif is suppressed 574 assertBubbleNotificationSuppressedFromShade(mBubbleEntry); 575 576 // Collapse 577 mBubbleController.collapseStack(); 578 verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey()); 579 assertStackCollapsed(); 580 assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); 581 } 582 583 @Test 584 @Ignore("Currently broken.") testCollapseAfterChangingExpandedBubble()585 public void testCollapseAfterChangingExpandedBubble() { 586 // Mark it as a bubble and add it explicitly 587 mEntryListener.onEntryAdded(mRow); 588 mEntryListener.onEntryAdded(mRow2); 589 mBubbleController.updateBubble(mBubbleEntry); 590 mBubbleController.updateBubble(mBubbleEntry2); 591 592 // We should have bubbles & their notifs should not be suppressed 593 assertTrue(mBubbleController.hasBubbles()); 594 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 595 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry2); 596 597 // Expand 598 BubbleStackView stackView = mBubbleController.getStackView(); 599 mBubbleData.setExpanded(true); 600 assertStackExpanded(); 601 verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged( 602 true, mRow2.getKey()); 603 assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); 604 605 // Last added is the one that is expanded 606 assertEquals(mRow2.getKey(), mBubbleData.getSelectedBubble().getKey()); 607 assertBubbleNotificationSuppressedFromShade(mBubbleEntry2); 608 609 // Switch which bubble is expanded 610 mBubbleData.setSelectedBubble(mBubbleData.getBubbleInStackWithKey( 611 mRow.getKey())); 612 mBubbleData.setExpanded(true); 613 assertEquals(mRow.getKey(), mBubbleData.getBubbleInStackWithKey( 614 stackView.getExpandedBubble().getKey()).getKey()); 615 assertBubbleNotificationSuppressedFromShade(mBubbleEntry); 616 617 // collapse for previous bubble 618 verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged( 619 false, mRow2.getKey()); 620 // expand for selected bubble 621 verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged( 622 true, mRow.getKey()); 623 624 625 // Collapse 626 mBubbleController.collapseStack(); 627 assertStackCollapsed(); 628 assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); 629 } 630 631 @Test testExpansionRemovesShowInShadeAndDot()632 public void testExpansionRemovesShowInShadeAndDot() { 633 // Mark it as a bubble and add it explicitly 634 mEntryListener.onEntryAdded(mRow); 635 mBubbleController.updateBubble(mBubbleEntry); 636 637 // We should have bubbles & their notifs should not be suppressed 638 assertTrue(mBubbleController.hasBubbles()); 639 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 640 641 mTestableLooper.processAllMessages(); 642 assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); 643 644 // Expand 645 mBubbleData.setExpanded(true); 646 assertStackExpanded(); 647 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); 648 assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); 649 650 // Notif is suppressed after expansion 651 assertBubbleNotificationSuppressedFromShade(mBubbleEntry); 652 // Notif shouldn't show dot after expansion 653 assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); 654 } 655 656 @Test testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot()657 public void testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot() { 658 // Mark it as a bubble and add it explicitly 659 mEntryListener.onEntryAdded(mRow); 660 mBubbleController.updateBubble(mBubbleEntry); 661 662 // We should have bubbles & their notifs should not be suppressed 663 assertTrue(mBubbleController.hasBubbles()); 664 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 665 666 mTestableLooper.processAllMessages(); 667 assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); 668 669 // Expand 670 mBubbleData.setExpanded(true); 671 assertStackExpanded(); 672 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); 673 assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); 674 675 // Notif is suppressed after expansion 676 assertBubbleNotificationSuppressedFromShade(mBubbleEntry); 677 // Notif shouldn't show dot after expansion 678 assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); 679 680 // Send update 681 mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true); 682 683 // Nothing should have changed 684 // Notif is suppressed after expansion 685 assertBubbleNotificationSuppressedFromShade(mBubbleEntry); 686 // Notif shouldn't show dot after expansion 687 assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); 688 } 689 690 @Test testRemoveLastExpanded_collapses()691 public void testRemoveLastExpanded_collapses() { 692 // Mark it as a bubble and add it explicitly 693 mEntryListener.onEntryAdded(mRow); 694 mEntryListener.onEntryAdded(mRow2); 695 mBubbleController.updateBubble(mBubbleEntry); 696 mBubbleController.updateBubble(mBubbleEntry2); 697 698 // Expand 699 BubbleStackView stackView = mBubbleController.getStackView(); 700 mBubbleData.setExpanded(true); 701 702 assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); 703 704 assertStackExpanded(); 705 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getKey()); 706 707 // Last added is the one that is expanded 708 assertEquals(mRow2.getKey(), mBubbleData.getBubbleInStackWithKey( 709 stackView.getExpandedBubble().getKey()).getKey()); 710 assertBubbleNotificationSuppressedFromShade(mBubbleEntry2); 711 712 // Dismiss currently expanded 713 mBubbleController.removeBubble( 714 mBubbleData.getBubbleInStackWithKey( 715 stackView.getExpandedBubble().getKey()).getKey(), 716 Bubbles.DISMISS_USER_GESTURE); 717 verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getKey()); 718 719 // Make sure first bubble is selected 720 assertEquals(mRow.getKey(), mBubbleData.getBubbleInStackWithKey( 721 stackView.getExpandedBubble().getKey()).getKey()); 722 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); 723 724 // Dismiss that one 725 mBubbleController.removeBubble( 726 mBubbleData.getBubbleInStackWithKey( 727 stackView.getExpandedBubble().getKey()).getKey(), 728 Bubbles.DISMISS_USER_GESTURE); 729 730 // We should be collapsed 731 verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey()); 732 assertFalse(mBubbleController.hasBubbles()); 733 assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); 734 } 735 736 @Test testRemoveLastExpandedEmptyOverflow_collapses()737 public void testRemoveLastExpandedEmptyOverflow_collapses() { 738 // Mark it as a bubble and add it explicitly 739 mEntryListener.onEntryAdded(mRow); 740 mBubbleController.updateBubble(mBubbleEntry); 741 742 // Expand 743 BubbleStackView stackView = mBubbleController.getStackView(); 744 mBubbleData.setExpanded(true); 745 746 assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); 747 assertStackExpanded(); 748 verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); 749 750 // Block the bubble so it won't be in the overflow 751 mBubbleController.removeBubble( 752 mBubbleData.getBubbleInStackWithKey( 753 stackView.getExpandedBubble().getKey()).getKey(), 754 Bubbles.DISMISS_BLOCKED); 755 756 verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey()); 757 758 // We should be collapsed 759 verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey()); 760 assertFalse(mBubbleController.hasBubbles()); 761 assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); 762 } 763 764 765 @Test testAutoExpand_fails_noFlag()766 public void testAutoExpand_fails_noFlag() { 767 assertStackCollapsed(); 768 setMetadataFlags(mRow, 769 Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, false /* enableFlag */); 770 771 // Add the auto expand bubble 772 mEntryListener.onEntryAdded(mRow); 773 mBubbleController.updateBubble(mBubbleEntry); 774 775 // Expansion shouldn't change 776 verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */, 777 mRow.getKey()); 778 assertStackCollapsed(); 779 assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); 780 } 781 782 @Test testAutoExpand_succeeds_withFlag()783 public void testAutoExpand_succeeds_withFlag() { 784 setMetadataFlags(mRow, 785 Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */); 786 787 // Add the auto expand bubble 788 mEntryListener.onEntryAdded(mRow); 789 mBubbleController.updateBubble(mBubbleEntry); 790 791 // Expansion should change 792 verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */, 793 mRow.getKey()); 794 assertStackExpanded(); 795 assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); 796 } 797 798 @Test testSuppressNotif_onInitialNotif()799 public void testSuppressNotif_onInitialNotif() { 800 setMetadataFlags(mRow, 801 Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */); 802 803 // Add the suppress notif bubble 804 mEntryListener.onEntryAdded(mRow); 805 mBubbleController.updateBubble(mBubbleEntry); 806 807 // Notif should be suppressed because we were foreground 808 assertBubbleNotificationSuppressedFromShade(mBubbleEntry); 809 // Dot + flyout is hidden because notif is suppressed 810 assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); 811 assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout()); 812 assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); 813 } 814 815 @Test testSuppressNotif_onUpdateNotif()816 public void testSuppressNotif_onUpdateNotif() { 817 mBubbleController.updateBubble(mBubbleEntry); 818 819 // Should not be suppressed 820 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 821 // Should show dot 822 assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); 823 824 // Update to suppress notif 825 setMetadataFlags(mRow, 826 Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */); 827 mBubbleController.updateBubble(mBubbleEntry); 828 829 // Notif should be suppressed 830 assertBubbleNotificationSuppressedFromShade(mBubbleEntry); 831 // Dot + flyout is hidden because notif is suppressed 832 assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); 833 assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout()); 834 assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); 835 } 836 837 @Test testMarkNewNotificationAsShowInShade()838 public void testMarkNewNotificationAsShowInShade() { 839 mEntryListener.onEntryAdded(mRow); 840 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 841 842 mTestableLooper.processAllMessages(); 843 assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); 844 } 845 846 @Test testAddNotif_notBubble()847 public void testAddNotif_notBubble() { 848 mEntryListener.onEntryAdded(mNonBubbleNotifRow.getEntry()); 849 mEntryListener.onEntryUpdated(mNonBubbleNotifRow.getEntry(), /* fromSystem= */ true); 850 851 assertThat(mBubbleController.hasBubbles()).isFalse(); 852 } 853 854 @Test testDeleteIntent_removeBubble_aged()855 public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException { 856 mBubbleController.updateBubble(mBubbleEntry); 857 mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_AGED); 858 verify(mDeleteIntent, never()).send(); 859 } 860 861 @Test testDeleteIntent_removeBubble_user()862 public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException { 863 mBubbleController.updateBubble(mBubbleEntry); 864 mBubbleController.removeBubble( 865 mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); 866 verify(mDeleteIntent, times(1)).send(); 867 } 868 869 @Test testDeleteIntent_dismissStack()870 public void testDeleteIntent_dismissStack() throws PendingIntent.CanceledException { 871 mBubbleController.updateBubble(mBubbleEntry); 872 mBubbleController.updateBubble(mBubbleEntry2); 873 mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE); 874 verify(mDeleteIntent, times(2)).send(); 875 } 876 877 @Test testRemoveBubble_noLongerBubbleAfterUpdate()878 public void testRemoveBubble_noLongerBubbleAfterUpdate() 879 throws PendingIntent.CanceledException { 880 mBubbleController.updateBubble(mBubbleEntry); 881 assertTrue(mBubbleController.hasBubbles()); 882 883 mRow.getSbn().getNotification().flags &= ~FLAG_BUBBLE; 884 NotificationListenerService.Ranking ranking = new RankingBuilder( 885 mRow.getRanking()).setCanBubble(false).build(); 886 mRow.setRanking(ranking); 887 mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true); 888 889 assertFalse(mBubbleController.hasBubbles()); 890 verify(mDeleteIntent, never()).send(); 891 } 892 893 @Test testRemoveBubble_entryListenerRemove()894 public void testRemoveBubble_entryListenerRemove() { 895 mEntryListener.onEntryAdded(mRow); 896 mBubbleController.updateBubble(mBubbleEntry); 897 898 assertTrue(mBubbleController.hasBubbles()); 899 900 // Removes the notification 901 mEntryListener.onEntryRemoved(mRow, REASON_APP_CANCEL); 902 assertFalse(mBubbleController.hasBubbles()); 903 } 904 905 @Test removeBubble_intercepted()906 public void removeBubble_intercepted() { 907 mEntryListener.onEntryAdded(mRow); 908 mBubbleController.updateBubble(mBubbleEntry); 909 910 assertTrue(mBubbleController.hasBubbles()); 911 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 912 913 boolean intercepted = mBubblesManager.handleDismissalInterception(mRow); 914 915 // Intercept! 916 assertTrue(intercepted); 917 // Should update show in shade state 918 assertBubbleNotificationSuppressedFromShade(mBubbleEntry); 919 } 920 921 @Test removeBubble_dismissIntoOverflow_intercepted()922 public void removeBubble_dismissIntoOverflow_intercepted() { 923 mEntryListener.onEntryAdded(mRow); 924 mBubbleController.updateBubble(mBubbleEntry); 925 926 assertTrue(mBubbleController.hasBubbles()); 927 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 928 929 // Dismiss the bubble 930 mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); 931 assertFalse(mBubbleController.hasBubbles()); 932 933 // Dismiss the notification 934 boolean intercepted = mBubblesManager.handleDismissalInterception(mRow); 935 936 // Intercept dismissal since bubble is going into overflow 937 assertTrue(intercepted); 938 } 939 940 @Test removeBubble_notIntercepted()941 public void removeBubble_notIntercepted() { 942 mEntryListener.onEntryAdded(mRow); 943 mBubbleController.updateBubble(mBubbleEntry); 944 945 assertTrue(mBubbleController.hasBubbles()); 946 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 947 948 // Dismiss the bubble 949 mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_NOTIF_CANCEL); 950 assertFalse(mBubbleController.hasBubbles()); 951 952 // Dismiss the notification 953 boolean intercepted = mBubblesManager.handleDismissalInterception(mRow); 954 955 // Not a bubble anymore so we don't intercept dismissal. 956 assertFalse(intercepted); 957 } 958 959 @Test testNotifyShadeSuppressionChange_notificationDismiss()960 public void testNotifyShadeSuppressionChange_notificationDismiss() { 961 mEntryListener.onEntryAdded(mRow); 962 963 assertTrue(mBubbleController.hasBubbles()); 964 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 965 966 mBubblesManager.handleDismissalInterception(mRow); 967 968 // Should update show in shade state 969 assertBubbleNotificationSuppressedFromShade(mBubbleEntry); 970 971 // Should notify delegate that shade state changed 972 verify(mBubbleController).onBubbleMetadataFlagChanged( 973 mBubbleData.getBubbleInStackWithKey(mRow.getKey())); 974 } 975 976 @Test testNotifyShadeSuppressionChange_bubbleExpanded()977 public void testNotifyShadeSuppressionChange_bubbleExpanded() { 978 mEntryListener.onEntryAdded(mRow); 979 980 assertTrue(mBubbleController.hasBubbles()); 981 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 982 983 mBubbleData.setExpanded(true); 984 985 // Once a bubble is expanded the notif is suppressed 986 assertBubbleNotificationSuppressedFromShade(mBubbleEntry); 987 988 // Should notify delegate that shade state changed 989 verify(mBubbleController).onBubbleMetadataFlagChanged( 990 mBubbleData.getBubbleInStackWithKey(mRow.getKey())); 991 } 992 993 @Test testBubbleSummaryDismissal_suppressesSummaryAndBubbleFromShade()994 public void testBubbleSummaryDismissal_suppressesSummaryAndBubbleFromShade() throws Exception { 995 // GIVEN a group summary with a bubble child 996 ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); 997 ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); 998 mEntryListener.onEntryAdded(groupedBubble.getEntry()); 999 when(mCommonNotifCollection.getEntry(groupedBubble.getEntry().getKey())) 1000 .thenReturn(groupedBubble.getEntry()); 1001 groupSummary.addChildNotification(groupedBubble); 1002 assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey())); 1003 1004 // WHEN the summary is dismissed 1005 mBubblesManager.handleDismissalInterception(groupSummary.getEntry()); 1006 1007 // THEN the summary and bubbled child are suppressed from the shade 1008 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( 1009 groupedBubble.getEntry().getKey(), 1010 groupedBubble.getEntry().getSbn().getGroupKey())); 1011 assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade( 1012 groupedBubble.getEntry().getKey(), 1013 groupedBubble.getEntry().getSbn().getGroupKey())); 1014 assertTrue(mBubbleData.isSummarySuppressed(groupSummary.getEntry().getSbn().getGroupKey())); 1015 } 1016 1017 @Test testAppRemovesSummary_removesAllBubbleChildren()1018 public void testAppRemovesSummary_removesAllBubbleChildren() throws Exception { 1019 // GIVEN a group summary with a bubble child 1020 ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); 1021 ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); 1022 mEntryListener.onEntryAdded(groupedBubble.getEntry()); 1023 when(mCommonNotifCollection.getEntry(groupedBubble.getEntry().getKey())) 1024 .thenReturn(groupedBubble.getEntry()); 1025 groupSummary.addChildNotification(groupedBubble); 1026 assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey())); 1027 1028 // GIVEN the summary is dismissed 1029 mBubblesManager.handleDismissalInterception(groupSummary.getEntry()); 1030 1031 // WHEN the summary is cancelled by the app 1032 mEntryListener.onEntryRemoved(groupSummary.getEntry(), REASON_APP_CANCEL); 1033 1034 // THEN the summary and its children are removed from bubble data 1035 assertFalse(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey())); 1036 assertFalse(mBubbleData.isSummarySuppressed( 1037 groupSummary.getEntry().getSbn().getGroupKey())); 1038 } 1039 1040 @Test testSummaryDismissalMarksBubblesHiddenFromShadeAndDismissesNonBubbledChildren()1041 public void testSummaryDismissalMarksBubblesHiddenFromShadeAndDismissesNonBubbledChildren() 1042 throws Exception { 1043 // GIVEN a group summary with two (non-bubble) children and one bubble child 1044 ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2); 1045 ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); 1046 mEntryListener.onEntryAdded(groupedBubble.getEntry()); 1047 when(mCommonNotifCollection.getEntry(groupedBubble.getEntry().getKey())) 1048 .thenReturn(groupedBubble.getEntry()); 1049 groupSummary.addChildNotification(groupedBubble); 1050 1051 // WHEN the summary is dismissed 1052 mBubblesManager.handleDismissalInterception(groupSummary.getEntry()); 1053 1054 // THEN only the NON-bubble children are dismissed 1055 List<ExpandableNotificationRow> childrenRows = groupSummary.getAttachedChildren(); 1056 verify(mNotifCallback, times(1)).removeNotification( 1057 eq(childrenRows.get(0).getEntry()), any(), eq(REASON_GROUP_SUMMARY_CANCELED)); 1058 verify(mNotifCallback, times(1)).removeNotification( 1059 eq(childrenRows.get(1).getEntry()), any(), eq(REASON_GROUP_SUMMARY_CANCELED)); 1060 verify(mNotifCallback, never()).removeNotification(eq(groupedBubble.getEntry()), 1061 any(), anyInt()); 1062 1063 // THEN the bubble child still exists as a bubble and is suppressed from the shade 1064 assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey())); 1065 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( 1066 groupedBubble.getEntry().getKey(), 1067 groupedBubble.getEntry().getSbn().getGroupKey())); 1068 assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade( 1069 groupedBubble.getEntry().getKey(), 1070 groupedBubble.getEntry().getSbn().getGroupKey())); 1071 1072 // THEN the summary is also suppressed from the shade 1073 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( 1074 groupSummary.getEntry().getKey(), 1075 groupSummary.getEntry().getSbn().getGroupKey())); 1076 assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade( 1077 groupSummary.getEntry().getKey(), 1078 groupSummary.getEntry().getSbn().getGroupKey())); 1079 } 1080 1081 1082 /** 1083 * Verifies that when the user changes, the bubbles in the overflow list is cleared. Doesn't 1084 * test the loading from the repository which would be a nice thing to add. 1085 */ 1086 @Test testOnUserChanged_overflowState()1087 public void testOnUserChanged_overflowState() { 1088 int firstUserId = mBubbleEntry.getStatusBarNotification().getUser().getIdentifier(); 1089 int secondUserId = mBubbleEntryUser11.getStatusBarNotification().getUser().getIdentifier(); 1090 1091 mBubbleController.updateBubble(mBubbleEntry); 1092 mBubbleController.updateBubble(mBubbleEntry2); 1093 assertTrue(mBubbleController.hasBubbles()); 1094 mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE); 1095 1096 // Verify these are in the overflow 1097 assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry.getKey())).isNotNull(); 1098 assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry2.getKey())).isNotNull(); 1099 1100 // Switch users 1101 switchUser(secondUserId); 1102 assertThat(mBubbleData.getOverflowBubbles()).isEmpty(); 1103 1104 // Give this user some bubbles 1105 mBubbleController.updateBubble(mBubbleEntryUser11); 1106 mBubbleController.updateBubble(mBubbleEntry2User11); 1107 assertTrue(mBubbleController.hasBubbles()); 1108 mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE); 1109 1110 // Verify these are in the overflow 1111 assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntryUser11.getKey())).isNotNull(); 1112 assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry2User11.getKey())).isNotNull(); 1113 1114 // Would have loaded bubbles twice because of user switch 1115 verify(mDataRepository, times(2)).loadBubbles(anyInt(), any()); 1116 } 1117 1118 @Test testOnUserChanged_bubblesRestored()1119 public void testOnUserChanged_bubblesRestored() { 1120 int firstUserId = mBubbleEntry.getStatusBarNotification().getUser().getIdentifier(); 1121 int secondUserId = mBubbleEntryUser11.getStatusBarNotification().getUser().getIdentifier(); 1122 // Mock current profile 1123 when(mLockscreenUserManager.isCurrentProfile(firstUserId)).thenReturn(true); 1124 when(mLockscreenUserManager.isCurrentProfile(secondUserId)).thenReturn(false); 1125 1126 mBubbleController.updateBubble(mBubbleEntry); 1127 assertThat(mBubbleController.hasBubbles()).isTrue(); 1128 // We start with 1 bubble 1129 assertThat(mBubbleData.getBubbles()).hasSize(1); 1130 1131 // Switch to second user 1132 switchUser(secondUserId); 1133 1134 // Second user has no bubbles 1135 assertThat(mBubbleController.hasBubbles()).isFalse(); 1136 1137 // Send bubble update for first user, ensure it does not show up 1138 mBubbleController.updateBubble(mBubbleEntry2); 1139 assertThat(mBubbleController.hasBubbles()).isFalse(); 1140 1141 // Start returning notif for first user again 1142 when(mCommonNotifCollection.getAllNotifs()).thenReturn(Arrays.asList(mRow, mRow2)); 1143 1144 // Switch back to first user 1145 switchUser(firstUserId); 1146 1147 // Check we now have two bubbles, one previous and one new that came in 1148 assertThat(mBubbleController.hasBubbles()).isTrue(); 1149 // Now there are 2 bubbles 1150 assertThat(mBubbleData.getBubbles()).hasSize(2); 1151 } 1152 1153 /** 1154 * Verifies we only load the overflow data once. 1155 */ 1156 @Test testOverflowLoadedOnce()1157 public void testOverflowLoadedOnce() { 1158 // XXX 1159 when(mCommonNotifCollection.getEntry(mRow.getKey())).thenReturn(mRow); 1160 when(mCommonNotifCollection.getEntry(mRow2.getKey())).thenReturn(mRow2); 1161 1162 mEntryListener.onEntryAdded(mRow); 1163 mEntryListener.onEntryAdded(mRow2); 1164 mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE); 1165 assertThat(mBubbleData.getOverflowBubbles()).isNotEmpty(); 1166 1167 mEntryListener.onEntryRemoved(mRow, REASON_APP_CANCEL); 1168 mEntryListener.onEntryRemoved(mRow2, REASON_APP_CANCEL); 1169 assertThat(mBubbleData.getOverflowBubbles()).isEmpty(); 1170 1171 verify(mDataRepository, times(1)).loadBubbles(anyInt(), any()); 1172 } 1173 1174 /** 1175 * Verifies that shortcut deletions triggers that bubble being removed from XML. 1176 */ 1177 @Test testDeleteShortcutsDeletesXml()1178 public void testDeleteShortcutsDeletesXml() throws Exception { 1179 ExpandableNotificationRow row = mNotificationTestHelper.createShortcutBubble("shortcutId"); 1180 BubbleEntry shortcutBubbleEntry = BubblesManager.notifToBubbleEntry(row.getEntry()); 1181 mBubbleController.updateBubble(shortcutBubbleEntry); 1182 1183 mBubbleData.dismissBubbleWithKey(shortcutBubbleEntry.getKey(), 1184 Bubbles.DISMISS_SHORTCUT_REMOVED); 1185 1186 verify(mDataRepository, atLeastOnce()).removeBubbles(anyInt(), mBubbleListCaptor.capture()); 1187 assertThat(mBubbleListCaptor.getValue().get(0).getKey()).isEqualTo( 1188 shortcutBubbleEntry.getKey()); 1189 } 1190 1191 /** 1192 * Verifies that the package manager for the user is used when loading info for the bubble. 1193 */ 1194 @Test test_bubbleViewInfoGetPackageForUser()1195 public void test_bubbleViewInfoGetPackageForUser() throws Exception { 1196 final int workProfileUserId = 10; 1197 final UserHandle workUser = new UserHandle(workProfileUserId); 1198 final String workPkg = "work.pkg"; 1199 1200 final Bubble bubble = createBubble(workProfileUserId, workPkg); 1201 assertEquals(workProfileUserId, bubble.getUser().getIdentifier()); 1202 1203 final Context context = setUpContextWithPackageManager(workPkg, null /* AppInfo */); 1204 when(context.getResources()).thenReturn(mContext.getResources()); 1205 final Context userContext = setUpContextWithPackageManager(workPkg, 1206 mock(ApplicationInfo.class)); 1207 1208 // If things are working correctly, CentralSurfaces.getPackageManagerForUser will call this 1209 when(context.createPackageContextAsUser(eq(workPkg), anyInt(), eq(workUser))) 1210 .thenReturn(userContext); 1211 1212 BubbleViewInfoTask.BubbleViewInfo info = BubbleViewInfoTask.BubbleViewInfo.populate(context, 1213 mBubbleController, 1214 mBubbleController.getStackView(), 1215 new BubbleIconFactory(mContext), 1216 new BubbleBadgeIconFactory(mContext), 1217 bubble, 1218 true /* skipInflation */); 1219 verify(userContext, times(1)).getPackageManager(); 1220 verify(context, times(1)).createPackageContextAsUser(eq(workPkg), 1221 eq(Context.CONTEXT_RESTRICTED), 1222 eq(workUser)); 1223 assertNotNull(info); 1224 } 1225 1226 @Test testShowManageMenuChangesSysuiState()1227 public void testShowManageMenuChangesSysuiState() { 1228 mBubbleController.updateBubble(mBubbleEntry); 1229 assertTrue(mBubbleController.hasBubbles()); 1230 1231 // Expand the stack 1232 BubbleStackView stackView = mBubbleController.getStackView(); 1233 mBubbleData.setExpanded(true); 1234 assertStackExpanded(); 1235 assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); 1236 1237 // Show the menu 1238 stackView.showManageMenu(true); 1239 assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */); 1240 } 1241 1242 @Test testHideManageMenuChangesSysuiState()1243 public void testHideManageMenuChangesSysuiState() { 1244 mBubbleController.updateBubble(mBubbleEntry); 1245 assertTrue(mBubbleController.hasBubbles()); 1246 1247 // Expand the stack 1248 BubbleStackView stackView = mBubbleController.getStackView(); 1249 mBubbleData.setExpanded(true); 1250 assertStackExpanded(); 1251 assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); 1252 1253 // Show the menu 1254 stackView.showManageMenu(true); 1255 assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */); 1256 1257 // Hide the menu 1258 stackView.showManageMenu(false); 1259 assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); 1260 } 1261 1262 @Test testCollapseBubbleManageMenuChangesSysuiState()1263 public void testCollapseBubbleManageMenuChangesSysuiState() { 1264 mBubbleController.updateBubble(mBubbleEntry); 1265 assertTrue(mBubbleController.hasBubbles()); 1266 1267 // Expand the stack 1268 BubbleStackView stackView = mBubbleController.getStackView(); 1269 mBubbleData.setExpanded(true); 1270 assertStackExpanded(); 1271 assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); 1272 1273 // Show the menu 1274 stackView.showManageMenu(true); 1275 assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */); 1276 1277 // Collapse the stack 1278 mBubbleData.setExpanded(false); 1279 1280 assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); 1281 } 1282 1283 @Test testNotificationChannelModified_channelUpdated_removesOverflowBubble()1284 public void testNotificationChannelModified_channelUpdated_removesOverflowBubble() 1285 throws Exception { 1286 // Setup 1287 ExpandableNotificationRow row = mNotificationTestHelper.createShortcutBubble("shortcutId"); 1288 NotificationEntry entry = row.getEntry(); 1289 entry.getChannel().setConversationId( 1290 row.getEntry().getChannel().getParentChannelId(), 1291 "shortcutId"); 1292 mBubbleController.updateBubble(BubblesManager.notifToBubbleEntry(row.getEntry())); 1293 assertTrue(mBubbleController.hasBubbles()); 1294 1295 // Overflow it 1296 mBubbleData.dismissBubbleWithKey(entry.getKey(), 1297 Bubbles.DISMISS_USER_GESTURE); 1298 assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isTrue(); 1299 1300 // Test 1301 entry.getChannel().setDeleted(true); 1302 mBubbleController.onNotificationChannelModified(entry.getSbn().getPackageName(), 1303 entry.getSbn().getUser(), 1304 entry.getChannel(), 1305 NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); 1306 assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isFalse(); 1307 } 1308 1309 @Test testNotificationChannelModified_channelDeleted_removesOverflowBubble()1310 public void testNotificationChannelModified_channelDeleted_removesOverflowBubble() 1311 throws Exception { 1312 // Setup 1313 ExpandableNotificationRow row = mNotificationTestHelper.createShortcutBubble("shortcutId"); 1314 NotificationEntry entry = row.getEntry(); 1315 entry.getChannel().setConversationId( 1316 row.getEntry().getChannel().getParentChannelId(), 1317 "shortcutId"); 1318 mBubbleController.updateBubble(BubblesManager.notifToBubbleEntry(row.getEntry())); 1319 assertTrue(mBubbleController.hasBubbles()); 1320 1321 // Overflow it 1322 mBubbleData.dismissBubbleWithKey(entry.getKey(), 1323 Bubbles.DISMISS_USER_GESTURE); 1324 assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isTrue(); 1325 1326 // Test 1327 entry.getChannel().setDeleted(true); 1328 mBubbleController.onNotificationChannelModified(entry.getSbn().getPackageName(), 1329 entry.getSbn().getUser(), 1330 entry.getChannel(), 1331 NOTIFICATION_CHANNEL_OR_GROUP_DELETED); 1332 assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isFalse(); 1333 } 1334 1335 @Test testStackViewOnBackPressed_updatesBubbleDataExpandState()1336 public void testStackViewOnBackPressed_updatesBubbleDataExpandState() { 1337 mBubbleController.updateBubble(mBubbleEntry); 1338 1339 // Expand the stack 1340 mBubbleData.setExpanded(true); 1341 assertStackExpanded(); 1342 1343 // Hit back 1344 BubbleStackView stackView = mBubbleController.getStackView(); 1345 stackView.onBackPressed(); 1346 1347 // Make sure we're collapsed 1348 assertStackCollapsed(); 1349 } 1350 1351 1352 @Test testRegisterUnregisterBroadcastListener()1353 public void testRegisterUnregisterBroadcastListener() { 1354 spyOn(mContext); 1355 mBubbleController.updateBubble(mBubbleEntry); 1356 verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), 1357 mFilterArgumentCaptor.capture()); 1358 assertThat(mFilterArgumentCaptor.getValue().getAction(0)).isEqualTo( 1359 Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 1360 assertThat(mFilterArgumentCaptor.getValue().getAction(1)).isEqualTo( 1361 Intent.ACTION_SCREEN_OFF); 1362 1363 mBubbleData.dismissBubbleWithKey(mBubbleEntry.getKey(), REASON_APP_CANCEL); 1364 // TODO: not certain why this isn't called normally when tests are run, perhaps because 1365 // it's after an animation in BSV. This calls BubbleController#removeFromWindowManagerMaybe 1366 mBubbleController.onAllBubblesAnimatedOut(); 1367 1368 verify(mContext).unregisterReceiver(eq(mBroadcastReceiverArgumentCaptor.getValue())); 1369 } 1370 1371 @Test testBroadcastReceiverCloseDialogs_notGestureNav()1372 public void testBroadcastReceiverCloseDialogs_notGestureNav() { 1373 spyOn(mContext); 1374 mBubbleController.updateBubble(mBubbleEntry); 1375 mBubbleData.setExpanded(true); 1376 verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), 1377 mFilterArgumentCaptor.capture()); 1378 Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 1379 mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i); 1380 1381 assertStackExpanded(); 1382 } 1383 1384 @Test testBroadcastReceiverCloseDialogs_reasonGestureNav()1385 public void testBroadcastReceiverCloseDialogs_reasonGestureNav() { 1386 spyOn(mContext); 1387 mBubbleController.updateBubble(mBubbleEntry); 1388 mBubbleData.setExpanded(true); 1389 1390 verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), 1391 mFilterArgumentCaptor.capture()); 1392 Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 1393 i.putExtra("reason", "gestureNav"); 1394 mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i); 1395 assertStackCollapsed(); 1396 } 1397 1398 @Test testBroadcastReceiver_screenOff()1399 public void testBroadcastReceiver_screenOff() { 1400 spyOn(mContext); 1401 mBubbleController.updateBubble(mBubbleEntry); 1402 mBubbleData.setExpanded(true); 1403 1404 verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), 1405 mFilterArgumentCaptor.capture()); 1406 1407 Intent i = new Intent(Intent.ACTION_SCREEN_OFF); 1408 mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i); 1409 assertStackCollapsed(); 1410 } 1411 1412 @Test testOnStatusBarStateChanged()1413 public void testOnStatusBarStateChanged() { 1414 mBubbleController.updateBubble(mBubbleEntry); 1415 mBubbleData.setExpanded(true); 1416 assertStackExpanded(); 1417 BubbleStackView stackView = mBubbleController.getStackView(); 1418 assertThat(stackView.getVisibility()).isEqualTo(View.VISIBLE); 1419 1420 mBubbleController.onStatusBarStateChanged(false); 1421 1422 assertStackCollapsed(); 1423 assertThat(stackView.getVisibility()).isEqualTo(View.INVISIBLE); 1424 1425 mBubbleController.onStatusBarStateChanged(true); 1426 assertThat(stackView.getVisibility()).isEqualTo(View.VISIBLE); 1427 } 1428 1429 /** 1430 * Test to verify behavior for following situation: 1431 * <ul> 1432 * <li>status bar shade state is set to <code>false</code></li> 1433 * <li>there is a bubble pending to be expanded</li> 1434 * </ul> 1435 * Test that duplicate status bar state updates to <code>false</code> do not clear the 1436 * pending bubble to be 1437 * expanded. 1438 */ 1439 @Test testOnStatusBarStateChanged_statusBarChangeDoesNotClearExpandingBubble()1440 public void testOnStatusBarStateChanged_statusBarChangeDoesNotClearExpandingBubble() { 1441 mBubbleController.updateBubble(mBubbleEntry); 1442 mBubbleController.onStatusBarStateChanged(false); 1443 // Set the bubble to expand once status bar state changes 1444 mBubbleController.expandStackAndSelectBubble(mBubbleEntry); 1445 // Check that stack is currently collapsed 1446 assertStackCollapsed(); 1447 // Post status bar state change update with the same value 1448 mBubbleController.onStatusBarStateChanged(false); 1449 // Stack should remain collapsed 1450 assertStackCollapsed(); 1451 // Post status bar state change which should trigger bubble to expand 1452 mBubbleController.onStatusBarStateChanged(true); 1453 assertStackExpanded(); 1454 } 1455 1456 /** 1457 * Test to verify behavior for the following scenario: 1458 * <ol> 1459 * <li>device is locked with keyguard on, status bar shade state updates to 1460 * <code>false</code></li> 1461 * <li>notification entry is marked to be a bubble and it is set to auto-expand</li> 1462 * <li>device unlock starts, status bar shade state receives another update to 1463 * <code>false</code></li> 1464 * <li>device is unlocked and status bar shade state is set to <code>true</code></li> 1465 * <li>bubble should be expanded</li> 1466 * </ol> 1467 */ 1468 @Test testOnStatusBarStateChanged_newAutoExpandedBubbleRemainsExpanded()1469 public void testOnStatusBarStateChanged_newAutoExpandedBubbleRemainsExpanded() { 1470 // Set device as locked 1471 mBubbleController.onStatusBarStateChanged(false); 1472 1473 // Create a auto-expanded bubble 1474 NotificationEntry entry = mNotificationTestHelper.createAutoExpandedBubble(); 1475 mEntryListener.onEntryAdded(entry); 1476 1477 // When unlocking, we may receive duplicate updates with shade=false, ensure they don't 1478 // clear the expanded state 1479 mBubbleController.onStatusBarStateChanged(false); 1480 mBubbleController.onStatusBarStateChanged(true); 1481 1482 // After unlocking, stack should be expanded 1483 assertStackExpanded(); 1484 } 1485 1486 @Test testSetShouldAutoExpand_notifiesFlagChanged()1487 public void testSetShouldAutoExpand_notifiesFlagChanged() { 1488 mBubbleController.updateBubble(mBubbleEntry); 1489 1490 assertTrue(mBubbleController.hasBubbles()); 1491 Bubble b = mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey()); 1492 assertThat(b.shouldAutoExpand()).isFalse(); 1493 1494 // Set it to the same thing 1495 b.setShouldAutoExpand(false); 1496 1497 // Verify it doesn't notify 1498 verify(mBubbleController, never()).onBubbleMetadataFlagChanged(any()); 1499 1500 // Set it to something different 1501 b.setShouldAutoExpand(true); 1502 verify(mBubbleController).onBubbleMetadataFlagChanged(b); 1503 } 1504 1505 @Test testUpdateBubble_skipsDndSuppressListNotifs()1506 public void testUpdateBubble_skipsDndSuppressListNotifs() { 1507 mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(), 1508 mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */, 1509 mRow.shouldSuppressPeek()); 1510 mBubbleEntry.getBubbleMetadata().setFlags( 1511 Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); 1512 1513 mBubbleController.updateBubble(mBubbleEntry); 1514 1515 Bubble b = mBubbleData.getPendingBubbleWithKey(mBubbleEntry.getKey()); 1516 assertThat(b.shouldAutoExpand()).isFalse(); 1517 assertThat(mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey())).isNull(); 1518 } 1519 1520 @Test testOnRankingUpdate_DndSuppressListNotif()1521 public void testOnRankingUpdate_DndSuppressListNotif() { 1522 // It's in the stack 1523 mBubbleController.updateBubble(mBubbleEntry); 1524 assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isTrue(); 1525 1526 // Set current user profile 1527 SparseArray<UserInfo> userInfos = new SparseArray<>(); 1528 userInfos.put(mBubbleEntry.getStatusBarNotification().getUser().getIdentifier(), 1529 mock(UserInfo.class)); 1530 mBubbleController.onCurrentProfilesChanged(userInfos); 1531 1532 // Send ranking update that the notif is suppressed from the list. 1533 HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey = new HashMap<>(); 1534 mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(), 1535 mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */, 1536 mRow.shouldSuppressPeek()); 1537 Pair<BubbleEntry, Boolean> pair = new Pair(mBubbleEntry, true); 1538 entryDataByKey.put(mBubbleEntry.getKey(), pair); 1539 1540 NotificationListenerService.RankingMap rankingMap = 1541 mock(NotificationListenerService.RankingMap.class); 1542 when(rankingMap.getOrderedKeys()).thenReturn(new String[] { mBubbleEntry.getKey() }); 1543 mBubbleController.onRankingUpdated(rankingMap, entryDataByKey); 1544 1545 // Should no longer be in the stack 1546 assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isFalse(); 1547 } 1548 1549 /** 1550 * Verifies that if a bubble is in the overflow and a non-interruptive notification update 1551 * comes in for it, it stays in the overflow but the entry is updated. 1552 */ 1553 @Test testNonInterruptiveUpdate_doesntBubbleFromOverflow()1554 public void testNonInterruptiveUpdate_doesntBubbleFromOverflow() { 1555 mEntryListener.onEntryAdded(mRow); 1556 mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true); 1557 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 1558 1559 // Dismiss the bubble so it's in the overflow 1560 mBubbleController.removeBubble( 1561 mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); 1562 assertThat(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey())).isTrue(); 1563 1564 // Update the entry to not show in shade 1565 setMetadataFlags(mRow, 1566 Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, /* enableFlag= */ true); 1567 mBubbleController.updateBubble(mBubbleEntry, 1568 /* suppressFlyout= */ false, /* showInShade= */ true); 1569 1570 // Check that the update was applied - shouldn't be show in shade 1571 assertBubbleNotificationSuppressedFromShade(mBubbleEntry); 1572 // Check that it wasn't inflated (1 because it would've been inflated via onEntryAdded) 1573 verify(mBubbleController, times(1)).inflateAndAdd( 1574 any(Bubble.class), anyBoolean(), anyBoolean()); 1575 } 1576 1577 /** 1578 * Verifies that if a bubble is active, and a non-interruptive notification update comes in for 1579 * it, it doesn't trigger a new inflate and add for that bubble. 1580 */ 1581 @Test testNonInterruptiveUpdate_doesntTriggerInflate()1582 public void testNonInterruptiveUpdate_doesntTriggerInflate() { 1583 mEntryListener.onEntryAdded(mRow); 1584 mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true); 1585 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 1586 1587 // Update the entry to not show in shade 1588 setMetadataFlags(mRow, 1589 Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, /* enableFlag= */ true); 1590 mBubbleController.updateBubble(mBubbleEntry, 1591 /* suppressFlyout= */ false, /* showInShade= */ true); 1592 1593 // Check that the update was applied - shouldn't be show in shade 1594 assertBubbleNotificationSuppressedFromShade(mBubbleEntry); 1595 // Check that it wasn't inflated (1 because it would've been inflated via onEntryAdded) 1596 verify(mBubbleController, times(1)).inflateAndAdd( 1597 any(Bubble.class), anyBoolean(), anyBoolean()); 1598 } 1599 1600 /** 1601 * Verifies that if a bubble is in the overflow and a non-interruptive notification update 1602 * comes in for it with FLAG_BUBBLE that the flag is removed. 1603 */ 1604 @Test testNonInterruptiveUpdate_doesntOverrideOverflowFlagBubble()1605 public void testNonInterruptiveUpdate_doesntOverrideOverflowFlagBubble() { 1606 mEntryListener.onEntryAdded(mRow); 1607 mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true); 1608 assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); 1609 1610 // Dismiss the bubble so it's in the overflow 1611 mBubbleController.removeBubble( 1612 mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); 1613 assertThat(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey())).isTrue(); 1614 // Once it's in the overflow it's not actively a bubble (doesn't have FLAG_BUBBLE) 1615 Bubble b = mBubbleData.getOverflowBubbleWithKey(mBubbleEntry.getKey()); 1616 assertThat(b.isBubble()).isFalse(); 1617 1618 // Send a non-notifying update that has FLAG_BUBBLE 1619 mRow.getSbn().getNotification().flags = FLAG_BUBBLE; 1620 assertThat(mRow.getSbn().getNotification().isBubbleNotification()).isTrue(); 1621 mBubbleController.updateBubble(mBubbleEntry, 1622 /* suppressFlyout= */ false, /* showInShade= */ true); 1623 1624 // Verify that it still doesn't have FLAG_BUBBLE because it's in the overflow. 1625 b = mBubbleData.getOverflowBubbleWithKey(mBubbleEntry.getKey()); 1626 assertThat(b.isBubble()).isFalse(); 1627 } 1628 1629 @Test testNonSystemUpdatesIgnored()1630 public void testNonSystemUpdatesIgnored() { 1631 mEntryListener.onEntryAdded(mRow); 1632 assertThat(mBubbleController.hasBubbles()).isTrue(); 1633 1634 mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ false); 1635 mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ false); 1636 mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ false); 1637 1638 // Check that it wasn't inflated (1 because it would've been inflated via onEntryAdded) 1639 verify(mBubbleController, times(1)).inflateAndAdd( 1640 any(Bubble.class), anyBoolean(), anyBoolean()); 1641 } 1642 1643 @Test testShowOrHideAppBubble_addsAndExpand()1644 public void testShowOrHideAppBubble_addsAndExpand() { 1645 assertThat(mBubbleController.isStackExpanded()).isFalse(); 1646 assertThat(mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE)).isNull(); 1647 1648 mBubbleController.showOrHideAppBubble(mAppBubbleIntent); 1649 1650 verify(mBubbleController).inflateAndAdd(any(Bubble.class), /* suppressFlyout= */ eq(true), 1651 /* showInShade= */ eq(false)); 1652 assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE); 1653 assertThat(mBubbleController.isStackExpanded()).isTrue(); 1654 } 1655 1656 @Test testShowOrHideAppBubble_expandIfCollapsed()1657 public void testShowOrHideAppBubble_expandIfCollapsed() { 1658 mBubbleController.showOrHideAppBubble(mAppBubbleIntent); 1659 mBubbleController.updateBubble(mBubbleEntry); 1660 mBubbleController.collapseStack(); 1661 assertThat(mBubbleController.isStackExpanded()).isFalse(); 1662 1663 // Calling this while collapsed will expand the app bubble 1664 mBubbleController.showOrHideAppBubble(mAppBubbleIntent); 1665 1666 assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE); 1667 assertThat(mBubbleController.isStackExpanded()).isTrue(); 1668 assertThat(mBubbleData.getBubbles().size()).isEqualTo(2); 1669 } 1670 1671 @Test testShowOrHideAppBubble_collapseIfSelected()1672 public void testShowOrHideAppBubble_collapseIfSelected() { 1673 mBubbleController.showOrHideAppBubble(mAppBubbleIntent); 1674 assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE); 1675 assertThat(mBubbleController.isStackExpanded()).isTrue(); 1676 1677 // Calling this while the app bubble is expanded should collapse the stack 1678 mBubbleController.showOrHideAppBubble(mAppBubbleIntent); 1679 1680 assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE); 1681 assertThat(mBubbleController.isStackExpanded()).isFalse(); 1682 assertThat(mBubbleData.getBubbles().size()).isEqualTo(1); 1683 } 1684 1685 @Test testShowOrHideAppBubble_selectIfNotSelected()1686 public void testShowOrHideAppBubble_selectIfNotSelected() { 1687 mBubbleController.showOrHideAppBubble(mAppBubbleIntent); 1688 mBubbleController.updateBubble(mBubbleEntry); 1689 mBubbleController.expandStackAndSelectBubble(mBubbleEntry); 1690 assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(mBubbleEntry.getKey()); 1691 assertThat(mBubbleController.isStackExpanded()).isTrue(); 1692 1693 mBubbleController.showOrHideAppBubble(mAppBubbleIntent); 1694 assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE); 1695 assertThat(mBubbleController.isStackExpanded()).isTrue(); 1696 assertThat(mBubbleData.getBubbles().size()).isEqualTo(2); 1697 } 1698 1699 /** Creates a bubble using the userId and package. */ createBubble(int userId, String pkg)1700 private Bubble createBubble(int userId, String pkg) { 1701 final UserHandle userHandle = new UserHandle(userId); 1702 NotificationEntry workEntry = new NotificationEntryBuilder() 1703 .setPkg(pkg) 1704 .setUser(userHandle) 1705 .build(); 1706 workEntry.setBubbleMetadata(getMetadata()); 1707 workEntry.setFlagBubble(true); 1708 1709 return new Bubble(BubblesManager.notifToBubbleEntry(workEntry), 1710 null, 1711 mock(Bubbles.PendingIntentCanceledListener.class), new SyncExecutor()); 1712 } 1713 1714 /** Creates a context that will return a PackageManager with specific AppInfo. */ setUpContextWithPackageManager(String pkg, ApplicationInfo info)1715 private Context setUpContextWithPackageManager(String pkg, ApplicationInfo info) 1716 throws Exception { 1717 final PackageManager pm = mock(PackageManager.class); 1718 when(pm.getApplicationInfo(eq(pkg), anyInt())).thenReturn(info); 1719 1720 if (info != null) { 1721 Drawable d = mock(Drawable.class); 1722 when(d.getBounds()).thenReturn(new Rect()); 1723 when(pm.getApplicationIcon(anyString())).thenReturn(d); 1724 when(pm.getUserBadgedIcon(any(), any())).thenReturn(d); 1725 } 1726 1727 final Context context = mock(Context.class); 1728 when(context.getPackageName()).thenReturn(pkg); 1729 when(context.getPackageManager()).thenReturn(pm); 1730 return context; 1731 } 1732 1733 /** 1734 * Sets the bubble metadata flags for this entry. These flags are normally set by 1735 * NotificationManagerService when the notification is sent, however, these tests do not 1736 * go through that path so we set them explicitly when testing. 1737 */ setMetadataFlags(NotificationEntry entry, int flag, boolean enableFlag)1738 private void setMetadataFlags(NotificationEntry entry, int flag, boolean enableFlag) { 1739 Notification.BubbleMetadata bubbleMetadata = 1740 entry.getSbn().getNotification().getBubbleMetadata(); 1741 int flags = bubbleMetadata.getFlags(); 1742 if (enableFlag) { 1743 flags |= flag; 1744 } else { 1745 flags &= ~flag; 1746 } 1747 bubbleMetadata.setFlags(flags); 1748 } 1749 getMetadata()1750 private Notification.BubbleMetadata getMetadata() { 1751 Intent target = new Intent(mContext, BubblesTestActivity.class); 1752 PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, target, FLAG_MUTABLE); 1753 return new Notification.BubbleMetadata.Builder( 1754 bubbleIntent, 1755 Icon.createWithResource( 1756 mContext, 1757 com.android.wm.shell.R.drawable.bubble_ic_create_bubble)) 1758 .build(); 1759 } 1760 switchUser(int userId)1761 private void switchUser(int userId) { 1762 when(mLockscreenUserManager.isCurrentProfile(anyInt())).thenAnswer( 1763 (Answer<Boolean>) invocation -> invocation.<Integer>getArgument(0) == userId); 1764 SparseArray<UserInfo> userInfos = new SparseArray<>(1); 1765 userInfos.put(userId, mock(UserInfo.class)); 1766 mBubbleController.onCurrentProfilesChanged(userInfos); 1767 mBubbleController.onUserChanged(userId); 1768 } 1769 1770 /** 1771 * Asserts that the bubble stack is expanded and also validates the cached state is updated. 1772 */ assertStackExpanded()1773 private void assertStackExpanded() { 1774 assertTrue(mBubbleController.isStackExpanded()); 1775 assertTrue(mBubbleController.getImplCachedState().isStackExpanded()); 1776 } 1777 1778 /** 1779 * Asserts that the bubble stack is collapsed and also validates the cached state is updated. 1780 */ assertStackCollapsed()1781 private void assertStackCollapsed() { 1782 assertFalse(mBubbleController.isStackExpanded()); 1783 assertFalse(mBubbleController.getImplCachedState().isStackExpanded()); 1784 } 1785 1786 /** 1787 * Asserts that a bubble notification is suppressed from the shade and also validates the cached 1788 * state is updated. 1789 */ assertBubbleNotificationSuppressedFromShade(BubbleEntry entry)1790 private void assertBubbleNotificationSuppressedFromShade(BubbleEntry entry) { 1791 assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( 1792 entry.getKey(), entry.getGroupKey())); 1793 assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade( 1794 entry.getKey(), entry.getGroupKey())); 1795 } 1796 1797 /** 1798 * Asserts that a bubble notification is not suppressed from the shade and also validates the 1799 * cached state is updated. 1800 */ assertBubbleNotificationNotSuppressedFromShade(BubbleEntry entry)1801 private void assertBubbleNotificationNotSuppressedFromShade(BubbleEntry entry) { 1802 assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( 1803 entry.getKey(), entry.getGroupKey())); 1804 assertFalse(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade( 1805 entry.getKey(), entry.getGroupKey())); 1806 } 1807 1808 /** 1809 * Asserts that the system ui states associated to bubbles are in the correct state. 1810 */ assertSysuiStates(boolean stackExpanded, boolean manageMenuExpanded)1811 private void assertSysuiStates(boolean stackExpanded, boolean manageMenuExpanded) { 1812 assertThat(mSysUiStateBubblesExpanded).isEqualTo(stackExpanded); 1813 assertThat(mSysUiStateBubblesManageMenuExpanded).isEqualTo(manageMenuExpanded); 1814 } 1815 } 1816