From 1fd6090eb7f1dbcb9f8beffd398df9a71c34a0a7 Mon Sep 17 00:00:00 2001 From: Ajinkya Chalke Date: Fri, 9 May 2025 12:50:48 +0000 Subject: [PATCH] Recreate DP for CD when displayInfo changes - Only IDP is updated when displayInfo changes but DPs of CD don't. As a result taskbars on CD end up reusing the old config DPs. For example, when user changes display size, displayInfo updates but taskbars on CD continue using old DP resources. - So, update DP for CD when DisplayInfo changes. - Also added using supplied context with dpToPx for calculating radius for taskbar roundedness at the ends. Fix: 414933860 Test: m Flag: com.android.window.flags.enable_taskbar_connected_displays Change-Id: I7d8ed3c5ba69b755a58d3462a5da9ed7fd404198 --- .../launcher3/taskbar/TaskbarManagerImpl.java | 127 ++++++++++-------- .../android/launcher3/taskbar/Utilities.java | 3 +- .../taskbar/TaskbarStashControllerTest.kt | 27 +++- .../taskbar/rules/TaskbarSandboxComponent.kt | 44 +++++- .../taskbar/rules/TaskbarUnitTestRule.kt | 9 +- .../launcher3/util/DisplayController.java | 9 +- 6 files changed, 157 insertions(+), 62 deletions(-) diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManagerImpl.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManagerImpl.java index 202e9e8996..92062600d1 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManagerImpl.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManagerImpl.java @@ -121,6 +121,7 @@ import java.util.Set; import java.util.StringJoiner; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; +import java.util.function.IntConsumer; /** * Class to manage taskbar lifecycle @@ -215,7 +216,7 @@ public class TaskbarManagerImpl implements DisplayDecorationListener { // Mode upon getting transition callback from shell side. So, we make sure that if taskbar is // already in recreate process due to transition callback, don't recreate for // DisplayInfoChangeListener. - private boolean mShouldIgnoreNextDesktopModeChangeFromDisplayController = false; + private boolean mShouldIgnoreNextDesktopModeChangeFromDisplayControllerForPrimary = false; private class RecreationListener implements DisplayController.DisplayInfoChangeListener { @Override @@ -235,27 +236,35 @@ public class TaskbarManagerImpl implements DisplayDecorationListener { debugTaskbarManager("onDisplayInfoChanged: Taskbar pinning changed", displayId); } + // Use a helper to update DP (only for secondary displays) and then recreate taskbar. + IntConsumer updateExternalDpAndRecreateTaskbar = displayIdToUpdate -> { + // Don't update DP for primary display as IDP already takes care of this. + createExternalDeviceProfile(displayIdToUpdate); + recreateTaskbarForDisplay(displayIdToUpdate, /* duration= */ 0); + }; + if ((flags & (CHANGE_DENSITY | CHANGE_NAVIGATION_MODE | CHANGE_DESKTOP_MODE | CHANGE_TASKBAR_PINNING | CHANGE_SHOW_LOCKED_TASKBAR)) != 0) { - TaskbarActivityContext taskbarActivityContext = getCurrentActivityContext(); + TaskbarActivityContext taskbarActivityContext = getTaskbarForDisplay(displayId); if ((flags & CHANGE_SHOW_LOCKED_TASKBAR) != 0) { debugTaskbarManager("onDisplayInfoChanged: show locked taskbar changed!", displayId); - recreateTaskbars(); + updateExternalDpAndRecreateTaskbar.accept(displayId); } else if ((flags & CHANGE_DESKTOP_MODE) != 0) { - if (mShouldIgnoreNextDesktopModeChangeFromDisplayController) { - mShouldIgnoreNextDesktopModeChangeFromDisplayController = false; + if (displayId == mPrimaryDisplayId + && mShouldIgnoreNextDesktopModeChangeFromDisplayControllerForPrimary) { + mShouldIgnoreNextDesktopModeChangeFromDisplayControllerForPrimary = false; return; } // Only Handles Special Exit Cases for Desktop Mode Taskbar Recreation. if (((flags & CHANGE_TASKBAR_PINNING) != 0) || (taskbarActivityContext != null && !taskbarActivityContext.showLockedTaskbarOnHome() && !taskbarActivityContext.showDesktopTaskbarForFreeformDisplay())) { - recreateTaskbars(); + updateExternalDpAndRecreateTaskbar.accept(displayId); } } else { - recreateTaskbars(); + updateExternalDpAndRecreateTaskbar.accept(displayId); } } } @@ -339,44 +348,31 @@ public class TaskbarManagerImpl implements DisplayDecorationListener { LatencyTracker.getInstance(mBaseContext).onActionStart( LatencyTracker.ACTION_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE); } - for (Entry entry : mTaskbars.entrySet()) { - int displayId = entry.getKey(); - if (isExternalDisplay(displayId)) { - continue; - } - TaskbarActivityContext taskbarActivityContext = entry.getValue(); - if (taskbarActivityContext != null - && !taskbarActivityContext.isInOverview() - && !taskbarActivityContext.showDesktopTaskbarForFreeformDisplay()) { - mShouldIgnoreNextDesktopModeChangeFromDisplayController = true; - AnimatorSet animatorSet = taskbarActivityContext.onDestroyAnimation( - TASKBAR_DESTROY_DURATION); - animatorSet.addListener(AnimatorListeners.forEndCallback( - () -> recreateTaskbarForDisplay(mPrimaryDisplayId, duration))); - animatorSet.start(); - } + TaskbarActivityContext taskbarActivityContext = getCurrentActivityContext(); + if (taskbarActivityContext != null + && !taskbarActivityContext.isInOverview() + && !taskbarActivityContext.showDesktopTaskbarForFreeformDisplay()) { + mShouldIgnoreNextDesktopModeChangeFromDisplayControllerForPrimary = true; + AnimatorSet animatorSet = taskbarActivityContext.onDestroyAnimation( + TASKBAR_DESTROY_DURATION); + animatorSet.addListener(AnimatorListeners.forEndCallback( + () -> recreateTaskbarForDisplay(mPrimaryDisplayId, duration))); + animatorSet.start(); } } @Override public void onEnterDesktopMode(int duration) { - for (Entry entry : mTaskbars.entrySet()) { - int displayId = entry.getKey(); - if (isExternalDisplay(displayId)) { - continue; - } - - TaskbarActivityContext taskbarActivityContext = entry.getValue(); - if (taskbarActivityContext != null - && !taskbarActivityContext.showDesktopTaskbarForFreeformDisplay()) { - mShouldIgnoreNextDesktopModeChangeFromDisplayController = true; - AnimatorSet animatorSet = taskbarActivityContext.onDestroyAnimation( - TASKBAR_DESTROY_DURATION); - animatorSet.addListener(AnimatorListeners.forEndCallback( - () -> recreateTaskbarForDisplay(mPrimaryDisplayId, duration))); - animatorSet.start(); - } + TaskbarActivityContext taskbarActivityContext = getCurrentActivityContext(); + if (taskbarActivityContext != null + && !taskbarActivityContext.showDesktopTaskbarForFreeformDisplay()) { + mShouldIgnoreNextDesktopModeChangeFromDisplayControllerForPrimary = true; + AnimatorSet animatorSet = taskbarActivityContext.onDestroyAnimation( + TASKBAR_DESTROY_DURATION); + animatorSet.addListener(AnimatorListeners.forEndCallback( + () -> recreateTaskbarForDisplay(mPrimaryDisplayId, duration))); + animatorSet.start(); } } @@ -640,14 +636,14 @@ public class TaskbarManagerImpl implements DisplayDecorationListener { public void onUserUnlocked() { debugPrimaryTaskbar("onUserUnlocked"); mUserUnlocked = true; - DisplayController.INSTANCE.get(mPrimaryWindowContext).addChangeListener( - mRecreationListener); + addRecreationListener(mPrimaryDisplayId); debugPrimaryTaskbar("onUserUnlocked: recreating all taskbars!"); // Create DPs for all connected displays if required. for (int i = 0; i < mWindowContexts.size(); i++) { int displayId = mWindowContexts.keyAt(i); if (displayId != mPrimaryDisplayId && !mExternalDeviceProfiles.contains(displayId)) { createExternalDeviceProfile(displayId); + addRecreationListener(displayId); } } @@ -783,7 +779,8 @@ public class TaskbarManagerImpl implements DisplayDecorationListener { * we fully want to destroy an existing taskbar for a specified display and create a new one. * In other case (folding/unfolding) we don't need to remove and add window. */ - private void recreateTaskbarForDisplay(int displayId, int duration) { + @VisibleForTesting + protected void recreateTaskbarForDisplay(int displayId, int duration) { debugTaskbarManager("recreateTaskbarForDisplay: ", displayId); Trace.beginSection("recreateTaskbarForDisplay"); try { @@ -1061,6 +1058,11 @@ public class TaskbarManagerImpl implements DisplayDecorationListener { "onDisplayAddSystemDecorations: createAndRegisterComponentCallbacks!", displayId); createAndRegisterComponentCallbacks(displayId); + + debugTaskbarManager( + "onDisplayAddSystemDecorations: addRecreationListener!", displayId); + addRecreationListener(displayId); + debugTaskbarManager("onDisplayAddSystemDecorations: recreateTaskbarForDisplay!", displayId); recreateTaskbarForDisplay(displayId, 0); @@ -1095,6 +1097,9 @@ public class TaskbarManagerImpl implements DisplayDecorationListener { displayId); removeAndUnregisterComponentCallbacks(displayId); + debugTaskbarManager("onDisplayRemoved: removeRecreationListener!", displayId); + removeRecreationListener(displayId); + debugTaskbarManager("onDisplayRemoved: removing DeviceProfile from map!", displayId); removeDeviceProfileFromMap(displayId); @@ -1152,10 +1157,7 @@ public class TaskbarManagerImpl implements DisplayDecorationListener { mGrowthBroadcastReceiver.unregisterReceiverSafely(); } - if (mUserUnlocked) { - DisplayController.INSTANCE.get(mPrimaryWindowContext).removeChangeListener( - mRecreationListener); - } + removeRecreationListener(mPrimaryDisplayId); SettingsCache.INSTANCE.get(mPrimaryWindowContext) .unregister(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener); SettingsCache.INSTANCE.get(mPrimaryWindowContext) @@ -1384,7 +1386,7 @@ public class TaskbarManagerImpl implements DisplayDecorationListener { * @param displayId The ID of the display. */ private void createExternalDeviceProfile(int displayId) { - if (!mUserUnlocked) { + if (!mUserUnlocked || displayId == mPrimaryDisplayId) { return; } @@ -1434,6 +1436,24 @@ public class TaskbarManagerImpl implements DisplayDecorationListener { mExternalDeviceProfiles.delete(displayId); } + private void addRecreationListener(int displayId) { + if (!mUserUnlocked) { + return; + } + + DisplayController.INSTANCE.get(mPrimaryWindowContext).addChangeListenerForDisplay( + mRecreationListener, displayId); + } + + private void removeRecreationListener(int displayId) { + if (!mUserUnlocked) { + return; + } + + DisplayController.INSTANCE.get(mPrimaryWindowContext).removeChangeListenerForDisplay( + mRecreationListener, displayId); + } + /** * Create {@link ComponentCallbacks} for the given display and register it to the relevant * WindowContext. For external displays, populate maps. @@ -1467,16 +1487,17 @@ public class TaskbarManagerImpl implements DisplayDecorationListener { debugTaskbarManager("onConfigurationChanged: | configDiff=" + Configuration.configurationDiffToString(configDiff), displayId); - if (configDiff != 0 || getCurrentActivityContext() == null) { + TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId); + if (configDiff != 0 || taskbar == null) { debugTaskbarManager("onConfigurationChanged: call recreateTaskbars", displayId); - recreateTaskbars(); + recreateTaskbarForDisplay(displayId, /* duration= */ 0); } else if (dp != null) { // Config change might be handled without re-creating the taskbar if (!isTaskbarEnabled(dp)) { debugPrimaryTaskbar( "onConfigurationChanged: isTaskbarEnabled(dp)=False | " + "destroyTaskbarForDisplay"); - destroyTaskbarForDisplay(mPrimaryDisplayId); + destroyTaskbarForDisplay(displayId); } else { debugPrimaryTaskbar("onConfigurationChanged: isTaskbarEnabled(dp)=True"); if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) { @@ -1484,16 +1505,16 @@ public class TaskbarManagerImpl implements DisplayDecorationListener { // by looking at screen-size change flag in configDiff in the // block above? debugPrimaryTaskbar("onConfigurationChanged: call recreateTaskbars"); - recreateTaskbars(); + recreateTaskbarForDisplay(displayId, /* duration= */ 0); } else { debugPrimaryTaskbar( "onConfigurationChanged: updateDeviceProfile for current " + "taskbar."); - getCurrentActivityContext().updateDeviceProfile(dp); + taskbar.updateDeviceProfile(dp); } } } else { - getCurrentActivityContext().onConfigurationChanged(configDiff); + taskbar.onConfigurationChanged(configDiff); } mOldConfig = new Configuration(newConfig); // reset taskbar was pinned value, so we don't automatically unstash taskbar upon diff --git a/quickstep/src/com/android/launcher3/taskbar/Utilities.java b/quickstep/src/com/android/launcher3/taskbar/Utilities.java index 123f0a3d00..9182e03175 100644 --- a/quickstep/src/com/android/launcher3/taskbar/Utilities.java +++ b/quickstep/src/com/android/launcher3/taskbar/Utilities.java @@ -57,6 +57,7 @@ public final class Utilities { ThemeManager.INSTANCE.get(activityContext).getIconState().getShapeRadius(); float iconSizeRatio = taskbarIconSize / maxIconSize; return dpToPx((iconShapeRadius * iconSizeRatio) - + TaskbarIconSpecs.INSTANCE.getDefaultTransientIconMargin().getSize()); + + TaskbarIconSpecs.INSTANCE.getDefaultTransientIconMargin().getSize(), + activityContext); } } diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt index 16555085cf..d224a3241b 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt @@ -17,9 +17,11 @@ package com.android.launcher3.taskbar import android.animation.AnimatorTestRule +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import com.android.launcher3.Flags import com.android.launcher3.LauncherPrefs import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING_IN_DESKTOP_MODE @@ -53,6 +55,7 @@ import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.UserSetupMode import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext +import com.android.launcher3.taskbar.rules.displayControllerSpy import com.android.launcher3.util.LauncherMultivalentJUnit import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED @@ -64,6 +67,8 @@ import org.junit.After import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.spy import org.mockito.kotlin.whenever @RunWith(LauncherMultivalentJUnit::class) @@ -125,7 +130,27 @@ class TaskbarStashControllerTest { } @Test - fun testRecreateAsTransient_timeoutStarted() { + @DisableFlags(Flags.FLAG_ENABLE_OVERVIEW_ON_CONNECTED_DISPLAYS) + fun testRecreateAsTransient_withoutOverviewOnConnectedDisplays_timeoutStarted() { + context.displayControllerSpy?.setupTaskbarPinningPrefListener(context.displayId) + + testRecreateAsTransient_timeoutStarted() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_OVERVIEW_ON_CONNECTED_DISPLAYS) + fun testRecreateAsTransient_withOverviewOnConnectedDisplay_timeoutStarted() { + context.displayControllerSpy?.let { controller -> + controller.setupTaskbarPinningPrefListener(context.displayId) + controller.infoModifierForDisplay = { + spy(it) { on { it?.isTransientTaskbar } doReturn true } + } + } + + testRecreateAsTransient_timeoutStarted() + } + + private fun testRecreateAsTransient_timeoutStarted() { var isPinned by TASKBAR_PINNING.asProperty(context) isPinned = true activityContext.controllers.sharedState?.taskbarWasPinned = true diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarSandboxComponent.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarSandboxComponent.kt index b7d49e5695..6371f9ad28 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarSandboxComponent.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarSandboxComponent.kt @@ -18,7 +18,10 @@ package com.android.launcher3.taskbar.rules import android.content.Context import com.android.app.displaylib.PerDisplayRepository +import com.android.launcher3.Flags +import com.android.launcher3.LauncherPrefChangeListener import com.android.launcher3.LauncherPrefs +import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING import com.android.launcher3.concurrent.ExecutorsModule import com.android.launcher3.dagger.ApiWrapperModule import com.android.launcher3.dagger.AppModule @@ -99,15 +102,54 @@ class DisplayControllerSpy constructor( @ApplicationContext context: Context, wmProxy: WindowManagerProxy, - prefs: LauncherPrefs, + private val prefs: LauncherPrefs, lifecycle: DaggerSingletonTracker, ) : DisplayController(context, wmProxy, prefs, lifecycle) { var infoModifier: ((Info) -> Info)? = null + var infoModifierForDisplay: ((Info?) -> Info?)? = null + + private var prefListener: LauncherPrefChangeListener? = null + + init { + // When overview on CD is disabled, DisplayController only adds the info associated with + // the DEFAULT_DISPLAY. So, instead of changing the production code of DisplayController to + // use display from context we manually add the info associated with the virtual display. + if (!Flags.enableOverviewOnConnectedDisplays()) { + getOrCreatePerDisplayInfo(context.display) + lifecycle.addCloseable { removePerDisplayInfo(context.displayId) } + } + } override fun getInfo(): Info = infoModifier?.invoke(super.getInfo()) ?: super.getInfo() + + override fun getInfoForDisplay(displayId: Int): Info? = + infoModifierForDisplay?.invoke(super.getInfoForDisplay(displayId)) + ?: super.getInfoForDisplay(displayId) + + /** + * Sets up [TASKBAR_PINNING] pref listener for the given display. + * + *

