Support switching navigation modes.

Switching navigation modes in Settings updates how bubbles are displayed.
Existing bubbles are re-added to the appropriate view.

Bug: 269670598
Test: In Settings go to Display and Navigation mode and switch between
Gesture and 3-button modes after creating bubbles.

Change-Id: I7562040ce907907325a00bac041b2eb3923d4848
This commit is contained in:
Liran Binyamin
2023-06-13 16:38:57 -04:00
parent 4b3863dfe7
commit 815722a7e5
12 changed files with 406 additions and 107 deletions

View File

@@ -422,6 +422,7 @@ public class Bubble implements BubbleViewProvider {
}
if (mBubbleBarExpandedView != null) {
mBubbleBarExpandedView.cleanUpExpandedState();
mBubbleBarExpandedView = null;
}
if (mIntent != null) {
mIntent.unregisterCancelListener(mIntentCancelListener);
@@ -549,10 +550,10 @@ public class Bubble implements BubbleViewProvider {
/**
* Set visibility of bubble in the expanded state.
*
* @param visibility {@code true} if the expanded bubble should be visible on the screen.
*
* Note that this contents visibility doesn't affect visibility at {@link android.view.View},
* <p>Note that this contents visibility doesn't affect visibility at {@link android.view.View},
* and setting {@code false} actually means rendering the expanded view in transparent.
*
* @param visibility {@code true} if the expanded bubble should be visible on the screen.
*/
@Override
public void setTaskViewVisibility(boolean visibility) {

View File

@@ -64,7 +64,6 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.service.notification.NotificationListenerService;
@@ -92,6 +91,7 @@ import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
import com.android.wm.shell.bubbles.properties.BubbleProperties;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.FloatingContentCoordinator;
@@ -143,16 +143,6 @@ public class BubbleController implements ConfigurationChangeListener,
private static final String SYSTEM_DIALOG_REASON_KEY = "reason";
private static final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav";
// TODO(b/256873975) Should use proper flag when available to shell/launcher
/**
* Whether bubbles are showing in the bubble bar from launcher. This is only available
* on large screens and {@link BubbleController#isShowingAsBubbleBar()} should be used
* to check all conditions that indicate if the bubble bar is in use.
*/
private static final boolean BUBBLE_BAR_ENABLED =
SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
/**
* Common interface to send updates to bubble views.
*/
@@ -195,6 +185,7 @@ public class BubbleController implements ConfigurationChangeListener,
private final ShellController mShellController;
private final ShellCommandHandler mShellCommandHandler;
private final IWindowManager mWmService;
private final BubbleProperties mBubbleProperties;
// Used to post to main UI thread
private final ShellExecutor mMainExecutor;
@@ -291,7 +282,8 @@ public class BubbleController implements ConfigurationChangeListener,
@ShellBackgroundThread ShellExecutor bgExecutor,
TaskViewTransitions taskViewTransitions,
SyncTransactionQueue syncQueue,
IWindowManager wmService) {
IWindowManager wmService,
BubbleProperties bubbleProperties) {
mContext = context;
mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
@@ -328,6 +320,7 @@ public class BubbleController implements ConfigurationChangeListener,
mDragAndDropController = dragAndDropController;
mSyncQueue = syncQueue;
mWmService = wmService;
mBubbleProperties = bubbleProperties;
shellInit.addInitCallback(this::onInit, this);
}
@@ -518,11 +511,14 @@ public class BubbleController implements ConfigurationChangeListener,
/**
* Sets a listener to be notified of bubble updates. This is used by launcher so that
* it may render bubbles in itself. Only one listener is supported.
*
* <p>If bubble bar is supported, bubble views will be updated to switch to bar mode.
*/
public void registerBubbleStateListener(Bubbles.BubbleStateListener listener) {
if (isShowingAsBubbleBar()) {
// Only set the listener if bubble bar is showing.
if (canShowAsBubbleBar() && listener != null) {
// Only set the listener if we can show the bubble bar.
mBubbleStateListener = listener;
setUpBubbleViewsForMode();
sendInitialListenerUpdate();
} else {
mBubbleStateListener = null;
@@ -531,9 +527,15 @@ public class BubbleController implements ConfigurationChangeListener,
/**
* Unregisters the {@link Bubbles.BubbleStateListener}.
*
* <p>If there's an existing listener, then we're switching back to stack mode and bubble views
* will be updated accordingly.
*/
public void unregisterBubbleStateListener() {
mBubbleStateListener = null;
if (mBubbleStateListener != null) {
mBubbleStateListener = null;
setUpBubbleViewsForMode();
}
}
/**
@@ -645,8 +647,12 @@ public class BubbleController implements ConfigurationChangeListener,
/** Whether bubbles are showing in the bubble bar. */
public boolean isShowingAsBubbleBar() {
// TODO(b/269670598): should also check that we're in gesture nav
return BUBBLE_BAR_ENABLED && mBubblePositioner.isLargeScreen();
return canShowAsBubbleBar() && mBubbleStateListener != null;
}
/** Whether the current configuration supports showing as bubble bar. */
private boolean canShowAsBubbleBar() {
return mBubbleProperties.isBubbleBarEnabled() && mBubblePositioner.isLargeScreen();
}
/** Whether this userId belongs to the current user. */
@@ -779,7 +785,7 @@ public class BubbleController implements ConfigurationChangeListener,
if (isShowingAsBubbleBar()) {
mWindowManager.addView(mLayerView, mWmLayoutParams);
mLayerView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
if (!windowInsets.equals(mWindowInsets)) {
if (!windowInsets.equals(mWindowInsets) && mLayerView != null) {
mWindowInsets = windowInsets;
mBubblePositioner.update();
mLayerView.onDisplaySizeChanged();
@@ -789,7 +795,7 @@ public class BubbleController implements ConfigurationChangeListener,
} else {
mWindowManager.addView(mStackView, mWmLayoutParams);
mStackView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
if (!windowInsets.equals(mWindowInsets)) {
if (!windowInsets.equals(mWindowInsets) && mStackView != null) {
mWindowInsets = windowInsets;
mBubblePositioner.update();
mStackView.onDisplaySizeChanged();
@@ -1066,9 +1072,11 @@ public class BubbleController implements ConfigurationChangeListener,
* Expands and selects the provided bubble as long as it already exists in the stack or the
* overflow.
*
* This is used by external callers (launcher).
* <p>This is used by external callers (launcher).
*/
public void expandStackAndSelectBubbleFromLauncher(String key) {
@VisibleForTesting
public void expandStackAndSelectBubbleFromLauncher(String key, boolean onLauncherHome) {
mBubblePositioner.setShowingInBubbleBar(onLauncherHome);
Bubble b = mBubbleData.getAnyBubbleWithkey(key);
if (b == null) {
return;
@@ -1286,6 +1294,50 @@ public class BubbleController implements ConfigurationChangeListener,
});
}
void setUpBubbleViewsForMode() {
mBubbleViewCallback = isShowingAsBubbleBar()
? mBubbleBarViewCallback
: mBubbleStackViewCallback;
// reset the overflow so that it can be re-added later if needed.
if (mStackView != null) {
mStackView.resetOverflowView();
mStackView.removeAllViews();
}
// cleanup existing bubble views so they can be recreated later if needed.
mBubbleData.getBubbles().forEach(Bubble::cleanupViews);
// remove the current bubble container from window manager, null it out, and create a new
// container based on the current mode.
removeFromWindowManagerMaybe();
mLayerView = null;
mStackView = null;
ensureBubbleViewsAndWindowCreated();
// inflate bubble views
BubbleViewInfoTask.Callback callback = null;
if (!isShowingAsBubbleBar()) {
callback = b -> {
if (mStackView != null) {
mStackView.addBubble(b);
mStackView.setSelectedBubble(b);
} else {
Log.w(TAG, "Tried to add a bubble to the stack but the stack is null");
}
};
}
for (int i = mBubbleData.getBubbles().size() - 1; i >= 0; i--) {
Bubble bubble = mBubbleData.getBubbles().get(i);
bubble.inflate(callback,
mContext,
this,
mStackView,
mLayerView,
mBubbleIconFactory,
false /* skipInflation */);
}
}
/**
* Adds or updates a bubble associated with the provided notification entry.
*
@@ -1746,7 +1798,7 @@ public class BubbleController implements ConfigurationChangeListener,
// Update the cached state for queries from SysUI
mImpl.mCachedState.update(update);
if (isShowingAsBubbleBar() && mBubbleStateListener != null) {
if (isShowingAsBubbleBar()) {
BubbleBarUpdate bubbleBarUpdate = update.toBubbleBarUpdate();
// Some updates aren't relevant to the bubble bar so check first.
if (bubbleBarUpdate.anythingChanged()) {
@@ -1868,10 +1920,17 @@ public class BubbleController implements ConfigurationChangeListener,
}
@VisibleForTesting
@Nullable
public BubbleStackView getStackView() {
return mStackView;
}
@VisibleForTesting
@Nullable
public BubbleBarLayerView getLayerView() {
return mLayerView;
}
/**
* Check if notification panel is in an expanded state.
* Makes a call to System UI process and delivers the result via {@code callback} on the
@@ -2010,22 +2069,18 @@ public class BubbleController implements ConfigurationChangeListener,
@Override
public void registerBubbleListener(IBubblesListener listener) {
mMainExecutor.execute(() -> {
mListener.register(listener);
});
mMainExecutor.execute(() -> mListener.register(listener));
}
@Override
public void unregisterBubbleListener(IBubblesListener listener) {
mMainExecutor.execute(() -> mListener.unregister());
mMainExecutor.execute(mListener::unregister);
}
@Override
public void showBubble(String key, boolean onLauncherHome) {
mMainExecutor.execute(() -> {
mBubblePositioner.setShowingInBubbleBar(onLauncherHome);
mController.expandStackAndSelectBubbleFromLauncher(key);
});
mMainExecutor.execute(
() -> mController.expandStackAndSelectBubbleFromLauncher(key, onLauncherHome));
}
@Override
@@ -2037,11 +2092,6 @@ public class BubbleController implements ConfigurationChangeListener,
public void collapseBubbles() {
mMainExecutor.execute(() -> mController.collapseStack());
}
@Override
public void onTaskbarStateChanged(int newState) {
// TODO (b/269670598)
}
}
private class BubblesImpl implements Bubbles {

View File

@@ -309,6 +309,7 @@ public class BubbleStackView extends FrameLayout
String bubblesOnScreen = BubbleDebugConfig.formatBubblesString(
getBubblesOnScreen(), getExpandedBubble());
pw.print(" stack visibility : "); pw.println(getVisibility());
pw.print(" bubbles on screen: "); pw.println(bubblesOnScreen);
pw.print(" gestureInProgress: "); pw.println(mIsGestureInProgress);
pw.print(" showingDismiss: "); pw.println(mDismissView.isShowing());
@@ -970,6 +971,8 @@ public class BubbleStackView extends FrameLayout
mBubbleContainer.bringToFront();
mBubbleOverflow = mBubbleData.getOverflow();
resetOverflowView();
mBubbleContainer.addView(mBubbleOverflow.getIconView(),
mBubbleContainer.getChildCount() /* index */,
new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
@@ -3413,6 +3416,19 @@ public class BubbleStackView extends FrameLayout
mDismissView.setPadding(/* left = */ 0, /* top = */ 0, /* right = */ 0, offset);
}
/**
* Removes the overflow view from the stack. This allows for re-adding it later to a new stack.
*/
void resetOverflowView() {
BadgedImageView overflowIcon = mBubbleOverflow.getIconView();
if (overflowIcon != null) {
PhysicsAnimationLayout parent = (PhysicsAnimationLayout) overflowIcon.getParent();
if (parent != null) {
parent.removeViewNoAnimation(overflowIcon);
}
}
}
/**
* Holds some commonly queried information about the stack.
*/

View File

@@ -60,9 +60,11 @@ public interface Bubbles {
DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE,
DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT,
DISMISS_OVERFLOW_MAX_REACHED, DISMISS_SHORTCUT_REMOVED, DISMISS_PACKAGE_REMOVED,
DISMISS_NO_BUBBLE_UP, DISMISS_RELOAD_FROM_DISK, DISMISS_USER_REMOVED})
DISMISS_NO_BUBBLE_UP, DISMISS_RELOAD_FROM_DISK, DISMISS_USER_REMOVED,
DISMISS_SWITCH_TO_STACK})
@Target({FIELD, LOCAL_VARIABLE, PARAMETER})
@interface DismissReason {}
@interface DismissReason {
}
int DISMISS_USER_GESTURE = 1;
int DISMISS_AGED = 2;
@@ -80,6 +82,7 @@ public interface Bubbles {
int DISMISS_NO_BUBBLE_UP = 14;
int DISMISS_RELOAD_FROM_DISK = 15;
int DISMISS_USER_REMOVED = 16;
int DISMISS_SWITCH_TO_STACK = 17;
/** Returns a binder that can be passed to an external process to manipulate Bubbles. */
default IBubbles createExternalInterface() {
@@ -120,8 +123,8 @@ public interface Bubbles {
/**
* This method has different behavior depending on:
* - if an app bubble exists
* - if an app bubble is expanded
* - if an app bubble exists
* - if an app bubble is expanded
*
* If no app bubble exists, this will add and expand a bubble with the provided intent. The
* intent must be explicit (i.e. include a package name or fully qualified component class name)
@@ -135,13 +138,13 @@ public interface Bubbles {
* the bubble or bubble stack.
*
* Some notes:
* - Only one app bubble is supported at a time, regardless of users. Multi-users support is
* tracked in b/273533235.
* - Calling this method with a different intent than the existing app bubble will do nothing
* - Only one app bubble is supported at a time, regardless of users. Multi-users support is
* tracked in b/273533235.
* - Calling this method with a different intent than the existing app bubble will do nothing
*
* @param intent the intent to display in the bubble expanded view.
* @param user the {@link UserHandle} of the user to start this activity for.
* @param icon the {@link Icon} to use for the bubble view.
* @param user the {@link UserHandle} of the user to start this activity for.
* @param icon the {@link Icon} to use for the bubble view.
*/
void showOrHideAppBubble(Intent intent, UserHandle user, @Nullable Icon icon);
@@ -172,13 +175,12 @@ public interface Bubbles {
* {@link Bubble#setSuppressNotification}. For the case of suppressed summaries, we also add
* {@link BubbleData#addSummaryToSuppress}.
*
* @param entry the notification of the BubbleEntry should be removed.
* @param children the list of child notification of the BubbleEntry from 1st param entry,
* this will be null if entry does have no children.
* @param entry the notification of the BubbleEntry should be removed.
* @param children the list of child notification of the BubbleEntry from 1st param entry,
* this will be null if entry does have no children.
* @param removeCallback the remove callback for SystemUI side to remove notification, the int
* number should be list position of children list and use -1 for
* removing the parent notification.
*
* @return true if we want to intercept the dismissal of the entry, else false.
*/
boolean handleDismissalInterception(BubbleEntry entry, @Nullable List<BubbleEntry> children,
@@ -200,9 +202,9 @@ public interface Bubbles {
/**
* Called when new notification entry updated.
*
* @param entry the {@link BubbleEntry} by the notification.
* @param entry the {@link BubbleEntry} by the notification.
* @param shouldBubbleUp {@code true} if this notification should bubble up.
* @param fromSystem {@code true} if this update is from NotificationManagerService.
* @param fromSystem {@code true} if this update is from NotificationManagerService.
*/
void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp, boolean fromSystem);
@@ -218,7 +220,7 @@ public interface Bubbles {
* filtering and sorting. This is used to dismiss or create bubbles based on changes in
* permissions on the notification channel or the global setting.
*
* @param rankingMap the updated ranking map from NotificationListenerService
* @param rankingMap the updated ranking map from NotificationListenerService
* @param entryDataByKey a map of ranking key to bubble entry and whether the entry should
* bubble up
*/
@@ -230,9 +232,9 @@ public interface Bubbles {
* Called when a notification channel is modified, in response to
* {@link NotificationListenerService#onNotificationChannelModified}.
*
* @param pkg the package the notification channel belongs to.
* @param user the user the notification channel belongs to.
* @param channel the channel being modified.
* @param pkg the package the notification channel belongs to.
* @param user the user the notification channel belongs to.
* @param channel the channel being modified.
* @param modificationType the type of modification that occurred to the channel.
*/
void onNotificationChannelModified(
@@ -300,7 +302,7 @@ public interface Bubbles {
* Called when the expansion state of the bubble stack changes.
*
* @param isExpanding whether it's expanding or collapsing
* @param key the notification key associated with bubble being expanded
* @param key the notification key associated with bubble being expanded
*/
void onBubbleExpandChanged(boolean isExpanding, String key);
}

View File

@@ -35,6 +35,4 @@ interface IBubbles {
oneway void collapseBubbles() = 5;
oneway void onTaskbarStateChanged(in int newState) = 6;
}

View File

@@ -327,6 +327,12 @@ public class PhysicsAnimationLayout extends FrameLayout {
addViewInternal(child, index, params, false /* isReorder */);
}
/** Removes the child view immediately. */
public void removeViewNoAnimation(View view) {
super.removeView(view);
view.setTag(R.id.physics_animator_tag, null);
}
@Override
public void removeView(View view) {
if (mController != null) {

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.bubbles.properties
/**
* An interface for exposing bubble properties via flags which can be controlled easily in tests.
*/
interface BubbleProperties {
/**
* Whether bubble bar is enabled.
*
* When this is `true`, depending on additional factors, such as screen size and taskbar state,
* bubbles will be displayed in the bubble bar instead of floating.
*
* When this is `false`, bubbles will be floating.
*/
val isBubbleBarEnabled: Boolean
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.bubbles.properties
import android.os.SystemProperties
/** Provides bubble properties in production. */
object ProdBubbleProperties : BubbleProperties {
// TODO(b/256873975) Should use proper flag when available to shell/launcher
override val isBubbleBarEnabled =
SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false)
}

View File

@@ -36,6 +36,7 @@ import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleDataRepository;
import com.android.wm.shell.bubbles.BubbleLogger;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.properties.ProdBubbleProperties;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
@@ -187,7 +188,7 @@ public abstract class WMShellModule {
statusBarService, windowManager, windowManagerShellWrapper, userManager,
launcherApps, logger, taskStackListener, organizer, positioner, displayController,
oneHandedOptional, dragAndDropController, mainExecutor, mainHandler, bgExecutor,
taskViewTransitions, syncQueue, wmService);
taskViewTransitions, syncQueue, wmService, ProdBubbleProperties.INSTANCE);
}
//
@@ -219,12 +220,12 @@ public abstract class WMShellModule {
desktopTasksController);
}
return new CaptionWindowDecorViewModel(
context,
mainHandler,
mainChoreographer,
taskOrganizer,
displayController,
syncQueue);
context,
mainHandler,
mainChoreographer,
taskOrganizer,
displayController,
syncQueue);
}
//
@@ -584,13 +585,13 @@ public abstract class WMShellModule {
animators.add(fullscreenAnimator);
return new UnfoldAnimationController(
shellInit,
transactionPool,
progressProvider.get(),
animators,
unfoldTransitionHandler,
mainExecutor
);
shellInit,
transactionPool,
progressProvider.get(),
animators,
unfoldTransitionHandler,
mainExecutor
);
}
@Provides

View File

@@ -137,11 +137,13 @@ import com.android.wm.shell.bubbles.BubbleStackView;
import com.android.wm.shell.bubbles.BubbleViewInfoTask;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.bubbles.StackEducationViewKt;
import com.android.wm.shell.bubbles.properties.BubbleProperties;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -299,6 +301,8 @@ public class BubblesTest extends SysuiTestCase {
private UserHandle mUser0;
private FakeBubbleProperties mBubbleProperties;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -360,6 +364,7 @@ public class BubblesTest extends SysuiTestCase {
mock(UserTracker.class)
);
when(mShellTaskOrganizer.getExecutor()).thenReturn(syncExecutor);
mBubbleProperties = new FakeBubbleProperties();
mBubbleController = new TestableBubbleController(
mContext,
mShellInit,
@@ -384,7 +389,8 @@ public class BubblesTest extends SysuiTestCase {
mock(Handler.class),
mTaskViewTransitions,
mock(SyncTransactionQueue.class),
mock(IWindowManager.class));
mock(IWindowManager.class),
mBubbleProperties);
mBubbleController.setExpandListener(mBubbleExpandListener);
spyOn(mBubbleController);
@@ -475,7 +481,7 @@ public class BubblesTest extends SysuiTestCase {
public void testAddBubble() {
mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
}
@Test
@@ -483,7 +489,7 @@ public class BubblesTest extends SysuiTestCase {
assertFalse(mBubbleController.hasBubbles());
mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
}
@Test
@@ -498,7 +504,7 @@ public class BubblesTest extends SysuiTestCase {
assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
verify(mNotifCallback, times(2)).invalidateNotifications(anyString());
assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
}
@Test
@@ -561,7 +567,7 @@ public class BubblesTest extends SysuiTestCase {
assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
assertNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey()));
assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
}
@Test
@@ -580,7 +586,7 @@ public class BubblesTest extends SysuiTestCase {
mBubbleData.setExpanded(true);
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
// Make sure the notif is suppressed
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -589,7 +595,7 @@ public class BubblesTest extends SysuiTestCase {
mBubbleController.collapseStack();
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
assertStackCollapsed();
assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
}
@Test
@@ -611,7 +617,7 @@ public class BubblesTest extends SysuiTestCase {
assertStackExpanded();
verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
true, mRow2.getKey());
assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
// Last added is the one that is expanded
assertEquals(mRow2.getKey(), mBubbleData.getSelectedBubble().getKey());
@@ -636,7 +642,7 @@ public class BubblesTest extends SysuiTestCase {
// Collapse
mBubbleController.collapseStack();
assertStackCollapsed();
assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
}
@Test
@@ -656,7 +662,7 @@ public class BubblesTest extends SysuiTestCase {
mBubbleData.setExpanded(true);
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
// Notif is suppressed after expansion
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -681,7 +687,7 @@ public class BubblesTest extends SysuiTestCase {
mBubbleData.setExpanded(true);
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
// Notif is suppressed after expansion
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -710,7 +716,7 @@ public class BubblesTest extends SysuiTestCase {
BubbleStackView stackView = mBubbleController.getStackView();
mBubbleData.setExpanded(true);
assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getKey());
@@ -741,7 +747,7 @@ public class BubblesTest extends SysuiTestCase {
// We should be collapsed
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
assertFalse(mBubbleController.hasBubbles());
assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
}
@Test
@@ -754,7 +760,7 @@ public class BubblesTest extends SysuiTestCase {
BubbleStackView stackView = mBubbleController.getStackView();
mBubbleData.setExpanded(true);
assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
@@ -769,7 +775,7 @@ public class BubblesTest extends SysuiTestCase {
// We should be collapsed
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
assertFalse(mBubbleController.hasBubbles());
assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
}
@@ -787,7 +793,7 @@ public class BubblesTest extends SysuiTestCase {
verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */,
mRow.getKey());
assertStackCollapsed();
assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
}
@Test
@@ -803,7 +809,7 @@ public class BubblesTest extends SysuiTestCase {
verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */,
mRow.getKey());
assertStackExpanded();
assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
}
@Test
@@ -820,7 +826,7 @@ public class BubblesTest extends SysuiTestCase {
// Dot + flyout is hidden because notif is suppressed
assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout());
assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
}
@Test
@@ -842,7 +848,7 @@ public class BubblesTest extends SysuiTestCase {
// Dot + flyout is hidden because notif is suppressed
assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout());
assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
}
@Test
@@ -1247,11 +1253,11 @@ public class BubblesTest extends SysuiTestCase {
BubbleStackView stackView = mBubbleController.getStackView();
mBubbleData.setExpanded(true);
assertStackExpanded();
assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
// Show the menu
stackView.showManageMenu(true);
assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
assertSysuiStates(true /* stackExpanded */, true /* manageMenuExpanded */);
assertTrue(stackView.isManageMenuSettingsVisible());
assertTrue(stackView.isManageMenuDontBubbleVisible());
}
@@ -1265,11 +1271,11 @@ public class BubblesTest extends SysuiTestCase {
BubbleStackView stackView = mBubbleController.getStackView();
mBubbleData.setExpanded(true);
assertStackExpanded();
assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
// Show the menu
stackView.showManageMenu(true);
assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
assertSysuiStates(true /* stackExpanded */, true /* manageMenuExpanded */);
assertFalse(stackView.isManageMenuSettingsVisible());
assertFalse(stackView.isManageMenuDontBubbleVisible());
}
@@ -1283,15 +1289,15 @@ public class BubblesTest extends SysuiTestCase {
BubbleStackView stackView = mBubbleController.getStackView();
mBubbleData.setExpanded(true);
assertStackExpanded();
assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
// Show the menu
stackView.showManageMenu(true);
assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
assertSysuiStates(true /* stackExpanded */, true /* manageMenuExpanded */);
// Hide the menu
stackView.showManageMenu(false);
assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
}
@Test
@@ -1303,16 +1309,16 @@ public class BubblesTest extends SysuiTestCase {
BubbleStackView stackView = mBubbleController.getStackView();
mBubbleData.setExpanded(true);
assertStackExpanded();
assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
// Show the menu
stackView.showManageMenu(true);
assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
assertSysuiStates(true /* stackExpanded */, true /* manageMenuExpanded */);
// Collapse the stack
mBubbleData.setExpanded(false);
assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
}
@Test
@@ -1574,7 +1580,7 @@ public class BubblesTest extends SysuiTestCase {
NotificationListenerService.RankingMap rankingMap =
mock(NotificationListenerService.RankingMap.class);
when(rankingMap.getOrderedKeys()).thenReturn(new String[] { mBubbleEntry.getKey() });
when(rankingMap.getOrderedKeys()).thenReturn(new String[]{mBubbleEntry.getKey()});
mBubbleController.onRankingUpdated(rankingMap, entryDataByKey);
// Should no longer be in the stack
@@ -1882,7 +1888,108 @@ public class BubblesTest extends SysuiTestCase {
);
}
/** Creates a bubble using the userId and package. */
@Test
public void registerBubbleBarListener_barDisabled_largeScreen_shouldBeIgnored() {
mBubbleProperties.mIsBubbleBarEnabled = false;
mPositioner.setIsLargeScreen(true);
mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
assertStackMode();
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
mBubbleController.registerBubbleStateListener(bubbleStateListener);
assertStackMode();
assertThat(mBubbleController.getStackView().getBubbleCount()).isEqualTo(1);
}
@Test
public void registerBubbleBarListener_barEnabled_smallScreen_shouldBeIgnored() {
mBubbleProperties.mIsBubbleBarEnabled = true;
mPositioner.setIsLargeScreen(false);
mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
assertStackMode();
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
mBubbleController.registerBubbleStateListener(bubbleStateListener);
assertStackMode();
assertThat(mBubbleController.getStackView().getBubbleCount()).isEqualTo(1);
}
@Test
public void registerBubbleBarListener_switchToBarAndBackToStack() {
mBubbleProperties.mIsBubbleBarEnabled = true;
mPositioner.setIsLargeScreen(true);
mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
assertStackMode();
assertThat(mBubbleData.getBubbles()).hasSize(1);
assertBubbleIsInflatedForStack(mBubbleData.getBubbles().get(0));
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
mBubbleController.registerBubbleStateListener(bubbleStateListener);
assertBarMode();
assertThat(mBubbleData.getBubbles()).hasSize(1);
assertBubbleIsInflatedForBar(mBubbleData.getBubbles().get(0));
mBubbleController.unregisterBubbleStateListener();
assertStackMode();
assertThat(mBubbleData.getBubbles()).hasSize(1);
assertBubbleIsInflatedForStack(mBubbleData.getBubbles().get(0));
}
@Test
public void switchBetweenBarAndStack_noBubbles_shouldBeIgnored() {
mBubbleProperties.mIsBubbleBarEnabled = false;
mPositioner.setIsLargeScreen(true);
assertFalse(mBubbleController.hasBubbles());
assertNoBubbleContainerViews();
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
mBubbleController.registerBubbleStateListener(bubbleStateListener);
assertNoBubbleContainerViews();
mBubbleController.unregisterBubbleStateListener();
assertNoBubbleContainerViews();
}
@Test
public void bubbleBarBubbleExpandedAndCollapsed() {
mBubbleProperties.mIsBubbleBarEnabled = true;
mPositioner.setIsLargeScreen(true);
mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
mBubbleController.registerBubbleStateListener(bubbleStateListener);
mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), true);
assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
mBubbleController.collapseStack();
assertThat(mBubbleController.getLayerView().isExpanded()).isFalse();
}
/** Creates a bubble using the userId and package. */
private Bubble createBubble(int userId, String pkg) {
final UserHandle userHandle = new UserHandle(userId);
NotificationEntry workEntry = new NotificationEntryBuilder()
@@ -1998,6 +2105,38 @@ public class BubblesTest extends SysuiTestCase {
assertFalse(mBubbleController.getImplCachedState().isStackExpanded());
}
/** Asserts that both the bubble stack and bar views don't exist. */
private void assertNoBubbleContainerViews() {
assertThat(mBubbleController.getStackView()).isNull();
assertThat(mBubbleController.getLayerView()).isNull();
}
/** Asserts that the stack is created and the bar is null. */
private void assertStackMode() {
assertThat(mBubbleController.getStackView()).isNotNull();
assertThat(mBubbleController.getLayerView()).isNull();
}
/** Asserts that the given bubble has the stack expanded view inflated. */
private void assertBubbleIsInflatedForStack(Bubble b) {
assertThat(b.getIconView()).isNotNull();
assertThat(b.getExpandedView()).isNotNull();
assertThat(b.getBubbleBarExpandedView()).isNull();
}
/** Asserts that the bar is created and the stack is null. */
private void assertBarMode() {
assertThat(mBubbleController.getStackView()).isNull();
assertThat(mBubbleController.getLayerView()).isNotNull();
}
/** Asserts that the given bubble has the bar expanded view inflated. */
private void assertBubbleIsInflatedForBar(Bubble b) {
assertThat(b.getIconView()).isNull();
assertThat(b.getExpandedView()).isNull();
assertThat(b.getBubbleBarExpandedView()).isNotNull();
}
/**
* Asserts that a bubble notification is suppressed from the shade and also validates the cached
* state is updated.
@@ -2027,4 +2166,19 @@ public class BubblesTest extends SysuiTestCase {
assertThat(mSysUiStateBubblesExpanded).isEqualTo(stackExpanded);
assertThat(mSysUiStateBubblesManageMenuExpanded).isEqualTo(manageMenuExpanded);
}
private static class FakeBubbleStateListener implements Bubbles.BubbleStateListener {
@Override
public void onBubbleStateChange(BubbleBarUpdate update) {
}
}
private static class FakeBubbleProperties implements BubbleProperties {
boolean mIsBubbleBarEnabled = false;
@Override
public boolean isBubbleBarEnabled() {
return mIsBubbleBarEnabled;
}
}
}

View File

@@ -31,6 +31,7 @@ import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleDataRepository;
import com.android.wm.shell.bubbles.BubbleLogger;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.properties.BubbleProperties;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
@@ -74,13 +75,14 @@ public class TestableBubbleController extends BubbleController {
Handler shellMainHandler,
TaskViewTransitions taskViewTransitions,
SyncTransactionQueue syncQueue,
IWindowManager wmService) {
IWindowManager wmService,
BubbleProperties bubbleProperties) {
super(context, shellInit, shellCommandHandler, shellController, data, Runnable::run,
floatingContentCoordinator, dataRepository, statusBarService, windowManager,
windowManagerShellWrapper, userManager, launcherApps, bubbleLogger,
taskStackListener, shellTaskOrganizer, positioner, displayController,
oneHandedOptional, dragAndDropController, shellMainExecutor, shellMainHandler,
new SyncExecutor(), taskViewTransitions, syncQueue, wmService);
new SyncExecutor(), taskViewTransitions, syncQueue, wmService, bubbleProperties);
setInflateSynchronously(true);
onInit();
}

View File

@@ -27,6 +27,7 @@ import com.android.wm.shell.bubbles.BubblePositioner;
public class TestableBubblePositioner extends BubblePositioner {
private int mMaxBubbles;
private boolean mIsLargeScreen = false;
public TestableBubblePositioner(Context context,
WindowManager windowManager) {
@@ -46,4 +47,13 @@ public class TestableBubblePositioner extends BubblePositioner {
public int getMaxBubbles() {
return mMaxBubbles;
}
public void setIsLargeScreen(boolean isLargeScreen) {
mIsLargeScreen = isLargeScreen;
}
@Override
public boolean isLargeScreen() {
return mIsLargeScreen;
}
}