Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2c4955bc authored by Mady Mellor's avatar Mady Mellor
Browse files

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
parent 207bc54d
Loading
Loading
Loading
Loading
+80 −48
Original line number Diff line number Diff line
@@ -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) {
        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 && newConfig != null) {
            if (newConfig.densityDpi != mDensityDpi
            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)) {
                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().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;
                if (fontScaleChanged) {
                    mStackView.updateFontScale();
                }
            if (newConfig.getLayoutDirection() != mLayoutDirection) {
                mLayoutDirection = newConfig.getLayoutDirection();
                mStackView.onLayoutDirectionChanged(mLayoutDirection);
                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) {
+44 −21
Original line number Diff line number Diff line
@@ -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
@@ -467,11 +473,6 @@ public class BubblesTest extends SysuiTestCase {
        verify(mShellInit, times(1)).addInitCallback(any(), any());
    }

    @Test
    public void instantiateController_registerConfigChangeListener() {
        verify(mShellController, times(1)).addConfigurationChangeListener(any());
    }

    @Test
    public void testAddBubble() {
        mBubbleController.updateBubble(mBubbleEntry);
@@ -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();
    }

+5 −0
Original line number Diff line number Diff line
@@ -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) {