Refactor all uses of DisplayController singleton INSTANCE
And make DisplayController display id aware
Test: locally tested on Tangor
Flag: EXEMPT refactor
Bug: 392858637
Change-Id: I805cd7323c48a2988c95b9fda7f6cfe4c153860c
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 6277e41..db4480a 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -178,6 +178,8 @@
private ActionMode mCurrentActionMode;
+ private DisplayController mDisplayController;
+
@Override
public ViewCache getViewCache() {
return mViewCache;
@@ -224,7 +226,8 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
registerBackDispatcher();
- DisplayController.INSTANCE.get(this).addChangeListener(this);
+ mDisplayController = DisplayController.get(this);
+ mDisplayController.addChangeListener(this);
}
@Override
@@ -272,7 +275,9 @@
protected void onDestroy() {
super.onDestroy();
mEventCallbacks[EVENT_DESTROYED].executeAllAndClear();
- DisplayController.INSTANCE.get(this).removeChangeListener(this);
+ if (mDisplayController != null) {
+ mDisplayController.removeChangeListener(this);
+ }
}
@Override
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 900f74d..472cac4 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -16,6 +16,8 @@
package com.android.launcher3;
+import static android.view.Display.DEFAULT_DISPLAY;
+
import static com.android.launcher3.LauncherPrefs.DB_FILE;
import static com.android.launcher3.LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE;
import static com.android.launcher3.LauncherPrefs.FIXED_LANDSCAPE_MODE;
@@ -256,7 +258,7 @@
String gridName = getCurrentGridName(context);
initGrid(context, gridName);
- DisplayController dc = DisplayController.INSTANCE.get(context);
+ DisplayController dc = DisplayController.get(context, DEFAULT_DISPLAY);
dc.setPriorityListener(
(displayContext, info, flags) -> {
if ((flags & (CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS
@@ -315,7 +317,7 @@
String gridName = getCurrentGridName(context);
// Get the display info based on default display and interpolate it to existing display
- Info defaultInfo = DisplayController.INSTANCE.get(context).getInfo();
+ Info defaultInfo = DisplayController.get(context, display.getDisplayId()).getInfo();
@DeviceType int defaultDeviceType = defaultInfo.getDeviceType();
DisplayOption defaultDisplayOption = invDistWeightedInterpolate(
defaultInfo,
@@ -374,7 +376,7 @@
+ ", dbFile:" + dbFile
+ ", LauncherPrefs GRID_NAME:" + LauncherPrefs.get(context).get(GRID_NAME)
+ ", LauncherPrefs DB_FILE:" + LauncherPrefs.get(context).get(DB_FILE));
- Info displayInfo = DisplayController.INSTANCE.get(context).getInfo();
+ Info displayInfo = DisplayController.get(context).getInfo();
List<DisplayOption> allOptions = getPredefinedDeviceProfiles(
context,
gridName,
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 2a5cd63..fba94fd 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -299,7 +299,7 @@
@JvmField
val ALLOW_ROTATION =
backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY, Boolean::class.java) {
- RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info)
+ RotationHelper.getAllowRotationDefaultValue(DisplayController.get(it).info)
}
@JvmField
diff --git a/src/com/android/launcher3/dagger/LauncherAppModule.java b/src/com/android/launcher3/dagger/LauncherAppModule.java
index c58a414..ece0ff0 100644
--- a/src/com/android/launcher3/dagger/LauncherAppModule.java
+++ b/src/com/android/launcher3/dagger/LauncherAppModule.java
@@ -23,6 +23,7 @@
ApiWrapperModule.class,
PluginManagerWrapperModule.class,
StaticObjectModule.class,
+ PerDisplayObjectProviderModule.class,
AppModule.class
})
public class LauncherAppModule {
diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
index 7bd7c3e..fe23093 100644
--- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
+++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
@@ -26,11 +26,11 @@
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.ApiWrapper;
import com.android.launcher3.util.DaggerSingletonTracker;
-import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DynamicResource;
import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.MSDLPlayerWrapper;
import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.PerDisplayObjectProvider;
import com.android.launcher3.util.PluginManagerWrapper;
import com.android.launcher3.util.ScreenOnTracker;
import com.android.launcher3.util.SettingsCache;
@@ -69,7 +69,7 @@
LauncherPrefs getLauncherPrefs();
ThemeManager getThemeManager();
UserCache getUserCache();
- DisplayController getDisplayController();
+ PerDisplayObjectProvider getPerDisplayObjectProvider();
WallpaperColorHints getWallpaperColorHints();
LockedUserState getLockedUserState();
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 911064c..4c39aa0 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -284,7 +284,7 @@
* Returns the insets of the screen closest to the display given by the context
*/
private Rect getInsets(Context context) {
- DisplayController.Info info = DisplayController.INSTANCE.get(context).getInfo();
+ DisplayController.Info info = DisplayController.get(context).getInfo();
float maxDiff = Float.MAX_VALUE;
Display display = context.getDisplay();
Rect insets = new Rect();
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index 6008287..1771627 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -292,7 +292,7 @@
* will remove that preference from the list.
*/
protected boolean initPreference(Preference preference) {
- DisplayController.Info info = DisplayController.INSTANCE.get(getContext()).getInfo();
+ DisplayController.Info info = DisplayController.get(getContext()).getInfo();
switch (preference.getKey()) {
case NOTIFICATION_DOTS_PREFERENCE_KEY:
return BuildConfig.NOTIFICATION_DOTS_ENABLED;
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 9376518..f6017c1 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -189,7 +189,7 @@
public void initialize() {
if (mInitialized) return;
mInitialized = true;
- DisplayController displayController = DisplayController.INSTANCE.get(mActivity);
+ DisplayController displayController = DisplayController.get(mActivity);
DisplayController.Info info = displayController.getInfo();
setIgnoreAutoRotateSettings(info.isTablet(info.realBounds));
displayController.addChangeListener(this);
@@ -201,7 +201,7 @@
if (mDestroyed) return;
mDestroyed = true;
mActivity.removeOnDeviceProfileChangeListener(this);
- DisplayController.INSTANCE.get(mActivity).removeChangeListener(this);
+ DisplayController.get(mActivity).removeChangeListener(this);
LauncherPrefs.get(mActivity).removeListener(this, ALLOW_ROTATION);
}
diff --git a/src/com/android/launcher3/util/DaggerSingletonTracker.java b/src/com/android/launcher3/util/DaggerSingletonTracker.java
index b7a88db..1de32e0 100644
--- a/src/com/android/launcher3/util/DaggerSingletonTracker.java
+++ b/src/com/android/launcher3/util/DaggerSingletonTracker.java
@@ -38,7 +38,7 @@
private boolean mClosed = false;
@Inject
- DaggerSingletonTracker() {
+ public DaggerSingletonTracker() {
}
/**
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index ee1af81..5ad22bf 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -16,6 +16,7 @@
package com.android.launcher3.util;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static com.android.launcher3.InvariantDeviceProfile.TYPE_MULTI_DISPLAY;
@@ -55,7 +56,6 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.dagger.ApplicationContext;
import com.android.launcher3.dagger.LauncherAppComponent;
-import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.util.window.CachedDisplayInfo;
import com.android.launcher3.util.window.WindowManagerProxy;
@@ -71,13 +71,10 @@
import java.util.StringJoiner;
import java.util.concurrent.CopyOnWriteArrayList;
-import javax.inject.Inject;
-
/**
* Utility class to cache properties of default display to avoid a system RPC on every call.
*/
@SuppressLint("NewApi")
-@LauncherAppSingleton
public class DisplayController implements ComponentCallbacks,
DesktopVisibilityListener {
@@ -89,8 +86,8 @@
// TODO(b/254119092) remove all logs with this tag
public static final String TASKBAR_NOT_DESTROYED_TAG = "b/254119092";
- public static final DaggerSingletonObject<DisplayController> INSTANCE =
- new DaggerSingletonObject<>(LauncherAppComponent::getDisplayController);
+ public static final DaggerSingletonObject<PerDisplayObjectProvider> PROVIDER =
+ new DaggerSingletonObject<>(LauncherAppComponent::getPerDisplayObjectProvider);
public static final int CHANGE_ACTIVE_SCREEN = 1 << 0;
public static final int CHANGE_ROTATION = 1 << 1;
@@ -125,14 +122,54 @@
private Info mInfo;
private boolean mDestroyed = false;
+ private final int mDisplayId;
- @Inject
- protected DisplayController(@ApplicationContext Context context,
+ /**
+ * Get a DisplayController associated with the given Context.
+ * @param context the context (must return a valid display id)
+ * @return the DisplayController instance associated with the display id of the context
+ */
+ public static DisplayController get(Context context) {
+ int displayId = DEFAULT_DISPLAY;
+ if (context != null) {
+ try {
+ displayId = context.getDisplay().getDisplayId();
+ } catch (UnsupportedOperationException ignored) {
+ Log.w(TAG, "DisplayController access from non-display context");
+ }
+ }
+ if (displayId == INVALID_DISPLAY) {
+ displayId = DEFAULT_DISPLAY;
+ }
+ return PROVIDER.get(context).getDisplayController(displayId);
+ }
+
+ /**
+ * Get a DisplayController associated with the given display id.
+ * @param context a context
+ * @param displayId a display id
+ * @return the DisplayController instance associated with the given display id
+ */
+ public static DisplayController get(Context context, int displayId) {
+ return PROVIDER.get(context).getDisplayController(displayId);
+ }
+
+ @VisibleForTesting
+ public DisplayController(@ApplicationContext Context context,
WindowManagerProxy wmProxy,
LauncherPrefs prefs,
DaggerSingletonTracker lifecycle) {
+ this(context, wmProxy, prefs, lifecycle, DEFAULT_DISPLAY);
+ }
+
+ public DisplayController(@ApplicationContext Context context,
+ WindowManagerProxy wmProxy,
+ LauncherPrefs prefs,
+ DaggerSingletonTracker lifecycle,
+ int displayId) {
mContext = context;
mWMProxy = wmProxy;
+ mDisplayId = displayId;
if (enableTaskbarPinning()) {
LauncherPrefChangeListener prefListener = key -> {
@@ -150,11 +187,17 @@
prefs.addListener(prefListener, TASKBAR_PINNING);
prefs.addListener(prefListener, TASKBAR_PINNING_IN_DESKTOP_MODE);
lifecycle.addCloseable(() -> prefs.removeListener(
- prefListener, TASKBAR_PINNING, TASKBAR_PINNING_IN_DESKTOP_MODE));
+ prefListener, TASKBAR_PINNING, TASKBAR_PINNING_IN_DESKTOP_MODE));
}
Display display = context.getSystemService(DisplayManager.class)
- .getDisplay(DEFAULT_DISPLAY);
+ .getDisplay(displayId);
+ if (display == null) {
+ // Race when a display is rapidly added then removed.
+ mWindowContext = null;
+ mInfo = null;
+ return;
+ }
mWindowContext = mContext.createWindowContext(display, TYPE_APPLICATION, null);
mWindowContext.registerComponentCallbacks(this);
@@ -174,20 +217,25 @@
});
}
- /**
- * Returns the current navigation mode
- */
- public static NavigationMode getNavigationMode(Context context) {
- return INSTANCE.get(context).getInfo().getNavigationMode();
+ public int getDisplayId() {
+ return mDisplayId;
}
/**
- * Returns whether taskbar is transient or persistent.
+ * Returns the current navigation mode for the display associated with the given Context.
+ */
+ public static NavigationMode getNavigationMode(Context context) {
+ return get(context).getInfo().getNavigationMode();
+ }
+
+ /**
+ * Returns whether taskbar is transient or persistent for the display associated with the given
+ * Context.
*
* @return {@code true} if transient, {@code false} if persistent.
*/
public static boolean isTransientTaskbar(Context context) {
- return INSTANCE.get(context).getInfo().isTransientTaskbar();
+ return get(context).getInfo().isTransientTaskbar();
}
/**
@@ -207,25 +255,27 @@
}
/**
- * Returns whether the taskbar is pinned in gesture navigation mode.
+ * Returns whether the taskbar is pinned in gesture navigation mode for the display associated
+ * with the given Context.
*/
public static boolean isPinnedTaskbar(Context context) {
- return INSTANCE.get(context).getInfo().isPinnedTaskbar();
+ return get(context).getInfo().isPinnedTaskbar();
}
/**
- * Returns whether the taskbar is forced to be pinned when home is visible.
+ * Returns whether the taskbar is forced to be pinned when home is visible for the display
+ * associated with the given Context.
*/
public static boolean showLockedTaskbarOnHome(Context context) {
- return INSTANCE.get(context).getInfo().showLockedTaskbarOnHome();
+ return get(context).getInfo().showLockedTaskbarOnHome();
}
/**
* Returns whether desktop taskbar (pinned taskbar that shows desktop tasks) is to be used
- * on the display because the display is a freeform display.
+ * on the display associated with the given Context because the display is a freeform display.
*/
public static boolean showDesktopTaskbarForFreeformDisplay(Context context) {
- return INSTANCE.get(context).getInfo().showDesktopTaskbarForFreeformDisplay();
+ return get(context).getInfo().showDesktopTaskbarForFreeformDisplay();
}
@Override
@@ -261,6 +311,7 @@
@Override
public final void onConfigurationChanged(Configuration config) {
Log.d(TASKBAR_NOT_DESTROYED_TAG, "DisplayController#onConfigurationChanged: " + config);
+ if (mWindowContext == null || mInfo == null) return;
if (config.densityDpi != mInfo.densityDpi
|| config.fontScale != mInfo.fontScale
|| !mInfo.mScreenSizeDp.equals(
@@ -295,6 +346,7 @@
@AnyThread
public void notifyConfigChange() {
+ if (mWindowContext == null || mInfo == null) return;
Info oldInfo = mInfo;
Context displayInfoContext = mWindowContext;
@@ -348,6 +400,7 @@
}
private void notifyChange(Context context, int flags) {
+ if (mInfo == null) return;
if (mPriorityListener != null) {
mPriorityListener.onDisplayInfoChanged(context, mInfo, flags);
}
@@ -582,6 +635,7 @@
* Dumps the current state information
*/
public void dump(PrintWriter pw) {
+ if (mInfo == null) return;
Info info = mInfo;
pw.println("DisplayController.Info:");
pw.println(" normalizedDisplayInfo=" + info.normalizedDisplayInfo);
diff --git a/src/com/android/launcher3/util/PerDisplayObjectProvider.java b/src/com/android/launcher3/util/PerDisplayObjectProvider.java
new file mode 100644
index 0000000..8cb4e20
--- /dev/null
+++ b/src/com/android/launcher3/util/PerDisplayObjectProvider.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+/**
+ * Interface for providers of objects for which there is one per display. The lifecycle of the
+ * object is for the time the display is connected or is that of the app, whichever is shorter.
+ */
+public interface PerDisplayObjectProvider {
+ /**
+ * Get the DisplayController the given display id.
+ *
+ * @param displayId The display id
+ * @return Returns the display controller if the display id is valid and otherwise throws an
+ * IllegalArgumentException.
+ */
+ DisplayController getDisplayController(int displayId);
+}