Allow Bubble overflow to be opened without other active bubbles

Currently you can only access the overflow if you have bubbles in
the stack. There are a bunch of explorations that show accessing
the overflow without the stack (e.g. from taskbar, from people area).

- BubbleData holds onto BubbleOverflow now
- When last bubble is removed, if showing in taskbar/already expanded
  & has overflow bubbs, select the overflow.
- No longer null out BubbleStackView when no more bubbles, this
  allows overflow to continue to show
- Added tests for overflow behaviour in BubblesTest

Test: atest SystemUITests
Test: manua 1) - produce a bunch of bubbles
               - expand the stack, dismiss each bubble
               => when you dismiss the last one, overflow selected
               - collapse the stack
               => overflow goes away
            2) - have taskbar cl / prototype
               - have bubbles in the overflow but no active bubbles
               => observe that taskbar shows the overflow (i.e. sysui
                 renders the stack in that spot)
Bug: 171322463
Bug: 167413172
Change-Id: I36b9cedc492ace59105dba58c89f9ef92ff4b9e8
This commit is contained in:
Mady Mellor
2020-11-11 11:23:09 -08:00
parent 97fcd7a082
commit 157cd3f736
7 changed files with 163 additions and 59 deletions

View File

@@ -171,7 +171,7 @@ public class BubbleController implements Bubbles {
ShellTaskOrganizer organizer) {
BubbleLogger logger = new BubbleLogger(uiEventLogger);
BubblePositioner positioner = new BubblePositioner(context, windowManager);
BubbleData data = new BubbleData(context, logger);
BubbleData data = new BubbleData(context, logger, positioner);
return new BubbleController(context, data, synchronizer, floatingContentCoordinator,
new BubbleDataRepository(context, launcherApps),
statusBarService, windowManager, windowManagerShellWrapper, launcherApps,
@@ -382,7 +382,6 @@ public class BubbleController implements Bubbles {
if (mStackView == null) {
mStackView = new BubbleStackView(
mContext, this, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator);
mStackView.addView(mBubbleScrim);
mStackView.onOrientationChanged();
if (mExpandListener != null) {
mStackView.setExpandListener(mExpandListener);
@@ -422,6 +421,8 @@ public class BubbleController implements Bubbles {
try {
mAddedToWindowManager = true;
mBubbleData.getOverflow().initialize(this);
mStackView.addView(mBubbleScrim);
mWindowManager.addView(mStackView, mWmLayoutParams);
// Position info is dependent on us being attached to a window
mBubblePositioner.update(mOrientation);
@@ -446,7 +447,7 @@ public class BubbleController implements Bubbles {
if (mStackView != null) {
mWindowManager.removeView(mStackView);
mStackView.removeView(mBubbleScrim);
mStackView = null;
mBubbleData.getOverflow().cleanUpExpandedState();
} else {
Log.w(TAG, "StackView added to WindowManager, but was null when removing!");
}
@@ -578,7 +579,7 @@ public class BubbleController implements Bubbles {
if (mStackView == null) {
return false;
}
return mBubbleData.hasBubbles();
return mBubbleData.hasBubbles() || mBubbleData.isShowingOverflow();
}
@Override

View File

@@ -69,7 +69,7 @@ public class BubbleData {
boolean selectionChanged;
boolean orderChanged;
boolean expanded;
@Nullable Bubble selectedBubble;
@Nullable BubbleViewProvider selectedBubble;
@Nullable Bubble addedBubble;
@Nullable Bubble updatedBubble;
@Nullable Bubble addedOverflowBubble;
@@ -116,13 +116,15 @@ public class BubbleData {
}
private final Context mContext;
private final BubblePositioner mPositioner;
/** Bubbles that are actively in the stack. */
private final List<Bubble> mBubbles;
/** Bubbles that aged out to overflow. */
private final List<Bubble> mOverflowBubbles;
/** Bubbles that are being loaded but haven't been added to the stack just yet. */
private final HashMap<String, Bubble> mPendingBubbles;
private Bubble mSelectedBubble;
private BubbleViewProvider mSelectedBubble;
private final BubbleOverflow mOverflow;
private boolean mShowingOverflow;
private boolean mExpanded;
private final int mMaxBubbles;
@@ -153,9 +155,11 @@ public class BubbleData {
*/
private HashMap<String, String> mSuppressedGroupKeys = new HashMap<>();
public BubbleData(Context context, BubbleLogger bubbleLogger) {
public BubbleData(Context context, BubbleLogger bubbleLogger, BubblePositioner positioner) {
mContext = context;
mLogger = bubbleLogger;
mPositioner = positioner;
mOverflow = new BubbleOverflow(context, positioner);
mBubbles = new ArrayList<>();
mOverflowBubbles = new ArrayList<>();
mPendingBubbles = new HashMap<>();
@@ -178,6 +182,10 @@ public class BubbleData {
return !mBubbles.isEmpty();
}
public boolean hasOverflowBubbles() {
return !mOverflowBubbles.isEmpty();
}
public boolean isExpanded() {
return mExpanded;
}
@@ -195,10 +203,14 @@ public class BubbleData {
}
@Nullable
public Bubble getSelectedBubble() {
public BubbleViewProvider getSelectedBubble() {
return mSelectedBubble;
}
public BubbleOverflow getOverflow() {
return mOverflow;
}
/** Return a read-only current active bubble lists. */
public List<Bubble> getActiveBubbles() {
return Collections.unmodifiableList(mBubbles);
@@ -212,7 +224,7 @@ public class BubbleData {
dispatchPendingChanges();
}
public void setSelectedBubble(Bubble bubble) {
public void setSelectedBubble(BubbleViewProvider bubble) {
if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "setSelectedBubble: " + bubble);
}
@@ -224,6 +236,10 @@ public class BubbleData {
mShowingOverflow = showingOverflow;
}
boolean isShowingOverflow() {
return mShowingOverflow && (isExpanded() || mPositioner.showingInTaskbar());
}
/**
* Constructs a new bubble or returns an existing one. Does not add new bubbles to
* bubble data, must go through {@link #notificationEntryUpdated(Bubble, boolean, boolean)}
@@ -264,8 +280,8 @@ public class BubbleData {
/**
* When this method is called it is expected that all info in the bubble has completed loading.
* @see Bubble#inflate(BubbleViewInfoTask.Callback, Context,
* BubbleStackView, BubbleIconFactory, boolean).
* @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, BubbleController, BubbleStackView,
* BubbleIconFactory, boolean)
*/
void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
if (DEBUG_BUBBLE_DATA) {
@@ -484,10 +500,17 @@ public class BubbleData {
Bubble bubbleToRemove = mBubbles.get(indexToRemove);
bubbleToRemove.stopInflation();
if (mBubbles.size() == 1) {
// Going to become empty, handle specially.
setExpandedInternal(false);
// Don't use setSelectedBubbleInternal because we don't want to trigger an applyUpdate
mSelectedBubble = null;
if (hasOverflowBubbles() && (mPositioner.showingInTaskbar() || isExpanded())) {
// No more active bubbles but we have stuff in the overflow -- select that view
// if we're already expanded or always showing.
setShowingOverflow(true);
setSelectedBubbleInternal(mOverflow);
} else {
setExpandedInternal(false);
// Don't use setSelectedBubbleInternal because we don't want to trigger an
// applyUpdate
mSelectedBubble = null;
}
}
if (indexToRemove < mBubbles.size() - 1) {
// Removing anything but the last bubble means positions will change.
@@ -505,7 +528,7 @@ public class BubbleData {
if (Objects.equals(mSelectedBubble, bubbleToRemove)) {
// Move selection to the new bubble at the same position.
int newIndex = Math.min(indexToRemove, mBubbles.size() - 1);
Bubble newSelected = mBubbles.get(newIndex);
BubbleViewProvider newSelected = mBubbles.get(newIndex);
setSelectedBubbleInternal(newSelected);
}
maybeSendDeleteIntent(reason, bubbleToRemove);
@@ -564,7 +587,7 @@ public class BubbleData {
*
* @param bubble the new selected bubble
*/
private void setSelectedBubbleInternal(@Nullable Bubble bubble) {
private void setSelectedBubbleInternal(@Nullable BubbleViewProvider bubble) {
if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "setSelectedBubbleInternal: " + bubble);
}
@@ -572,14 +595,17 @@ public class BubbleData {
return;
}
// Otherwise, if we are showing the overflow menu, return to the previously selected bubble.
if (bubble != null && !mBubbles.contains(bubble) && !mOverflowBubbles.contains(bubble)) {
boolean isOverflow = bubble != null && BubbleOverflow.KEY.equals(bubble.getKey());
if (bubble != null
&& !mBubbles.contains(bubble)
&& !mOverflowBubbles.contains(bubble)
&& !isOverflow) {
Log.e(TAG, "Cannot select bubble which doesn't exist!"
+ " (" + bubble + ") bubbles=" + mBubbles);
return;
}
if (mExpanded && bubble != null) {
bubble.markAsAccessedAt(mTimeSource.currentTimeMillis());
if (mExpanded && bubble != null && !isOverflow) {
((Bubble) bubble).markAsAccessedAt(mTimeSource.currentTimeMillis());
}
mSelectedBubble = bubble;
mStateChange.selectedBubble = bubble;
@@ -629,7 +655,7 @@ public class BubbleData {
return;
}
if (shouldExpand) {
if (mBubbles.isEmpty()) {
if (mBubbles.isEmpty() && !mShowingOverflow) {
Log.e(TAG, "Attempt to expand stack when empty!");
return;
}
@@ -637,7 +663,9 @@ public class BubbleData {
Log.e(TAG, "Attempt to expand stack without selected bubble!");
return;
}
mSelectedBubble.markAsAccessedAt(mTimeSource.currentTimeMillis());
if (mSelectedBubble instanceof Bubble) {
((Bubble) mSelectedBubble).markAsAccessedAt(mTimeSource.currentTimeMillis());
}
mStateChange.orderChanged |= repackAll();
} else if (!mBubbles.isEmpty()) {
// Apply ordering and grouping rules from expanded -> collapsed, then save
@@ -647,14 +675,18 @@ public class BubbleData {
if (mShowingOverflow) {
// Show previously selected bubble instead of overflow menu on next expansion.
setSelectedBubbleInternal(mSelectedBubble);
if (!mSelectedBubble.getKey().equals(mOverflow.getKey())) {
setSelectedBubbleInternal(mSelectedBubble);
} else {
setSelectedBubbleInternal(mBubbles.get(0));
}
}
if (mBubbles.indexOf(mSelectedBubble) > 0) {
// Move the selected bubble to the top while collapsed.
int index = mBubbles.indexOf(mSelectedBubble);
if (index != 0) {
mBubbles.remove(mSelectedBubble);
mBubbles.add(0, mSelectedBubble);
mBubbles.remove((Bubble) mSelectedBubble);
mBubbles.add(0, (Bubble) mSelectedBubble);
mStateChange.orderChanged = true;
}
}

View File

@@ -63,11 +63,17 @@ class BubbleOverflow(
overflowBtn = null
}
/** Call before use and again if cleanUpExpandedState was called. */
fun initialize(controller: BubbleController) {
getExpandedView()?.initialize(controller, controller.stackView)
getExpandedView()?.setOverflow(true)
}
fun cleanUpExpandedState() {
expandedView?.cleanUpExpandedState()
expandedView = null
}
fun update() {
updateResources()
getExpandedView()?.applyThemeAttrs()
@@ -132,11 +138,6 @@ class BubbleOverflow(
overflowBtn?.updateDotVisibility(true /* animate */)
}
fun cleanUpExpandedState() {
expandedView?.cleanUpExpandedState()
expandedView = null
}
override fun getExpandedView(): BubbleExpandedView? {
if (expandedView == null) {
expandedView = inflater.inflate(R.layout.bubble_expanded_view,

View File

@@ -227,7 +227,7 @@ public class BubbleStackView extends FrameLayout
* once it collapses.
*/
@Nullable
private Bubble mBubbleToExpandAfterFlyoutCollapse = null;
private BubbleViewProvider mBubbleToExpandAfterFlyoutCollapse = null;
/** Layout change listener that moves the stack to the nearest valid position on rotation. */
private OnLayoutChangeListener mOrientationChangedListener;
@@ -571,6 +571,16 @@ public class BubbleStackView extends FrameLayout
mBubbleContainer.setActiveController(mStackAnimationController);
hideFlyoutImmediate();
if (!mPositioner.showingInTaskbar()) {
// Also, save the magnetized stack so we can dispatch touch events to it.
mMagnetizedObject = mStackAnimationController.getMagnetizedStack(
mMagneticTarget);
mMagnetizedObject.setMagnetListener(mStackMagnetListener);
} else {
// In taskbar, the stack isn't draggable so we shouldn't dispatch touch events.
mMagnetizedObject = null;
}
// Also, save the magnetized stack so we can dispatch touch events to it.
mMagnetizedObject = mStackAnimationController.getMagnetizedStack(mMagneticTarget);
mMagnetizedObject.setMagnetListener(mStackMagnetListener);
@@ -592,7 +602,9 @@ public class BubbleStackView extends FrameLayout
public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
float viewInitialY, float dx, float dy) {
// If we're expanding or collapsing, ignore all touch events.
if (mIsExpansionAnimating) {
if (mIsExpansionAnimating
// Also ignore events if we shouldn't be draggable.
|| (mPositioner.showingInTaskbar() && !mIsExpanded)) {
return;
}
@@ -602,7 +614,7 @@ public class BubbleStackView extends FrameLayout
// First, see if the magnetized object consumes the event - if so, we shouldn't move the
// bubble since it's stuck to the target.
if (!passEventToMagnetizedObject(ev)) {
if (mBubbleData.isExpanded()) {
if (mBubbleData.isExpanded() || mPositioner.showingInTaskbar()) {
mExpandedAnimationController.dragBubbleOut(
v, viewInitialX + dx, viewInitialY + dy);
} else {
@@ -743,7 +755,7 @@ public class BubbleStackView extends FrameLayout
ta.recycle();
final Runnable onBubbleAnimatedOut = () -> {
if (getBubbleCount() == 0) {
if (getBubbleCount() == 0 && !mBubbleData.isShowingOverflow()) {
mBubbleController.onAllBubblesAnimatedOut();
}
};
@@ -816,16 +828,16 @@ public class BubbleStackView extends FrameLayout
setFocusable(true);
mBubbleContainer.bringToFront();
mBubbleOverflow = new BubbleOverflow(getContext(), mPositioner);
mBubbleOverflow.initialize(mBubbleController);
mBubbleOverflow = mBubbleData.getOverflow();
mBubbleContainer.addView(mBubbleOverflow.getIconView(),
mBubbleContainer.getChildCount() /* index */,
new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
mPositioner.getBubbleSize()));
updateOverflow();
mBubbleOverflow.getIconView().setOnClickListener((View v) -> {
setSelectedBubble(mBubbleOverflow);
showManageMenu(false);
mBubbleData.setShowingOverflow(true);
mBubbleData.setSelectedBubble(mBubbleOverflow);
mBubbleData.setExpanded(true);
});
setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
@@ -996,11 +1008,12 @@ public class BubbleStackView extends FrameLayout
mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container).setOnClickListener(
view -> {
showManageMenu(false /* show */);
final Bubble bubble = mBubbleData.getSelectedBubble();
final BubbleViewProvider bubble = mBubbleData.getSelectedBubble();
if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
final Intent intent = bubble.getSettingsIntent(mContext);
// If it's in the stack it's a proper Bubble.
final Intent intent = ((Bubble) bubble).getSettingsIntent(mContext);
mBubbleData.setExpanded(false);
mContext.startActivityAsUser(intent, bubble.getUser());
mContext.startActivityAsUser(intent, ((Bubble) bubble).getUser());
logBubbleEvent(bubble,
FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
}
@@ -1443,10 +1456,9 @@ public class BubbleStackView extends FrameLayout
}
private void updateOverflowVisibility() {
if (mBubbleOverflow == null) {
return;
}
mBubbleOverflow.setVisible(mIsExpanded ? VISIBLE : GONE);
mBubbleOverflow.setVisible((mIsExpanded || mBubbleData.isShowingOverflow())
? VISIBLE
: GONE);
}
// via BubbleData.Listener
@@ -2128,7 +2140,7 @@ public class BubbleStackView extends FrameLayout
}
}
private void dismissBubbleIfExists(@Nullable Bubble bubble) {
private void dismissBubbleIfExists(@Nullable BubbleViewProvider bubble) {
if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
mBubbleData.dismissBubbleWithKey(bubble.getKey(), Bubbles.DISMISS_USER_GESTURE);
}
@@ -2337,7 +2349,7 @@ public class BubbleStackView extends FrameLayout
}
if (!mIsExpanded) {
if (getBubbleCount() > 0) {
if (getBubbleCount() > 0 || mBubbleData.isShowingOverflow()) {
mBubbleContainer.getChildAt(0).getBoundsOnScreen(outRect);
// Increase the touch target size of the bubble
outRect.top -= mBubbleTouchPadding;

View File

@@ -35,6 +35,7 @@ import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.Pair;
import android.view.WindowManager;
import androidx.test.filters.SmallTest;
@@ -135,8 +136,9 @@ public class BubbleDataTest extends ShellTestCase {
mBubbleB2 = new Bubble(mEntryB2, mSuppressionListener, mPendingIntentCanceledListener);
mBubbleB3 = new Bubble(mEntryB3, mSuppressionListener, mPendingIntentCanceledListener);
mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener, mPendingIntentCanceledListener);
mBubbleData = new BubbleData(getContext(), mBubbleLogger);
TestableBubblePositioner positioner = new TestableBubblePositioner(mContext,
mock(WindowManager.class));
mBubbleData = new BubbleData(getContext(), mBubbleLogger, positioner);
// Used by BubbleData to set lastAccessedTime
when(mTimeSource.currentTimeMillis()).thenReturn(1000L);

View File

@@ -96,6 +96,7 @@ import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleDataRepository;
import com.android.wm.shell.bubbles.BubbleEntry;
import com.android.wm.shell.bubbles.BubbleLogger;
import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubbleStackView;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.common.FloatingContentCoordinator;
@@ -251,9 +252,8 @@ public class BubblesTest extends SysuiTestCase {
mSysUiStateBubblesExpanded =
(sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0);
mBubbleData = new BubbleData(mContext, mBubbleLogger);
mPositioner = new TestableBubblePositioner(mContext, mWindowManager);
mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner);
TestableNotificationInterruptStateProviderImpl interruptionStateProvider =
new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
@@ -578,7 +578,7 @@ public class BubblesTest extends SysuiTestCase {
}
@Test
public void testRemoveLastExpandedCollapses() {
public void testRemoveLastExpanded_selectsOverflow() {
// Mark it as a bubble and add it explicitly
mEntryListener.onPendingEntryAdded(mRow.getEntry());
mEntryListener.onPendingEntryAdded(mRow2.getEntry());
@@ -618,10 +618,38 @@ public class BubblesTest extends SysuiTestCase {
stackView.getExpandedBubble().getKey()).getKey(),
Bubbles.DISMISS_USER_GESTURE);
// Make sure state changes and collapse happens
// Overflow should be selected
assertEquals(mBubbleData.getSelectedBubble().getKey(), BubbleOverflow.KEY);
verify(mBubbleExpandListener).onBubbleExpandChanged(true, BubbleOverflow.KEY);
assertTrue(mBubbleController.hasBubbles());
assertTrue(mSysUiStateBubblesExpanded);
}
@Test
public void testRemoveLastExpandedEmptyOverflow_collapses() {
// Mark it as a bubble and add it explicitly
mEntryListener.onPendingEntryAdded(mRow.getEntry());
mBubbleController.updateBubble(mBubbleEntry);
// Expand
BubbleStackView stackView = mBubbleController.getStackView();
mBubbleData.setExpanded(true);
assertTrue(mSysUiStateBubblesExpanded);
assertTrue(mBubbleController.isStackExpanded());
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
// Block the bubble so it won't be in the overflow
mBubbleController.removeBubble(
mBubbleData.getBubbleInStackWithKey(
stackView.getExpandedBubble().getKey()).getKey(),
Bubbles.DISMISS_BLOCKED);
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey());
// We should be collapsed
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey());
assertFalse(mBubbleController.hasBubbles());
assertFalse(mSysUiStateBubblesExpanded);
}

View File

@@ -89,6 +89,7 @@ import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleDataRepository;
import com.android.wm.shell.bubbles.BubbleEntry;
import com.android.wm.shell.bubbles.BubbleLogger;
import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubbleStackView;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.common.FloatingContentCoordinator;
@@ -220,9 +221,8 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
mZenModeConfig.suppressedVisualEffects = 0;
when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
mBubbleData = new BubbleData(mContext, mBubbleLogger);
mPositioner = new TestableBubblePositioner(mContext, mWindowManager);
mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner);
TestableNotificationInterruptStateProviderImpl interruptionStateProvider =
new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
@@ -509,7 +509,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
}
@Test
public void testRemoveLastExpandedCollapses() {
public void testRemoveLastExpanded_selectsOverflow() {
// Mark it as a bubble and add it explicitly
mEntryListener.onEntryAdded(mRow.getEntry());
mEntryListener.onEntryAdded(mRow2.getEntry());
@@ -547,11 +547,39 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
stackView.getExpandedBubble().getKey()).getKey(),
Bubbles.DISMISS_USER_GESTURE);
// Make sure state changes and collapse happens
// Overflow should be selected
assertEquals(mBubbleData.getSelectedBubble().getKey(), BubbleOverflow.KEY);
verify(mBubbleExpandListener).onBubbleExpandChanged(true, BubbleOverflow.KEY);
assertTrue(mBubbleController.hasBubbles());
}
@Test
public void testRemoveLastExpandedEmptyOverflow_collapses() {
// Mark it as a bubble and add it explicitly
mEntryListener.onEntryAdded(mRow.getEntry());
mBubbleController.updateBubble(mBubbleEntry);
// Expand
BubbleStackView stackView = mBubbleController.getStackView();
mBubbleData.setExpanded(true);
assertTrue(mBubbleController.isStackExpanded());
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
// Block the bubble so it won't be in the overflow
mBubbleController.removeBubble(
mBubbleData.getBubbleInStackWithKey(
stackView.getExpandedBubble().getKey()).getKey(),
Bubbles.DISMISS_BLOCKED);
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey());
// We should be collapsed
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey());
assertFalse(mBubbleController.hasBubbles());
}
@Test
public void testAutoExpand_fails_noFlag() {
assertFalse(mBubbleController.isStackExpanded());