summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Mady Mellor <madym@google.com> 2023-05-10 14:27:21 -0700
committer Mady Mellor <madym@google.com> 2023-05-11 16:20:40 -0700
commit2c4955bcf664e9ed5b9fa5b7fb201c923f9a9b68 (patch)
tree4d7426aa4c1cb5d73ab41546c9949d00fa6ae2ec
parent207bc54d297ec38e46e10aed6541cb49b9aa3684 (diff)
Use ComponentCallbacks instead of ConfigurationChangeListener
ConfigurationChangeListener can be out of sync with theme so instead use ComponentCallbacks listener. For this to work, it needs to be registered with a window context. Updates tests to use the window context bubble controller creates. Adds a new test to ensure component callback is added / removed appropriately. Updates SysuiTestableContext to create a window context, this ensures that registered recievers for bubbles get tracked. Test: atest BubblesTest Test: manual - have a bubble, expand it, change the theme, check that the manage button & contents is in correct theme along with the overflow button & contents and flyout - repeat above with font size, display size, density, and RTL and verify bubble UI elements update for those changes Bug: 281748524 Change-Id: Ibdcb680e64bbe81af72ec04318f091941da5fe89
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java128
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java65
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java5
3 files changed, 129 insertions, 69 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 3eb9fa2eef6b..698681029595 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -17,12 +17,18 @@
package com.android.wm.shell.bubbles;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS;
+import static android.content.pm.ActivityInfo.CONFIG_DENSITY;
+import static android.content.pm.ActivityInfo.CONFIG_FONT_SCALE;
+import static android.content.pm.ActivityInfo.CONFIG_LAYOUT_DIRECTION;
+import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
@@ -47,6 +53,7 @@ import android.app.Notification;
import android.app.NotificationChannel;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
+import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -74,7 +81,6 @@ import android.util.Pair;
import android.util.SparseArray;
import android.view.IWindowManager;
import android.view.SurfaceControl;
-import android.view.View;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
import android.view.WindowInsets;
@@ -102,6 +108,7 @@ import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.annotations.ExternalMainThread;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
@@ -109,7 +116,6 @@ import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
-import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -135,7 +141,7 @@ import java.util.function.IntConsumer;
*
* The controller manages addition, removal, and visible state of bubbles on screen.
*/
-public class BubbleController implements ConfigurationChangeListener,
+public class BubbleController implements ComponentCallbacks2,
RemoteCallable<BubbleController> {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
@@ -153,7 +159,6 @@ public class BubbleController implements ConfigurationChangeListener,
private static final boolean BUBBLE_BAR_ENABLED =
SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
-
/**
* Common interface to send updates to bubble views.
*/
@@ -237,17 +242,17 @@ public class BubbleController implements ConfigurationChangeListener,
/** Whether or not the BubbleStackView has been added to the WindowManager. */
private boolean mAddedToWindowManager = false;
- /** Saved screen density, used to detect display size changes in {@link #onConfigChanged}. */
- private int mDensityDpi = Configuration.DENSITY_DPI_UNDEFINED;
-
- /** Saved screen bounds, used to detect screen size changes in {@link #onConfigChanged}. **/
- private Rect mScreenBounds = new Rect();
-
- /** Saved font scale, used to detect font size changes in {@link #onConfigChanged}. */
- private float mFontScale = 0;
+ /**
+ * Saved configuration, used to detect changes in
+ * {@link #onConfigurationChanged(Configuration)}
+ */
+ private final Configuration mLastConfiguration = new Configuration();
- /** Saved direction, used to detect layout direction changes @link #onConfigChanged}. */
- private int mLayoutDirection = View.LAYOUT_DIRECTION_UNDEFINED;
+ /**
+ * Saved screen bounds, used to detect screen size changes in
+ * {@link #onConfigurationChanged(Configuration)}.
+ */
+ private final Rect mScreenBounds = new Rect();
/** Saved insets, used to detect WindowInset changes. */
private WindowInsets mWindowInsets;
@@ -293,7 +298,8 @@ public class BubbleController implements ConfigurationChangeListener,
TaskViewTransitions taskViewTransitions,
SyncTransactionQueue syncQueue,
IWindowManager wmService) {
- mContext = context;
+ mContext = context.createWindowContext(TYPE_APPLICATION_OVERLAY, null);
+ mLastConfiguration.setTo(mContext.getResources().getConfiguration());
mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
mLauncherApps = launcherApps;
@@ -317,11 +323,11 @@ public class BubbleController implements ConfigurationChangeListener,
mBubblePositioner = positioner;
mBubbleData = data;
mSavedUserBubbleData = new SparseArray<>();
- mBubbleIconFactory = new BubbleIconFactory(context,
- context.getResources().getDimensionPixelSize(R.dimen.bubble_size),
- context.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
- context.getResources().getColor(R.color.important_conversation),
- context.getResources().getDimensionPixelSize(
+ mBubbleIconFactory = new BubbleIconFactory(mContext,
+ mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size),
+ mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
+ mContext.getResources().getColor(R.color.important_conversation),
+ mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.importance_ring_stroke_width));
mDisplayController = displayController;
mTaskViewTransitions = taskViewTransitions;
@@ -482,7 +488,6 @@ public class BubbleController implements ConfigurationChangeListener,
}
mCurrentProfiles = userProfiles;
- mShellController.addConfigurationChangeListener(this);
mShellController.addExternalInterface(KEY_EXTRA_SHELL_BUBBLES,
this::createExternalInterface, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
@@ -774,6 +779,7 @@ public class BubbleController implements ConfigurationChangeListener,
try {
mAddedToWindowManager = true;
registerBroadcastReceiver();
+ mContext.registerComponentCallbacks(this);
mBubbleData.getOverflow().initialize(this);
// (TODO: b/273314541) some duplication in the inset listener
if (isShowingAsBubbleBar()) {
@@ -831,6 +837,7 @@ public class BubbleController implements ConfigurationChangeListener,
// Put on background for this binder call, was causing jank
mBackgroundExecutor.execute(() -> {
try {
+ mContext.unregisterComponentCallbacks(this);
mContext.unregisterReceiver(mBroadcastReceiver);
} catch (IllegalArgumentException e) {
// Not sure if this happens in production, but was happening in tests
@@ -930,8 +937,7 @@ public class BubbleController implements ConfigurationChangeListener,
mSavedUserBubbleData.remove(userId);
}
- @Override
- public void onThemeChanged() {
+ private void onThemeChanged() {
if (mStackView != null) {
mStackView.onThemeChanged();
}
@@ -963,34 +969,60 @@ public class BubbleController implements ConfigurationChangeListener,
}
}
+ // Note: Component callbacks are always called on the main thread of the process
+ @ExternalMainThread
@Override
public void onConfigurationChanged(Configuration newConfig) {
- if (mBubblePositioner != null) {
- mBubblePositioner.update();
- }
- if (mStackView != null && newConfig != null) {
- if (newConfig.densityDpi != mDensityDpi
- || !newConfig.windowConfiguration.getBounds().equals(mScreenBounds)) {
- mDensityDpi = newConfig.densityDpi;
- mScreenBounds.set(newConfig.windowConfiguration.getBounds());
- mBubbleData.onMaxBubblesChanged();
- mBubbleIconFactory = new BubbleIconFactory(mContext,
- mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size),
- mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
- mContext.getResources().getColor(R.color.important_conversation),
- mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.importance_ring_stroke_width));
- mStackView.onDisplaySizeChanged();
- }
- if (newConfig.fontScale != mFontScale) {
- mFontScale = newConfig.fontScale;
- mStackView.updateFontScale();
- }
- if (newConfig.getLayoutDirection() != mLayoutDirection) {
- mLayoutDirection = newConfig.getLayoutDirection();
- mStackView.onLayoutDirectionChanged(mLayoutDirection);
+ mMainExecutor.execute(() -> {
+ final int diff = newConfig.diff(mLastConfiguration);
+ final boolean themeChanged = (diff & CONFIG_ASSETS_PATHS) != 0
+ || (diff & CONFIG_UI_MODE) != 0;
+ if (themeChanged) {
+ onThemeChanged();
}
- }
+ if (mBubblePositioner != null) {
+ mBubblePositioner.update();
+ }
+ if (mStackView != null) {
+ final boolean densityChanged = (diff & CONFIG_DENSITY) != 0;
+ final boolean fontScaleChanged = (diff & CONFIG_FONT_SCALE) != 0;
+ final boolean layoutDirectionChanged = (diff & CONFIG_LAYOUT_DIRECTION) != 0;
+ if (densityChanged
+ || !newConfig.windowConfiguration.getBounds().equals(mScreenBounds)) {
+ mScreenBounds.set(newConfig.windowConfiguration.getBounds());
+ mBubbleData.onMaxBubblesChanged();
+ mBubbleIconFactory = new BubbleIconFactory(mContext,
+ mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size),
+ mContext.getResources().getDimensionPixelSize(
+ R.dimen.bubble_badge_size),
+ mContext.getResources().getColor(R.color.important_conversation),
+ mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.importance_ring_stroke_width));
+ mStackView.onDisplaySizeChanged();
+ }
+ if (fontScaleChanged) {
+ mStackView.updateFontScale();
+ }
+ if (layoutDirectionChanged) {
+ mStackView.onLayoutDirectionChanged(newConfig.getLayoutDirection());
+ }
+ }
+ mLastConfiguration.setTo(newConfig);
+ });
+ }
+
+ // Note: Component callbacks are always called on the main thread of the process
+ @ExternalMainThread
+ @Override
+ public void onTrimMemory(int level) {
+ // Do nothing
+ }
+
+ // Note: Component callbacks are always called on the main thread of the process
+ @ExternalMainThread
+ @Override
+ public void onLowMemory() {
+ // Do nothing
}
private void onNotificationPanelExpandedChanged(boolean expanded) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 47a86b1fca5c..f0683a4628b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -300,6 +300,10 @@ public class BubblesTest extends SysuiTestCase {
private UserHandle mUser0;
+ // The window context being used by the controller, use this to verify
+ // any actions on the context.
+ private Context mBubbleControllerContext;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -436,6 +440,8 @@ public class BubblesTest extends SysuiTestCase {
// Get a reference to KeyguardStateController.Callback
verify(mKeyguardStateController, atLeastOnce())
.addCallback(mKeyguardStateControllerCallbackCaptor.capture());
+
+ mBubbleControllerContext = mBubbleController.getContext();
}
@After
@@ -468,11 +474,6 @@ public class BubblesTest extends SysuiTestCase {
}
@Test
- public void instantiateController_registerConfigChangeListener() {
- verify(mShellController, times(1)).addConfigurationChangeListener(any());
- }
-
- @Test
public void testAddBubble() {
mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
@@ -1385,13 +1386,28 @@ public class BubblesTest extends SysuiTestCase {
assertStackCollapsed();
}
+ @Test
+ public void testRegisterUnregisterComponentCallbacks() {
+ spyOn(mBubbleControllerContext);
+ mBubbleController.updateBubble(mBubbleEntry);
+ verify(mBubbleControllerContext).registerComponentCallbacks(eq(mBubbleController));
+
+ mBubbleData.dismissBubbleWithKey(mBubbleEntry.getKey(), REASON_APP_CANCEL);
+ // TODO: not certain why this isn't called normally when tests are run, perhaps because
+ // it's after an animation in BSV. This calls BubbleController#removeFromWindowManagerMaybe
+ mBubbleController.onAllBubblesAnimatedOut();
+
+ verify(mBubbleControllerContext).unregisterComponentCallbacks(eq(mBubbleController));
+ }
@Test
public void testRegisterUnregisterBroadcastListener() {
- spyOn(mContext);
+ spyOn(mBubbleControllerContext);
mBubbleController.updateBubble(mBubbleEntry);
- verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
+ verify(mBubbleControllerContext).registerReceiver(
+ mBroadcastReceiverArgumentCaptor.capture(),
+ mFilterArgumentCaptor.capture(),
+ eq(Context.RECEIVER_EXPORTED));
assertThat(mFilterArgumentCaptor.getValue()
.hasAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)).isTrue();
assertThat(mFilterArgumentCaptor.getValue()
@@ -1402,47 +1418,54 @@ public class BubblesTest extends SysuiTestCase {
// it's after an animation in BSV. This calls BubbleController#removeFromWindowManagerMaybe
mBubbleController.onAllBubblesAnimatedOut();
- verify(mContext).unregisterReceiver(eq(mBroadcastReceiverArgumentCaptor.getValue()));
+ verify(mBubbleControllerContext).unregisterReceiver(
+ eq(mBroadcastReceiverArgumentCaptor.getValue()));
}
@Test
public void testBroadcastReceiverCloseDialogs_notGestureNav() {
- spyOn(mContext);
+ spyOn(mBubbleControllerContext);
mBubbleController.updateBubble(mBubbleEntry);
mBubbleData.setExpanded(true);
- verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
+ verify(mBubbleControllerContext).registerReceiver(
+ mBroadcastReceiverArgumentCaptor.capture(),
+ mFilterArgumentCaptor.capture(),
+ eq(Context.RECEIVER_EXPORTED));
Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mBubbleControllerContext, i);
assertStackExpanded();
}
@Test
public void testBroadcastReceiverCloseDialogs_reasonGestureNav() {
- spyOn(mContext);
+ spyOn(mBubbleControllerContext);
mBubbleController.updateBubble(mBubbleEntry);
mBubbleData.setExpanded(true);
- verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
+ verify(mBubbleControllerContext).registerReceiver(
+ mBroadcastReceiverArgumentCaptor.capture(),
+ mFilterArgumentCaptor.capture(),
+ eq(Context.RECEIVER_EXPORTED));
Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
i.putExtra("reason", "gestureNav");
- mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mBubbleControllerContext, i);
assertStackCollapsed();
}
@Test
public void testBroadcastReceiver_screenOff() {
- spyOn(mContext);
+ spyOn(mBubbleControllerContext);
mBubbleController.updateBubble(mBubbleEntry);
mBubbleData.setExpanded(true);
- verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
+ verify(mBubbleControllerContext).registerReceiver(
+ mBroadcastReceiverArgumentCaptor.capture(),
+ mFilterArgumentCaptor.capture(),
+ eq(Context.RECEIVER_EXPORTED));
Intent i = new Intent(Intent.ACTION_SCREEN_OFF);
- mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mBubbleControllerContext, i);
assertStackCollapsed();
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
index 5ff57aad9f5d..4b6dd3ef9d62 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
@@ -19,6 +19,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.display.DisplayManager;
+import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
import android.testing.LeakCheck;
@@ -62,6 +63,10 @@ public class SysuiTestableContext extends TestableContext {
return (SysuiTestableContext) createDisplayContext(display);
}
+ public SysuiTestableContext createWindowContext(int type, Bundle bundle) {
+ return new SysuiTestableContext(getBaseContext().createWindowContext(type, bundle));
+ }
+
public void cleanUpReceivers(String testName) {
Set<BroadcastReceiver> copy;
synchronized (mRegisteredReceivers) {