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