diff options
| author | 2023-05-10 14:27:21 -0700 | |
|---|---|---|
| committer | 2023-05-11 16:20:40 -0700 | |
| commit | 2c4955bcf664e9ed5b9fa5b7fb201c923f9a9b68 (patch) | |
| tree | 4d7426aa4c1cb5d73ab41546c9949d00fa6ae2ec | |
| parent | 207bc54d297ec38e46e10aed6541cb49b9aa3684 (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
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) { |