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