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