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