DisplayController sets up LauncherPrefChangeListener only for the DEFAULT_DISPLAY, this is + * correct but tests rely on treating the created virtual display as default. So, instead of + * changing the production code of DisplayController to be more testable, we add a custom + * listener for our virtual display. + */ + fun setupTaskbarPinningPrefListener(displayId: Int) { + prefListener = + LauncherPrefChangeListener { notifyConfigChangeForDisplay(displayId) } + .also { prefs.addListener(it, TASKBAR_PINNING) } + } + + fun removeTaskbarPinningPrefListener() { + prefListener?.let { prefs.removeListener(it, TASKBAR_PINNING) } + } } +/** Convenient extension to access [DisplayControllerSpy] from [TaskbarWindowSandboxContext]. */ +val TaskbarWindowSandboxContext.displayControllerSpy: DisplayControllerSpy? + get() = DisplayController.INSTANCE[this] as? DisplayControllerSpy + @Module object DesktopVisibilityControllerModule { @JvmStatic diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt index 8827836d07..0fa3e5a7b3 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt @@ -119,9 +119,11 @@ class TaskbarUnitTestRule( object : TaskbarNavButtonCallbacks {}, RecentsWindowManager.REPOSITORY_INSTANCE.get(context), ) { - override fun recreateTaskbars() { - super.recreateTaskbars() - if (currentActivityContext != null) { + override fun recreateTaskbarForDisplay(displayId: Int, duration: Int) { + super.recreateTaskbarForDisplay(displayId, duration) + if ( + displayId == context.displayId && currentActivityContext != null + ) { injectControllers() // TODO(b/346394875): we should test a non-default uiController. activityContext.setUIController(TaskbarUIController.DEFAULT) @@ -148,6 +150,7 @@ class TaskbarUnitTestRule( base.evaluate() } finally { instrumentation.runOnMainSync { taskbarManager.destroy() } + context.displayControllerSpy?.removeTaskbarPinningPrefListener() } } } diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java index aee895a396..2a5586efbf 100644 --- a/src/com/android/launcher3/util/DisplayController.java +++ b/src/com/android/launcher3/util/DisplayController.java @@ -454,7 +454,8 @@ public class DisplayController implements DesktopVisibilityListener { } } - private PerDisplayInfo getOrCreatePerDisplayInfo(Display display) { + @VisibleForTesting + protected PerDisplayInfo getOrCreatePerDisplayInfo(Display display) { int displayId = display.getDisplayId(); PerDisplayInfo perDisplayInfo = mPerDisplayInfo.get(displayId); if (perDisplayInfo != null) { @@ -477,7 +478,8 @@ public class DisplayController implements DesktopVisibilityListener { * Clean up resources for the given display id. * @param displayId The display id */ - void removePerDisplayInfo(int displayId) { + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + protected void removePerDisplayInfo(int displayId) { PerDisplayInfo info = mPerDisplayInfo.get(displayId); if (info == null) return; info.cleanup(); @@ -769,7 +771,8 @@ public class DisplayController implements DesktopVisibilityListener { } } - private class PerDisplayInfo implements ComponentCallbacks { + @VisibleForTesting + protected class PerDisplayInfo implements ComponentCallbacks { final int mDisplayId; final CopyOnWriteArrayList mListeners = new CopyOnWriteArrayList<>();