Wrapping display properties in a wrapper class

This would allow us to support gestures on multiple-displays

Change-Id: I8f426bbdf04520f4189cadce2bde6aab4933c8d9
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 5c9fd47..3867c35 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -38,7 +38,7 @@
 import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT;
 import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP;
 import static com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory.INDEX_PAUSE_TO_OVERVIEW_ANIM;
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.CANCEL;
 import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AbsSwipeUpHandler.java
index d2fee30..282f6fa 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -29,7 +29,7 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_GESTURE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
 import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
index e2e25f3..a5af181 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
@@ -41,7 +41,7 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.statehandlers.DepthController;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.util.SurfaceTransactionApplier;
 import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.util.TransformParams;
@@ -146,7 +146,7 @@
         DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile();
         // RecentsView never updates the display rotation until swipe-up so the value may be stale.
         // Use the display value instead.
-        int displayRotation = DefaultDisplay.INSTANCE.get(context).getInfo().rotation;
+        int displayRotation = DisplayController.getDefaultDisplay(context).getInfo().rotation;
 
         TaskViewSimulator topMostSimulator = null;
         if (targets.apps.length > 0) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
index be3fdde..a4a7bd3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
@@ -21,7 +21,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.Utilities;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.RecentsActivity;
 import com.android.quickstep.SysUINavigationMode;
@@ -43,7 +43,7 @@
         SysUINavigationMode.Mode sysUINavigationMode = SysUINavigationMode.getMode(mActivity);
         if (sysUINavigationMode == SysUINavigationMode.Mode.NO_BUTTON) {
             NavBarPosition navBarPosition = new NavBarPosition(sysUINavigationMode,
-                    DefaultDisplay.INSTANCE.get(mActivity).getInfo());
+                    DisplayController.getDefaultDisplay(mActivity).getInfo());
             mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(mActivity,
                     true /* disableHorizontalSwipe */, navBarPosition,
                     null /* onInterceptTouch */, this);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index 7032fae..584ff28 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -41,7 +41,7 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.InputConsumer;
@@ -115,7 +115,7 @@
                 R.dimen.device_locked_y_offset);
 
         // Do not use DeviceProfile as the user data might be locked
-        mDisplaySize = DefaultDisplay.INSTANCE.get(context).getInfo().realSize;
+        mDisplaySize = DisplayController.getDefaultDisplay(context).getInfo().realSize;
 
         // Init states
         mStateCallback = new MultiStateCallback(STATE_NAMES);
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
index 22d205a..5cb55ec 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
@@ -32,7 +32,7 @@
 import android.view.Surface;
 
 import com.android.launcher3.ResourceUtils;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
 
 import org.junit.Before;
 import org.junit.Ignore;
@@ -50,7 +50,7 @@
     private OrientationTouchTransformer mTouchTransformer;
 
     Resources mResources;
-    private DefaultDisplay.Info mInfo;
+    private DisplayController.Info mInfo;
 
 
     @Before
@@ -231,12 +231,12 @@
         assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion2.getX(), inRegion2.getY()));
     }
 
-    private DefaultDisplay.Info createDisplayInfo(int rotation) {
+    private DisplayController.Info createDisplayInfo(int rotation) {
         Point p = new Point(SIZE_WIDTH, SIZE_HEIGHT);
         if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
             p = new Point(SIZE_HEIGHT, SIZE_WIDTH);
         }
-        return new DefaultDisplay.Info(0, rotation, 0, p, p, p, null);
+        return new DisplayController.Info(0, rotation, 0, p, p, p, null);
     }
 
     private float generateTouchRegionHeight(int rotation) {
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
index a31ba21..5491daa 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -27,7 +27,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.shadows.LShadowDisplay;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.LauncherActivityInterface;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
 
@@ -144,7 +144,7 @@
                     LauncherActivityInterface.INSTANCE);
             tvs.setDp(mDeviceProfile);
 
-            int launcherRotation = DefaultDisplay.INSTANCE.get(mContext).getInfo().rotation;
+            int launcherRotation = DisplayController.INSTANCE.get(mContext).getInfo().rotation;
             if (mAppRotation < 0) {
                 mAppRotation = launcherRotation;
             }
diff --git a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
index 5ad0bca..199cf63 100644
--- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
@@ -16,7 +16,7 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.Utilities.postAsyncCallback;
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 import static com.android.systemui.shared.recents.utilities.Utilities.postAtFrontOfQueueAsynchronously;
 
 import android.animation.Animator;
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index b2bce0c..eb33f98 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -37,7 +37,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController.Info;
 
 import java.io.PrintWriter;
 
@@ -85,7 +85,7 @@
      * QUICKSTEP_ROTATION_UNINITIALIZED, then user has not tapped on an active nav region.
      * Otherwise it will be the rotation of the display when the user first interacted with the
      * active nav bar region.
