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
This commit is contained in:
Ajinkya Chalke
2025-05-15 22:42:09 +00:00
parent a098f6a855
commit 1fd6090eb7
6 changed files with 157 additions and 62 deletions
@@ -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<Integer, TaskbarActivityContext> 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<Integer, TaskbarActivityContext> 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
@@ -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);
}
}
@@ -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
@@ -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.
*
* <p>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
@@ -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()
}
}
}
@@ -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<DisplayInfoChangeListener> mListeners =
new CopyOnWriteArrayList<>();