-     * The "session" ends when {@link #enableMultipleRegions(boolean, DefaultDisplay.Info)} is
+     * The "session" ends when {@link #enableMultipleRegions(boolean, Info)} is
      * called - usually from a timeout or if user starts interacting w/ the foreground app.
      *
      * This is different than {@link #mLastRectTouched} as it can get reset by the system whereas
@@ -108,7 +108,7 @@
         mNavBarGesturalHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
     }
 
-    private void refreshTouchRegion(DefaultDisplay.Info info, Resources newRes) {
+    private void refreshTouchRegion(Info info, Resources newRes) {
         // Swipe touch regions are independent of nav mode, so we have to clear them explicitly
         // here to avoid, for ex, a nav region for 2-button rotation 0 being used for 3-button mode
         // It tries to cache and reuse swipe regions whenever possible based only on rotation
@@ -117,7 +117,7 @@
         resetSwipeRegions(info);
     }
 
-    void setNavigationMode(SysUINavigationMode.Mode newMode, DefaultDisplay.Info info,
+    void setNavigationMode(SysUINavigationMode.Mode newMode, Info info,
             Resources newRes) {
         if (mMode == newMode) {
             return;
@@ -126,7 +126,7 @@
         refreshTouchRegion(info, newRes);
     }
 
-    void setGesturalHeight(int newGesturalHeight, DefaultDisplay.Info info, Resources newRes) {
+    void setGesturalHeight(int newGesturalHeight, Info info, Resources newRes) {
         if (mNavBarGesturalHeight == newGesturalHeight) {
             return;
         }
@@ -140,9 +140,9 @@
      * alongside other regions.
      * Ok to call multiple times
      *
-     * @see #enableMultipleRegions(boolean, DefaultDisplay.Info)
+     * @see #enableMultipleRegions(boolean, Info)
      */
-    void createOrAddTouchRegion(DefaultDisplay.Info info) {
+    void createOrAddTouchRegion(Info info) {
         mCurrentDisplayRotation = info.rotation;
         if (mQuickStepStartingRotation > QUICKSTEP_ROTATION_UNINITIALIZED
                 && mCurrentDisplayRotation == mQuickStepStartingRotation) {
@@ -170,7 +170,7 @@
      * @param enableMultipleRegions Set to true to start tracking multiple nav bar regions
      * @param info The current displayInfo which will be the start of the quickswitch gesture
      */
-    void enableMultipleRegions(boolean enableMultipleRegions, DefaultDisplay.Info info) {
+    void enableMultipleRegions(boolean enableMultipleRegions, Info info) {
         mEnableMultipleRegions = enableMultipleRegions &&
                 mMode != SysUINavigationMode.Mode.TWO_BUTTONS;
         if (mEnableMultipleRegions) {
@@ -191,7 +191,7 @@
      *
      * @param displayInfo The display whos rotation will be used as the current active rotation
      */
-    void setSingleActiveRegion(DefaultDisplay.Info displayInfo) {
+    void setSingleActiveRegion(Info displayInfo) {
         mActiveTouchRotation = displayInfo.rotation;
         resetSwipeRegions(displayInfo);
     }
@@ -202,7 +202,7 @@
      * To be called whenever we want to stop tracking more than one swipe region.
      * Ok to call multiple times.
      */
-    private void resetSwipeRegions(DefaultDisplay.Info region) {
+    private void resetSwipeRegions(Info region) {
         if (DEBUG) {
             Log.d(TAG, "clearing all regions except rotation: " + mCurrentDisplayRotation);
         }
@@ -226,7 +226,7 @@
         }
     }
 
-    private OrientationRectF createRegionForDisplay(DefaultDisplay.Info display) {
+    private OrientationRectF createRegionForDisplay(Info display) {
         if (DEBUG) {
             Log.d(TAG, "creating rotation region for: " + mCurrentDisplayRotation);
         }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index fe152a0..e9fc423 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -17,8 +17,8 @@
 
 import static android.content.Intent.ACTION_USER_UNLOCKED;
 
-import static com.android.launcher3.util.DefaultDisplay.CHANGE_ALL;
-import static com.android.launcher3.util.DefaultDisplay.CHANGE_FRAME_DELAY;
+import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_ALL;
+import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_FRAME_DELAY;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
 import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
@@ -56,7 +56,10 @@
 
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.DisplayController.DisplayHolder;
+import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
+import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.SecureSettingsObserver;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
 import com.android.quickstep.SysUINavigationMode.OneHandedModeChangeListener;
@@ -76,14 +79,14 @@
  */
 public class RecentsAnimationDeviceState implements
         NavigationModeChangeListener,
-        DefaultDisplay.DisplayInfoChangeListener,
+        DisplayInfoChangeListener,
         OneHandedModeChangeListener {
 
     static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode";
 
     private final Context mContext;
     private final SysUINavigationMode mSysUiNavMode;
-    private final DefaultDisplay mDefaultDisplay;
+    private final DisplayHolder mDisplayHolder;
     private final int mDisplayId;
     private final RotationTouchHelper mRotationTouchHelper;
 
@@ -120,13 +123,17 @@
     private boolean mIsUserSetupComplete;
 
     public RecentsAnimationDeviceState(Context context) {
+        this(context, DisplayController.getDefaultDisplay(context));
+    }
+
+    public RecentsAnimationDeviceState(Context context, DisplayHolder displayHolder) {
         mContext = context;
+        mDisplayHolder = displayHolder;
         mSysUiNavMode = SysUINavigationMode.INSTANCE.get(context);
-        mDefaultDisplay = DefaultDisplay.INSTANCE.get(context);
-        mDisplayId = mDefaultDisplay.getInfo().id;
+        mDisplayId = mDisplayHolder.getInfo().id;
         mIsOneHandedModeSupported = SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false);
-        runOnDestroy(() -> mDefaultDisplay.removeChangeListener(this));
-        mRotationTouchHelper = new RotationTouchHelper(context);
+        runOnDestroy(() -> mDisplayHolder.removeChangeListener(this));
+        mRotationTouchHelper = new RotationTouchHelper(context, mDisplayHolder);
         runOnDestroy(mRotationTouchHelper::destroy);
 
         // Register for user unlocked if necessary
@@ -232,9 +239,9 @@
 
     @Override
     public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) {
-        mDefaultDisplay.removeChangeListener(this);
-        mDefaultDisplay.addChangeListener(this);
-        onDisplayInfoChanged(mDefaultDisplay.getInfo(), CHANGE_ALL);
+        mDisplayHolder.removeChangeListener(this);
+        mDisplayHolder.addChangeListener(this);
+        onDisplayInfoChanged(mDisplayHolder.getInfo(), CHANGE_ALL);
 
         if (newMode == NO_BUTTON) {
             mExclusionListener.register();
@@ -242,12 +249,12 @@
             mExclusionListener.unregister();
         }
 
-        mNavBarPosition = new NavBarPosition(newMode, mDefaultDisplay.getInfo());
+        mNavBarPosition = new NavBarPosition(newMode, mDisplayHolder.getInfo());
         mMode = newMode;
     }
 
     @Override
-    public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) {
+    public void onDisplayInfoChanged(Info info, int flags) {
         if (info.id != getDisplayId() || flags == CHANGE_FRAME_DELAY) {
             // ignore displays that aren't running launcher and frame refresh rate changes
             return;
@@ -529,7 +536,7 @@
         }
 
         if (mIsOneHandedModeEnabled || mIsSwipeToNotificationEnabled) {
-            final DefaultDisplay.Info displayInfo = mDefaultDisplay.getInfo();
+            final Info displayInfo = mDisplayHolder.getInfo();
             return (mRotationTouchHelper.touchInOneHandedModeRegion(ev)
                 && displayInfo.rotation != Surface.ROTATION_90
                 && displayInfo.rotation != Surface.ROTATION_270
diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
index 2b5e42a..2cf3212 100644
--- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -17,8 +17,8 @@
 
 import static android.view.Surface.ROTATION_0;
 
-import static com.android.launcher3.util.DefaultDisplay.CHANGE_ALL;
-import static com.android.launcher3.util.DefaultDisplay.CHANGE_FRAME_DELAY;
+import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_ALL;
+import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_FRAME_DELAY;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
 
@@ -28,8 +28,9 @@
 import android.view.OrientationEventListener;
 
 import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.util.DefaultDisplay;
-import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.DisplayController.DisplayHolder;
+import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
+import com.android.launcher3.util.DisplayController.Info;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -40,10 +41,10 @@
 
 public class RotationTouchHelper implements
         SysUINavigationMode.NavigationModeChangeListener,
-        DefaultDisplay.DisplayInfoChangeListener {
+        DisplayInfoChangeListener {
 
     private final OrientationTouchTransformer mOrientationTouchTransformer;
-    private final DefaultDisplay mDefaultDisplay;
+    private final DisplayHolder mDisplayHolder;
     private final SysUINavigationMode mSysUiNavMode;
     private final int mDisplayId;
     private int mDisplayRotation;
@@ -120,12 +121,12 @@
 
     private final Context mContext;
 
-    public RotationTouchHelper(Context context) {
+    public RotationTouchHelper(Context context, DisplayHolder displayHolder) {
         mContext = context;
+        mDisplayHolder = displayHolder;
         Resources resources = mContext.getResources();
         mSysUiNavMode = SysUINavigationMode.INSTANCE.get(context);
-        mDefaultDisplay = DefaultDisplay.INSTANCE.get(context);
-        mDisplayId = mDefaultDisplay.getInfo().id;
+        mDisplayId = mDisplayHolder.getInfo().id;
 
         mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
                 () -> QuickStepContract.getWindowCornerRadius(resources));
@@ -200,7 +201,7 @@
             return;
         }
 
-        mOrientationTouchTransformer.createOrAddTouchRegion(mDefaultDisplay.getInfo());
+        mOrientationTouchTransformer.createOrAddTouchRegion(mDisplayHolder.getInfo());
     }
 
     /**
@@ -222,11 +223,11 @@
 
     @Override
     public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) {
-        mDefaultDisplay.removeChangeListener(this);
-        mDefaultDisplay.addChangeListener(this);
-        onDisplayInfoChanged(mDefaultDisplay.getInfo(), CHANGE_ALL);
+        mDisplayHolder.removeChangeListener(this);
+        mDisplayHolder.addChangeListener(this);
+        onDisplayInfoChanged(mDisplayHolder.getInfo(), CHANGE_ALL);
 
-        mOrientationTouchTransformer.setNavigationMode(newMode, mDefaultDisplay.getInfo(),
+        mOrientationTouchTransformer.setNavigationMode(newMode, mDisplayHolder.getInfo(),
             mContext.getResources());
         if (!mMode.hasGestures && newMode.hasGestures) {
             setupOrientationSwipeHandler();
@@ -242,7 +243,7 @@
     }
 
     @Override
-    public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) {
+    public void onDisplayInfoChanged(Info info, int flags) {
         if (info.id != mDisplayId|| flags == CHANGE_FRAME_DELAY) {
             // ignore displays that aren't running launcher and frame refresh rate changes
             return;
@@ -275,7 +276,7 @@
      * Sets the gestural height.
      */
     void setGesturalHeight(int newGesturalHeight) {
-        mOrientationTouchTransformer.setGesturalHeight(newGesturalHeight, mDefaultDisplay.getInfo(),
+        mOrientationTouchTransformer.setGesturalHeight(newGesturalHeight, mDisplayHolder.getInfo(),
             mContext.getResources());
     }
 
@@ -292,7 +293,7 @@
     }
 
     private void enableMultipleRegions(boolean enable) {
-        mOrientationTouchTransformer.enableMultipleRegions(enable, mDefaultDisplay.getInfo());
+        mOrientationTouchTransformer.enableMultipleRegions(enable, mDisplayHolder.getInfo());
         notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getQuickStepStartingRotation());
         if (enable && !mInOverview && !TestProtocol.sDisableSensorRotation) {
             // Clear any previous state from sensor manager
@@ -355,7 +356,7 @@
      * notifies system UI of the primary rotation the user is interacting with
      */
     private void toggleSecondaryNavBarsForRotation() {
-        mOrientationTouchTransformer.setSingleActiveRegion(mDefaultDisplay.getInfo());
+        mOrientationTouchTransformer.setSingleActiveRegion(mDisplayHolder.getInfo());
         notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
     }
 
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
index 60d8ef6..865b66e 100644
--- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -16,7 +16,7 @@
 package com.android.quickstep.interaction;
 
 import static com.android.launcher3.anim.Interpolators.ACCEL;
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
 import static com.android.quickstep.AbsSwipeUpHandler.MAX_SWIPE_DURATION;
 import static com.android.quickstep.interaction.TutorialController.TutorialType.HOME_NAVIGATION_COMPLETE;
diff --git a/quickstep/src/com/android/quickstep/util/NavBarPosition.java b/quickstep/src/com/android/quickstep/util/NavBarPosition.java
index 0a98e1b..449dba8 100644
--- a/quickstep/src/com/android/quickstep/util/NavBarPosition.java
+++ b/quickstep/src/com/android/quickstep/util/NavBarPosition.java
@@ -19,7 +19,7 @@
 
 import android.view.Surface;
 
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController.Info;
 import com.android.quickstep.SysUINavigationMode;
 
 /**
@@ -30,7 +30,7 @@
     private final SysUINavigationMode.Mode mMode;
     private final int mDisplayRotation;
 
-    public NavBarPosition(SysUINavigationMode.Mode mode, DefaultDisplay.Info info) {
+    public NavBarPosition(SysUINavigationMode.Mode mode, Info info) {
         mMode = mode;
         mDisplayRotation = info.rotation;
     }
diff --git a/quickstep/src/com/android/quickstep/util/SplitScreenBounds.java b/quickstep/src/com/android/quickstep/util/SplitScreenBounds.java
index a770e8e..176478f 100644
--- a/quickstep/src/com/android/quickstep/util/SplitScreenBounds.java
+++ b/quickstep/src/com/android/quickstep/util/SplitScreenBounds.java
@@ -32,7 +32,7 @@
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.R;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.WindowBounds;
 
 import java.util.ArrayList;
@@ -77,7 +77,7 @@
 
         WindowBounds bounds = new WindowBounds(wm.getBounds(),
                 new Rect(insets.left, insets.top, insets.right, insets.bottom));
-        int rotation = DefaultDisplay.INSTANCE.get(context).getInfo().rotation;
+        int rotation = DisplayController.getDefaultDisplay(context).getInfo().rotation;
         int halfDividerSize = context.getResources()
                 .getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
 
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index bd8c53c..cbc3abc 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -17,7 +17,7 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
-import static com.android.launcher3.util.DefaultDisplay.CHANGE_ROTATION;
+import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_ROTATION;
 
 import android.app.ActivityOptions;
 import android.content.ActivityNotFoundException;
@@ -50,9 +50,9 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
-import com.android.launcher3.util.DefaultDisplay;
-import com.android.launcher3.util.DefaultDisplay.DisplayInfoChangeListener;
-import com.android.launcher3.util.DefaultDisplay.Info;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
+import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.TraceHelper;
@@ -84,7 +84,7 @@
 
         mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
                 () -> getPackageManager().isSafeMode());
-        DefaultDisplay.INSTANCE.get(this).addChangeListener(this);
+        DisplayController.getDefaultDisplay(this).addChangeListener(this);
 
         // Update theme
         WallpaperColorInfo.INSTANCE.get(this).addOnChangeListener(this);
@@ -257,7 +257,7 @@
     protected void onDestroy() {
         super.onDestroy();
         WallpaperColorInfo.INSTANCE.get(this).removeOnChangeListener(this);
-        DefaultDisplay.INSTANCE.get(this).removeChangeListener(this);
+        DisplayController.getDefaultDisplay(this).removeChangeListener(this);
     }
 
     public void runOnceOnStart(Runnable action) {
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 49caa93..12ce9f3 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -28,7 +28,8 @@
 import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.icons.IconNormalizer;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.WindowBounds;
 
 public class DeviceProfile {
@@ -38,7 +39,7 @@
 
 
     public final InvariantDeviceProfile inv;
-    private final DefaultDisplay.Info mInfo;
+    private final Info mInfo;
 
     // Device properties
     public final boolean isTablet;
@@ -140,7 +141,7 @@
     public DotRenderer mDotRendererWorkSpace;
     public DotRenderer mDotRendererAllApps;
 
-    DeviceProfile(Context context, InvariantDeviceProfile inv, DefaultDisplay.Info info,
+    DeviceProfile(Context context, InvariantDeviceProfile inv, Info info,
             Point minSize, Point maxSize, int width, int height, boolean isLandscape,
             boolean isMultiWindowMode, boolean transposeLayoutWithOrientation,
             Point windowPosition) {
@@ -606,7 +607,7 @@
      */
     public boolean updateIsSeascape(Context context) {
         if (isVerticalBarLayout()) {
-            boolean isSeascape = DefaultDisplay.INSTANCE.get(context).getInfo().rotation
+            boolean isSeascape = DisplayController.getDefaultDisplay(context).getInfo().rotation
                     == Surface.ROTATION_270;
             if (mIsSeascape != isSeascape) {
                 mIsSeascape = isSeascape;
@@ -638,7 +639,7 @@
         }
     }
 
-    private static Context getContext(Context c, DefaultDisplay.Info info, int orientation) {
+    private static Context getContext(Context c, Info info, int orientation) {
         Configuration config = new Configuration(c.getResources().getConfiguration());
         config.orientation = orientation;
         config.densityDpi = info.metrics.densityDpi;
@@ -662,7 +663,7 @@
     public static class Builder {
         private Context mContext;
         private InvariantDeviceProfile mInv;
-        private DefaultDisplay.Info mInfo;
+        private Info mInfo;
 
         private final Point mWindowPosition = new Point();
         private Point mMinSize, mMaxSize;
@@ -672,7 +673,7 @@
         private boolean mIsMultiWindowMode = false;
         private boolean mTransposeLayoutWithOrientation;
 
-        public Builder(Context context, InvariantDeviceProfile inv, DefaultDisplay.Info info) {
+        public Builder(Context context, InvariantDeviceProfile inv, Info info) {
             mContext = context;
             mInv = inv;
             mInfo = info;
diff --git a/src/com/android/launcher3/FirstFrameAnimatorHelper.java b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
index 6c5bc40..a199a57 100644
--- a/src/com/android/launcher3/FirstFrameAnimatorHelper.java
+++ b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
@@ -15,7 +15,7 @@
  */
 package com.android.launcher3;
 
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index e39e89c..370bd6f 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -48,8 +48,8 @@
 
 import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.util.ConfigMonitor;
-import com.android.launcher3.util.DefaultDisplay;
-import com.android.launcher3.util.DefaultDisplay.Info;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.Themes;
@@ -198,7 +198,7 @@
 
         // Get the display info based on default display and interpolate it to existing display
         DisplayOption defaultDisplayOption = invDistWeightedInterpolate(
-                DefaultDisplay.INSTANCE.get(context).getInfo(),
+                DisplayController.getDefaultDisplay(context).getInfo(),
                 getPredefinedDeviceProfiles(context, gridName));
 
         Info myInfo = new Info(context, display);
@@ -231,7 +231,7 @@
     }
 
     private String initGrid(Context context, String gridName) {
-        DefaultDisplay.Info displayInfo = DefaultDisplay.INSTANCE.get(context).getInfo();
+        Info displayInfo = DisplayController.getDefaultDisplay(context).getInfo();
         ArrayList<DisplayOption> allOptions = getPredefinedDeviceProfiles(context, gridName);
 
         DisplayOption displayOption = invDistWeightedInterpolate(displayInfo, allOptions);
@@ -240,7 +240,7 @@
     }
 
     private void initGrid(
-            Context context, DefaultDisplay.Info displayInfo, DisplayOption displayOption) {
+            Context context, Info displayInfo, DisplayOption displayOption) {
         GridOption closestProfile = displayOption.grid;
         numRows = closestProfile.numRows;
         numColumns = closestProfile.numColumns;
@@ -466,7 +466,7 @@
 
     @VisibleForTesting
     static DisplayOption invDistWeightedInterpolate(
-            DefaultDisplay.Info displayInfo, ArrayList<DisplayOption> points) {
+            Info displayInfo, ArrayList<DisplayOption> points) {
         Point smallestSize = new Point(displayInfo.smallestSize);
         Point largestSize = new Point(displayInfo.largestSize);
 
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index ea0ff8b..dcdfb6e 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -19,7 +19,7 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 
 import android.animation.Animator;
 import android.animation.Animator.AnimatorListener;
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index 6ad43ea..8016b2d 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -16,7 +16,7 @@
 
 package com.android.launcher3.anim;
 
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 
 import android.content.Context;
 import android.graphics.Path;
diff --git a/src/com/android/launcher3/anim/SpringAnimationBuilder.java b/src/com/android/launcher3/anim/SpringAnimationBuilder.java
index a9702b4..bd52158 100644
--- a/src/com/android/launcher3/anim/SpringAnimationBuilder.java
+++ b/src/com/android/launcher3/anim/SpringAnimationBuilder.java
@@ -25,7 +25,7 @@
 import androidx.annotation.FloatRange;
 import androidx.dynamicanimation.animation.SpringForce;
 
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
 
 /**
  * Utility class to build an object animator which follows the same path as a spring animation for
@@ -134,7 +134,7 @@
     }
 
     public SpringAnimationBuilder computeParams() {
-        int singleFrameMs = DefaultDisplay.getSingleFrameMs(mContext);
+        int singleFrameMs = DisplayController.getSingleFrameMs(mContext);
         double naturalFreq = Math.sqrt(mStiffness);
         double dampedFreq = naturalFreq * Math.sqrt(1 - mDampingRatio * mDampingRatio);
 
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 02ca926..8ee5a6e 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -26,7 +26,7 @@
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS;
 import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.PLAY_NON_ATOMIC;
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
diff --git a/src/com/android/launcher3/util/ConfigMonitor.java b/src/com/android/launcher3/util/ConfigMonitor.java
index 0f81520..b3b69f6 100644
--- a/src/com/android/launcher3/util/ConfigMonitor.java
+++ b/src/com/android/launcher3/util/ConfigMonitor.java
@@ -26,14 +26,16 @@
 import android.graphics.Point;
 import android.util.Log;
 
+import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
+import com.android.launcher3.util.DisplayController.Info;
+
 import java.util.function.Consumer;
 
 /**
  * {@link BroadcastReceiver} which watches configuration changes and
  * notifies the callback in case changes which affect the device profile occur.
  */
-public class ConfigMonitor extends BroadcastReceiver implements
-        DefaultDisplay.DisplayInfoChangeListener {
+public class ConfigMonitor extends BroadcastReceiver implements DisplayInfoChangeListener {
 
     private static final String TAG = "ConfigMonitor";
 
@@ -57,9 +59,9 @@
         mFontScale = config.fontScale;
         mDensity = config.densityDpi;
 
-        DefaultDisplay display = DefaultDisplay.INSTANCE.get(context);
+        DisplayController.DisplayHolder display = DisplayController.getDefaultDisplay(context);
         display.addChangeListener(this);
-        DefaultDisplay.Info displayInfo = display.getInfo();
+        Info displayInfo = display.getInfo();
         mDisplayId = displayInfo.id;
 
         mRealSize = new Point(displayInfo.realSize);
@@ -82,7 +84,7 @@
     }
 
     @Override
-    public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) {
+    public void onDisplayInfoChanged(Info info, int flags) {
         if (info.id != mDisplayId) {
             return;
         }
@@ -113,8 +115,7 @@
     public void unregister() {
         try {
             mContext.unregisterReceiver(this);
-            DefaultDisplay display = DefaultDisplay.INSTANCE.get(mContext);
-            display.removeChangeListener(this);
+            DisplayController.getDefaultDisplay(mContext).removeChangeListener(this);
         } catch (Exception e) {
             Log.e(TAG, "Failed to unregister config monitor", e);
         }
diff --git a/src/com/android/launcher3/util/DefaultDisplay.java b/src/com/android/launcher3/util/DefaultDisplay.java
deleted file mode 100644
index 35788a5..0000000
--- a/src/com/android/launcher3/util/DefaultDisplay.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright (C) 2019 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;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
-import android.content.Context;
-import android.graphics.Point;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManager.DisplayListener;
-import android.os.Handler;
-import android.os.Message;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.Display;
-
-import androidx.annotation.VisibleForTesting;
-
-import java.util.ArrayList;
-
-/**
- * Utility class to cache properties of default display to avoid a system RPC on every call.
- */
-public class DefaultDisplay implements DisplayListener {
-
-    public static final MainThreadInitializedObject<DefaultDisplay> INSTANCE =
-            new MainThreadInitializedObject<>(DefaultDisplay::new);
-
-    private static final String TAG = "DefaultDisplay";
-
-    public static final int CHANGE_SIZE = 1 << 0;
-    public static final int CHANGE_ROTATION = 1 << 1;
-    public static final int CHANGE_FRAME_DELAY = 1 << 2;
-
-    public static final int CHANGE_ALL = CHANGE_SIZE | CHANGE_ROTATION | CHANGE_FRAME_DELAY;
-
-    private final Context mDisplayContext;
-    private final int mId;
-    private final ArrayList<DisplayInfoChangeListener> mListeners = new ArrayList<>();
-    private final Handler mChangeHandler;
-    private Info mInfo;
-
-    private DefaultDisplay(Context context) {
-        DisplayManager dm = context.getSystemService(DisplayManager.class);
-        // Use application context to create display context so that it can have its own Resources.
-        mDisplayContext = context.getApplicationContext().createDisplayContext(
-                dm.getDisplay(DEFAULT_DISPLAY));
-        // Note that the Display object must be obtained from DisplayManager which is associated to
-        // the display context, so the Display is isolated from Activity and Application to provide
-        // the actual state of device that excludes the additional adjustment and override.
-        mInfo = new Info(mDisplayContext);
-        mId = mInfo.id;
-        mChangeHandler = new Handler(this::onChange);
-
-        dm.registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler());
-    }
-
-    @Override
-    public final void onDisplayAdded(int displayId) {  }
-
-    @Override
-    public final void onDisplayRemoved(int displayId) { }
-
-    @Override
-    public final void onDisplayChanged(int displayId) {
-        if (displayId != mId) {
-            return;
-        }
-
-        Info oldInfo = mInfo;
-        Info info = new Info(mDisplayContext);
-
-        int change = 0;
-        if (info.hasDifferentSize(oldInfo)) {
-            change |= CHANGE_SIZE;
-        }
-        if (oldInfo.rotation != info.rotation) {
-            change |= CHANGE_ROTATION;
-        }
-        if (info.singleFrameMs != oldInfo.singleFrameMs) {
-            change |= CHANGE_FRAME_DELAY;
-        }
-
-        if (change != 0) {
-            mInfo = info;
-            mChangeHandler.sendEmptyMessage(change);
-        }
-    }
-
-    public static int getSingleFrameMs(Context context) {
-        return INSTANCE.get(context).getInfo().singleFrameMs;
-    }
-
-    public Info getInfo() {
-        return mInfo;
-    }
-
-    public void addChangeListener(DisplayInfoChangeListener listener) {
-        mListeners.add(listener);
-    }
-
-    public void removeChangeListener(DisplayInfoChangeListener listener) {
-        mListeners.remove(listener);
-    }
-
-    private boolean onChange(Message msg) {
-        for (int i = mListeners.size() - 1; i >= 0; i--) {
-            mListeners.get(i).onDisplayInfoChanged(mInfo, msg.what);
-        }
-        return true;
-    }
-
-    public static class Info {
-
-        public final int id;
-        public final int rotation;
-        public final int singleFrameMs;
-
-        public final Point realSize;
-        public final Point smallestSize;
-        public final Point largestSize;
-
-        public final DisplayMetrics metrics;
-
-        @VisibleForTesting
-        public Info(int id, int rotation, int singleFrameMs, Point realSize, Point smallestSize,
-                Point largestSize, DisplayMetrics metrics) {
-            this.id = id;
-            this.rotation = rotation;
-            this.singleFrameMs = singleFrameMs;
-            this.realSize = realSize;
-            this.smallestSize = smallestSize;
-            this.largestSize = largestSize;
-            this.metrics = metrics;
-        }
-
-        private Info(Context context) {
-            this(context, context.getSystemService(DisplayManager.class)
-                    .getDisplay(DEFAULT_DISPLAY));
-        }
-
-        public Info(Context context, Display display) {
-            id = display.getDisplayId();
-            rotation = display.getRotation();
-
-            float refreshRate = display.getRefreshRate();
-            singleFrameMs = refreshRate > 0 ? (int) (1000 / refreshRate) : 16;
-
-            realSize = new Point();
-            smallestSize = new Point();
-            largestSize = new Point();
-            display.getRealSize(realSize);
-            display.getCurrentSizeRange(smallestSize, largestSize);
-
-            metrics = context.getResources().getDisplayMetrics();
-        }
-
-        private boolean hasDifferentSize(Info info) {
-            if (!realSize.equals(info.realSize)
-                    && !realSize.equals(info.realSize.y, info.realSize.x)) {
-                Log.d(TAG, String.format("Display size changed from %s to %s",
-                        info.realSize, realSize));
-                return true;
-            }
-
-            if (!smallestSize.equals(info.smallestSize) || !largestSize.equals(info.largestSize)) {
-                Log.d(TAG, String.format("Available size changed from [%s, %s] to [%s, %s]",
-                        smallestSize, largestSize, info.smallestSize, info.largestSize));
-                return true;
-            }
-
-            return false;
-        }
-    }
-
-    /**
-     * Interface for listening for display changes
-     */
-    public interface DisplayInfoChangeListener {
-
-        void onDisplayInfoChanged(Info info, int flags);
-    }
-}
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
new file mode 100644
index 0000000..e5c8441
--- /dev/null
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2019 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;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Display;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class to cache properties of default display to avoid a system RPC on every call.
+ */
+public class DisplayController implements DisplayListener {
+
+    private static final String TAG = "DisplayController";
+
+    public static final MainThreadInitializedObject<DisplayController> INSTANCE =
+            new MainThreadInitializedObject<>(DisplayController::new);
+
+    private final SparseArray<DisplayHolder> mOtherDisplays = new SparseArray<>(0);
+    // We store the default display separately, to avoid null checks for primary use case.
+    private final DisplayHolder mDefaultDisplay;
+
+    private final ArrayList<DisplayListChangeListener> mListListeners = new ArrayList<>();
+
+    private DisplayController(Context context) {
+        mDefaultDisplay = new DisplayHolder(context, DEFAULT_DISPLAY);
+
+        DisplayManager dm = context.getSystemService(DisplayManager.class);
+        dm.registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler());
+    }
+
+    @Override
+    public final void onDisplayAdded(int displayId) {
+        DisplayHolder holder = new DisplayHolder(mDefaultDisplay.mDisplayContext, displayId);
+        synchronized (mOtherDisplays) {
+            mOtherDisplays.put(displayId, holder);
+        }
+        MAIN_EXECUTOR.execute(() -> mListListeners.forEach(l-> l.onDisplayAdded(holder)));
+    }
+
+    @Override
+    public final void onDisplayRemoved(int displayId) {
+        synchronized (mOtherDisplays) {
+            mOtherDisplays.remove(displayId);
+        }
+        MAIN_EXECUTOR.execute(() -> mListListeners.forEach(l-> l.onDisplayRemoved(displayId)));
+    }
+
+    /**
+     * Returns the holder corresponding to the given display
+     */
+    public DisplayHolder getHolder(int displayId) {
+        if (displayId == mDefaultDisplay.mId) {
+            return mDefaultDisplay;
+        } else {
+            synchronized (mOtherDisplays) {
+                return mOtherDisplays.get(displayId);
+            }
+        }
+    }
+
+    /**
+     * Adds a listener for display list changes
+     */
+    public void addListChangeListener(DisplayListChangeListener listener) {
+        mListListeners.add(listener);
+    }
+
+    /**
+     * Removes a previously added display list change listener
+     */
+    public void removeListChangeListener(DisplayListChangeListener listener) {
+        mListListeners.remove(listener);
+    }
+
+    @Override
+    public final void onDisplayChanged(int displayId) {
+        DisplayHolder holder = getHolder(displayId);
+        if (holder != null) {
+            holder.handleOnChange();
+        }
+    }
+
+    public static int getSingleFrameMs(Context context) {
+        return getDefaultDisplay(context).getInfo().singleFrameMs;
+    }
+
+    public static DisplayHolder getDefaultDisplay(Context context) {
+        return INSTANCE.get(context).mDefaultDisplay;
+    }
+
+    /**
+     * A listener to receiving addition or removal of new displays
+     */
+    public interface DisplayListChangeListener {
+
+        /**
+         * Called when a new display is added
+         */
+        void onDisplayAdded(DisplayHolder holder);
+
+        /**
+         * Called when a previously added display is removed
+         */
+        void onDisplayRemoved(int displayId);
+    }
+
+    /**
+     * Interface for listening for display changes
+     */
+    public interface DisplayInfoChangeListener {
+
+        void onDisplayInfoChanged(Info info, int flags);
+    }
+
+    public static class DisplayHolder {
+
+        public static final int CHANGE_SIZE = 1 << 0;
+        public static final int CHANGE_ROTATION = 1 << 1;
+        public static final int CHANGE_FRAME_DELAY = 1 << 2;
+
+        public static final int CHANGE_ALL = CHANGE_SIZE | CHANGE_ROTATION | CHANGE_FRAME_DELAY;
+
+        final Context mDisplayContext;
+        final int mId;
+        private final ArrayList<DisplayInfoChangeListener> mListeners = new ArrayList<>();
+        private DisplayController.Info mInfo;
+
+        public DisplayHolder(Context context, int id) {
+            DisplayManager dm = context.getSystemService(DisplayManager.class);
+            // Use application context to create display context so that it can have its own
+            // Resources.
+            mDisplayContext = context.getApplicationContext()
+                    .createDisplayContext(dm.getDisplay(id));
+            // Note that the Display object must be obtained from DisplayManager which is
+            // associated to the display context, so the Display is isolated from Activity and
+            // Application to provide the actual state of device that excludes the additional
+            // adjustment and override.
+            mInfo = new DisplayController.Info(mDisplayContext);
+            mId = mInfo.id;
+        }
+
+        public void addChangeListener(DisplayInfoChangeListener listener) {
+            mListeners.add(listener);
+        }
+
+        public void removeChangeListener(DisplayInfoChangeListener listener) {
+            mListeners.remove(listener);
+        }
+
+        public DisplayController.Info getInfo() {
+            return mInfo;
+        }
+
+        protected void handleOnChange() {
+            Info oldInfo = mInfo;
+            Info info = new Info(mDisplayContext);
+
+            int change = 0;
+            if (info.hasDifferentSize(oldInfo)) {
+                change |= CHANGE_SIZE;
+            }
+            if (oldInfo.rotation != info.rotation) {
+                change |= CHANGE_ROTATION;
+            }
+            if (info.singleFrameMs != oldInfo.singleFrameMs) {
+                change |= CHANGE_FRAME_DELAY;
+            }
+
+            if (change != 0) {
+                mInfo = info;
+                final int flags = change;
+                MAIN_EXECUTOR.execute(() -> notifyChange(flags));
+            }
+        }
+
+        private void notifyChange(int flags) {
+            for (int i = mListeners.size() - 1; i >= 0; i--) {
+                mListeners.get(i).onDisplayInfoChanged(mInfo, flags);
+            }
+        }
+
+    }
+
+    public static class Info {
+
+        public final int id;
+        public final int rotation;
+        public final int singleFrameMs;
+
+        public final Point realSize;
+        public final Point smallestSize;
+        public final Point largestSize;
+
+        public final DisplayMetrics metrics;
+
+        @VisibleForTesting
+        public Info(int id, int rotation, int singleFrameMs, Point realSize, Point smallestSize,
+                Point largestSize, DisplayMetrics metrics) {
+            this.id = id;
+            this.rotation = rotation;
+            this.singleFrameMs = singleFrameMs;
+            this.realSize = realSize;
+            this.smallestSize = smallestSize;
+            this.largestSize = largestSize;
+            this.metrics = metrics;
+        }
+
+        private Info(Context context) {
+            this(context, context.getSystemService(DisplayManager.class)
+                    .getDisplay(DEFAULT_DISPLAY));
+        }
+
+        public Info(Context context, Display display) {
+            id = display.getDisplayId();
+            rotation = display.getRotation();
+
+            float refreshRate = display.getRefreshRate();
+            singleFrameMs = refreshRate > 0 ? (int) (1000 / refreshRate) : 16;
+
+            realSize = new Point();
+            smallestSize = new Point();
+            largestSize = new Point();
+            display.getRealSize(realSize);
+            display.getCurrentSizeRange(smallestSize, largestSize);
+
+            metrics = context.getResources().getDisplayMetrics();
+        }
+
+        private boolean hasDifferentSize(Info info) {
+            if (!realSize.equals(info.realSize)
+                    && !realSize.equals(info.realSize.y, info.realSize.x)) {
+                Log.d(TAG, String.format("Display size changed from %s to %s",
+                        info.realSize, realSize));
+                return true;
+            }
+
+            if (!smallestSize.equals(info.smallestSize) || !largestSize.equals(info.largestSize)) {
+                Log.d(TAG, String.format("Available size changed from [%s, %s] to [%s, %s]",
+                        smallestSize, largestSize, info.smallestSize, info.largestSize));
+                return true;
+            }
+
+            return false;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 357eeb8..2be827b 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -20,7 +20,7 @@
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
 
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 
 import android.annotation.TargetApi;
 import android.app.WallpaperInfo;
diff --git a/src/com/android/launcher3/views/FloatingSurfaceView.java b/src/com/android/launcher3/views/FloatingSurfaceView.java
index 040619e..9582232 100644
--- a/src/com/android/launcher3/views/FloatingSurfaceView.java
+++ b/src/com/android/launcher3/views/FloatingSurfaceView.java
@@ -40,7 +40,7 @@
 import com.android.launcher3.Insettable;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.Executors;
 
 /**
@@ -97,7 +97,7 @@
 
         // Remove after some time, to avoid flickering
         Executors.MAIN_EXECUTOR.getHandler().postDelayed(mRemoveViewRunnable,
-                DefaultDisplay.INSTANCE.get(mLauncher).getInfo().singleFrameMs);
+                DisplayController.getDefaultDisplay(mLauncher).getInfo().singleFrameMs);
     }
 
     private void removeViewFromParent() {