Merging ub-launcher3-qt-r1-dev, build 5758528

Test: Manual

Bug:128940249 P2 Investigate performance options for 90Hz when swiping to -1
Bug:134074554 P2 Enable test testPressHomeOnAllAppsContextMenu()
Bug:134181439 P3 Missing drag down gesture to open shade while in landscape
Bug:134981174 P1 [Google] Search bar disappears from home screen
Bug:136800511 P1 Brightness shifts when swipe up + hold to recents
Bug:137137090 P2 Improve swipe down noti shade (landscape, logging) and other swipe up home gesture logging
Bug:138025252 P4 Pixel Launcher keeps stopping when changing system language
Bug:138144992 P4 NullPointer in TouchInteractionService.dump
Bug:138227387 P1 Remove 2-button option where not appropriate
Bug:138245847 P4 On a third party wallpaper, long press not highlighting home screen folder name.
Bug:138251824 P1 Flake: want to switch from background to overview; Swipe failed to receive an event for the swipe end
Bug:138401738 P1 Master: java.lang.AssertionError: Navigation mode didn't change to TWO_BUTTON
Change-Id: Ib66a27de5515957a42d0791b76dfca8ed7b248d7
diff --git a/SecondaryDisplayLauncher/res/values-zh-rTW/strings.xml b/SecondaryDisplayLauncher/res/values-zh-rTW/strings.xml
index bf76f29..c02fe2c 100644
--- a/SecondaryDisplayLauncher/res/values-zh-rTW/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-zh-rTW/strings.xml
@@ -21,5 +21,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="couldnt_launch" msgid="7873588052226763866">"無法啟動活動"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"新增應用程式捷徑"</string>
-    <string name="set_wallpaper" msgid="6475195450505435904">"設定桌布"</string>
+    <string name="set_wallpaper" msgid="6475195450505435904">"套用桌布"</string>
 </resources>
diff --git a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index fe159b5..92900f2 100644
--- a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -25,6 +25,7 @@
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.app.ActivityOptions;
+import android.content.Context;
 import android.os.Handler;
 import android.util.Log;
 
@@ -151,7 +152,7 @@
     }
 
     @Override
-    public ActivityOptions toActivityOptions(Handler handler, long duration) {
+    public ActivityOptions toActivityOptions(Handler handler, long duration, Context context) {
         LauncherAnimationRunner runner = new LauncherAnimationRunner(handler,
                 false /* startAtFrontOfQueue */) {
 
@@ -165,7 +166,7 @@
                     );
                     return;
                 }
-                result.setAnimation(createWindowAnimation(targetCompats));
+                result.setAnimation(createWindowAnimation(targetCompats), context);
             }
         };
         return ActivityOptionsCompat.makeRemoteAnimation(
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
index 820a8df..596bc4f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
@@ -165,8 +165,7 @@
         }
 
         if (FeatureFlags.PULL_DOWN_STATUS_BAR
-                && !launcher.getDeviceProfile().isMultiWindowMode
-                && !launcher.getDeviceProfile().isVerticalBarLayout()) {
+                && !launcher.getDeviceProfile().isMultiWindowMode) {
             list.add(new StatusBarTouchController(launcher));
         }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 5ee08c1..50cfac8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -19,6 +19,7 @@
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.util.LayoutUtils;
@@ -68,8 +69,8 @@
         if (taskCount == 0) {
             return super.getOverviewScaleAndTranslation(launcher);
         }
-        TaskView dummyTask = recentsView.getTaskViewAt(Math.max(taskCount - 1,
-                recentsView.getCurrentPage()));
+        TaskView dummyTask = recentsView.getTaskViewAt(Utilities.boundToRange(
+                recentsView.getCurrentPage(), 0, taskCount - 1));
         return recentsView.getTempClipAnimationHelper().updateForFullscreenOverview(dummyTask)
                 .getScaleAndTranslation();
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 8e32bb3..00e4f58 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -16,10 +16,10 @@
 package com.android.launcher3.uioverrides.touchcontrollers;
 
 import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE;
-import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -266,8 +266,8 @@
             animationDuration *= LauncherAnimUtils.blockedFlingDurationFactor(velocity);
         }
 
-        float nextFrameProgress = Utilities.boundToRange(
-                progress + velocity * SINGLE_FRAME_MS / Math.abs(mEndDisplacement), 0f, 1f);
+        float nextFrameProgress = Utilities.boundToRange(progress
+                + velocity * getSingleFrameMs(mActivity) / Math.abs(mEndDisplacement), 0f, 1f);
 
         mCurrentAnimation.setEndAction(() -> onCurrentAnimationEnd(goingToEnd, logAction));
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
index c43155b..8c5a788 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
@@ -80,7 +80,9 @@
 
     @Override
     public void onAssistantVisibilityChanged(float visibility) {
-        // TODO:
+        // This class becomes active when the screen is locked.
+        // Rather than having it handle assistant visibility changes, the assistant visibility is
+        // set to zero prior to this class becoming active.
     }
 
     @NonNull
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
index 60e7b12..f08ae4a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
@@ -155,7 +155,7 @@
                         mFallbackRecentsView.resetViewUI();
                     }
                 });
-                result.setAnimation(anim);
+                result.setAnimation(anim, RecentsActivity.this);
             }
         };
         return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 6f36b05..5afc73c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -335,6 +335,9 @@
         if (mInputEventReceiver != null) {
             mInputEventReceiver.dispose();
             mInputEventReceiver = null;
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "disposeEventHandlers");
+            }
         }
         if (mInputMonitorCompat != null) {
             mInputMonitorCompat.dispose();
@@ -723,7 +726,7 @@
             return new OverviewInputConsumer(activity, mInputMonitorCompat,
                     false /* startingInActivityBounds */);
         } else {
-            return new OverviewWithoutFocusInputConsumer(this, mInputMonitorCompat,
+            return new OverviewWithoutFocusInputConsumer(activity, mInputMonitorCompat,
                     disableHorizontalSwipe(event));
         }
     }
@@ -816,8 +819,9 @@
             pw.println("  assistantAvailable=" + mAssistantAvailable);
             pw.println("  assistantDisabled="
                     + QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags));
-            pw.println("  resumed="
-                    + mOverviewComponentObserver.getActivityControlHelper().isResumed());
+            boolean resumed = mOverviewComponentObserver != null
+                    && mOverviewComponentObserver.getActivityControlHelper().isResumed();
+            pw.println("  resumed=" + resumed);
             pw.println("  useSharedState=" + mConsumer.useSharedSwipeState());
             if (mConsumer.useSharedSwipeState()) {
                 sSwipeSharedState.dump("    ", pw);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
index cc9719b..363e4cc 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -17,12 +17,12 @@
 
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
-import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
 import static com.android.launcher3.util.RaceConditionTracker.ENTER;
 import static com.android.launcher3.util.RaceConditionTracker.EXIT;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
@@ -780,14 +780,14 @@
             interpolator = endTarget == RECENTS ? OVERSHOOT_1_2 : DEACCEL;
         } else {
             startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y
-                    * SINGLE_FRAME_MS / mTransitionDragLength, 0, mDragLengthFactor);
+                    * getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor);
             float minFlingVelocity = mContext.getResources()
                     .getDimension(R.dimen.quickstep_fling_min_velocity);
             if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) {
                 if (endTarget == RECENTS && mMode != Mode.NO_BUTTON) {
                     Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams(
                             startShift, endShift, endShift, endVelocity / 1000,
-                            mTransitionDragLength);
+                            mTransitionDragLength, mContext);
                     endShift = overshoot.end;
                     interpolator = overshoot.interpolator;
                     duration = Utilities.boundToRange(overshoot.duration, MIN_OVERSHOOT_DURATION,
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index 425b8b6..05cbb78 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -30,7 +30,13 @@
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
 
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.logging.StatsLogUtils;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.quickstep.OverviewCallbacks;
 import com.android.quickstep.util.NavBarPosition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -131,12 +137,14 @@
                 ? -velocityX : (mNavBarPosition.isLeftEdge() ? velocityX : -velocityY);
 
         final boolean triggerQuickstep;
+        int touch = Touch.FLING;
         if (Math.abs(velocity) >= ViewConfiguration.get(mContext).getScaledMinimumFlingVelocity()) {
             triggerQuickstep = velocity > 0;
         } else {
             float displacementX = mDisableHorizontalSwipe ? 0 : (ev.getX() - mDownPos.x);
             float displacementY = ev.getY() - mDownPos.y;
             triggerQuickstep = squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop;
+            touch = Touch.SWIPE;
         }
 
         if (triggerQuickstep) {
@@ -144,6 +152,13 @@
             ActivityManagerWrapper.getInstance()
                     .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
             TOUCH_INTERACTION_LOG.addLog("startQuickstep");
+            BaseActivity activity = BaseDraggingActivity.fromContext(mContext);
+            int pageIndex = -1; // This number doesn't reflect workspace page index.
+                                // It only indicates that launcher client screen was shown.
+            int containerType = StatsLogUtils.getContainerTypeFromState(activity.getCurrentState());
+            activity.getUserEventDispatcher().logActionOnContainer(
+                    touch, Direction.UP, containerType, pageIndex);
+            activity.getUserEventDispatcher().setPreviousHomeGesture(true);
         } else {
             // ignore
         }
diff --git a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
index 78f6ffa..a8e2956 100644
--- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
@@ -15,8 +15,8 @@
  */
 package com.android.launcher3;
 
-import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
 import static com.android.launcher3.Utilities.postAsyncCallback;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
 import static com.android.systemui.shared.recents.utilities.Utilities
         .postAtFrontOfQueueAsynchronously;
 
@@ -24,6 +24,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.annotation.TargetApi;
+import android.content.Context;
 import android.os.Build;
 import android.os.Handler;
 
@@ -66,7 +67,7 @@
 
     /**
      * Called on the UI thread when the animation targets are received. The implementation must
-     * call {@link AnimationResult#setAnimation(AnimatorSet)} with the target animation to be run.
+     * call {@link AnimationResult#setAnimation} with the target animation to be run.
      */
     @UiThread
     public abstract void onCreateAnimation(
@@ -110,7 +111,7 @@
         }
 
         @UiThread
-        public void setAnimation(AnimatorSet animation) {
+        public void setAnimation(AnimatorSet animation, Context context) {
             if (mInitialized) {
                 throw new IllegalStateException("Animation already initialized");
             }
@@ -134,7 +135,7 @@
 
                 // Because t=0 has the app icon in its original spot, we can skip the
                 // first frame and have the same movement one frame earlier.
-                mAnimator.setCurrentPlayTime(SINGLE_FRAME_MS);
+                mAnimator.setCurrentPlayTime(getSingleFrameMs(context));
             }
         }
     }
diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java
index c5c5323..b9ce1ce 100644
--- a/quickstep/src/com/android/launcher3/LauncherInitListener.java
+++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java
@@ -85,7 +85,7 @@
 
         register();
 
-        Bundle options = animProvider.toActivityOptions(handler, duration).toBundle();
+        Bundle options = animProvider.toActivityOptions(handler, duration, context).toBundle();
         context.startActivity(addToIntent(new Intent((intent))), options);
     }
 }
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index b60a017..991408c 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -219,7 +219,7 @@
                         anim.addListener(mForceInvisibleListener);
                     }
 
-                    result.setAnimation(anim);
+                    result.setAnimation(anim, mLauncher);
                 }
             };
 
@@ -822,7 +822,7 @@
             }
 
             mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
-            result.setAnimation(anim);
+            result.setAnimation(anim, mLauncher);
         }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
index f5ba372..11a8043 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
@@ -33,6 +33,9 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.RecentsModel;
 import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -132,8 +135,12 @@
 
     @Override
     public final boolean onControllerTouchEvent(MotionEvent ev) {
-        if (ev.getAction() == ACTION_UP || ev.getAction() == ACTION_CANCEL) {
+        int action = ev.getAction();
+        if (action == ACTION_UP || action == ACTION_CANCEL) {
             dispatchTouchEvent(ev);
+            mLauncher.getUserEventDispatcher().logActionOnContainer(action == ACTION_UP ?
+                    Touch.FLING : Touch.SWIPE, Direction.DOWN, ContainerType.WORKSPACE,
+                    mLauncher.getWorkspace().getCurrentPage());
             setWindowSlippery(false);
             return true;
         }
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 4a2ed3a..88a4eb6 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -116,6 +116,14 @@
                 .getHomeActivities(new ArrayList<>());
 
         mIsDefaultHome = Objects.equals(mMyHomeIntent.getComponent(), defaultHome);
+
+        // Set assistant visibility to 0 from launcher's perspective, ensures any elements that
+        // launcher made invisible become visible again before the new activity control helper
+        // becomes active.
+        if (mActivityControlHelper != null) {
+            mActivityControlHelper.onAssistantVisibilityChanged(0.f);
+        }
+
         if ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
                 && (defaultHome == null || mIsDefaultHome)) {
             // User default home is same as out home app. Use Overview integrated in Launcher.
@@ -131,6 +139,7 @@
             }
         } else {
             // The default home app is a different launcher. Use the fallback Overview instead.
+
             mActivityControlHelper = new FallbackActivityControllerHelper();
             mIsHomeAndOverviewSame = false;
             mOverviewIntent = mFallbackIntent;
diff --git a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
index 0822e61..f9d2f11 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
@@ -68,7 +68,7 @@
             Context context, Handler handler, long duration) {
         register();
 
-        Bundle options = animProvider.toActivityOptions(handler, duration).toBundle();
+        Bundle options = animProvider.toActivityOptions(handler, duration, context).toBundle();
         context.startActivity(intent, options);
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
index a7e6d74..4503a43 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
@@ -17,6 +17,7 @@
 
 import android.animation.AnimatorSet;
 import android.app.ActivityOptions;
+import android.content.Context;
 import android.os.Handler;
 
 import com.android.launcher3.LauncherAnimationRunner;
@@ -32,14 +33,14 @@
 
     AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targets);
 
-    default ActivityOptions toActivityOptions(Handler handler, long duration) {
+    default ActivityOptions toActivityOptions(Handler handler, long duration, Context context) {
         LauncherAnimationRunner runner = new LauncherAnimationRunner(handler,
                 false /* startAtFrontOfQueue */) {
 
             @Override
             public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
                     AnimationResult result) {
-                result.setAnimation(createWindowAnimation(targetCompats));
+                result.setAnimation(createWindowAnimation(targetCompats), context);
             }
         };
         return ActivityOptionsCompat.makeRemoteAnimation(
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
index 63c8023..5a2f5c1 100644
--- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
+++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
@@ -74,6 +74,9 @@
     private int mMidAlpha;
     private float mMidProgress;
 
+    // The progress at which the drag handle starts moving up with the shelf.
+    private float mDragHandleProgress;
+
     private Interpolator mBeforeMidProgressColorInterpolator = ACCEL;
     private Interpolator mAfterMidProgressColorInterpolator = ACCEL;
 
@@ -150,15 +153,16 @@
 
             if ((OVERVIEW.getVisibleElements(mLauncher) & ALL_APPS_HEADER_EXTRA) == 0) {
                 mMidProgress = 1;
+                mDragHandleProgress = 1;
                 mMidAlpha = 0;
             } else {
                 mMidAlpha = Themes.getAttrInteger(getContext(), R.attr.allAppsInterimScrimAlpha);
+                mMidProgress =  OVERVIEW.getVerticalProgress(mLauncher);
                 Rect hotseatPadding = dp.getHotseatLayoutPadding();
                 int hotseatSize = dp.hotseatBarSizePx + dp.getInsets().bottom
                         - hotseatPadding.bottom - hotseatPadding.top;
-                float arrowTop = Math.min(hotseatSize, OverviewState.getDefaultSwipeHeight(dp));
-                mMidProgress =  1 - (arrowTop / mShiftRange);
-
+                float dragHandleTop = Math.min(hotseatSize, OverviewState.getDefaultSwipeHeight(dp));
+                mDragHandleProgress =  1 - (dragHandleTop / mShiftRange);
             }
             mTopOffset = dp.getInsets().top - mShelfOffset;
             mShelfTopAtThreshold = mShiftRange * SCRIM_CATCHUP_THRESHOLD + mTopOffset;
@@ -199,8 +203,6 @@
                     mProgress, mMidProgress, 1, mMidAlpha, 0, mBeforeMidProgressColorInterpolator));
             mShelfColor = setColorAlphaBound(mEndScrim, alpha);
         } else {
-            mDragHandleOffset += mShiftRange * (mMidProgress - mProgress);
-
             // Note that these ranges and interpolators are inverted because progress goes 1 to 0.
             int alpha = Math.round(
                     Utilities.mapToRange(mProgress, (float) 0, mMidProgress, (float) mEndAlpha,
@@ -212,6 +214,10 @@
                             (float) 0, LINEAR));
             mRemainingScreenColor = setColorAlphaBound(mScrimColor, remainingScrimAlpha);
         }
+
+        if (mProgress < mDragHandleProgress) {
+            mDragHandleOffset += mShiftRange * (mDragHandleProgress - mProgress);
+        }
     }
 
     @Override
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index b6cd1be..f27f400 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -27,6 +27,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_GESTURAL_OVERLAY;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.util.Log;
 
 import androidx.test.uiautomator.UiDevice;
@@ -102,36 +103,55 @@
                         if (mode == THREE_BUTTON || mode == ALL) {
                             evaluateWithThreeButtons();
                         }
+                    } catch (Exception e) {
+                        Log.e(TAG, "Exception", e);
+                        throw e;
                     } finally {
-                        setActiveOverlay(prevOverlayPkg, originalMode);
+                        Assert.assertTrue(setActiveOverlay(prevOverlayPkg, originalMode));
                     }
                     mLauncher.disableDebugTracing();
                 }
 
-                public void evaluateWithoutChangingSetting(Statement base) throws Throwable {
-                    base.evaluate();
-                }
-
                 private void evaluateWithThreeButtons() throws Throwable {
-                    setActiveOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY,
-                            LauncherInstrumentation.NavigationModel.THREE_BUTTON);
-                    evaluateWithoutChangingSetting(base);
+                    if (setActiveOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY,
+                            LauncherInstrumentation.NavigationModel.THREE_BUTTON)) {
+                        base.evaluate();
+                    }
                 }
 
                 private void evaluateWithTwoButtons() throws Throwable {
-                    setActiveOverlay(NAV_BAR_MODE_2BUTTON_OVERLAY,
-                            LauncherInstrumentation.NavigationModel.TWO_BUTTON);
-                    base.evaluate();
+                    if (setActiveOverlay(NAV_BAR_MODE_2BUTTON_OVERLAY,
+                            LauncherInstrumentation.NavigationModel.TWO_BUTTON)) {
+                        base.evaluate();
+                    }
                 }
 
                 private void evaluateWithZeroButtons() throws Throwable {
-                    setActiveOverlay(NAV_BAR_MODE_GESTURAL_OVERLAY,
-                            LauncherInstrumentation.NavigationModel.ZERO_BUTTON);
-                    base.evaluate();
+                    if (setActiveOverlay(NAV_BAR_MODE_GESTURAL_OVERLAY,
+                            LauncherInstrumentation.NavigationModel.ZERO_BUTTON)) {
+                        base.evaluate();
+                    }
                 }
 
-                private void setActiveOverlay(String overlayPackage,
+                private boolean packageExists(String packageName) {
+                    try {
+                        PackageManager pm = getInstrumentation().getContext().getPackageManager();
+                        if (pm.getApplicationInfo(packageName, 0 /* flags */) == null) {
+                            return false;
+                        }
+                    } catch (PackageManager.NameNotFoundException e) {
+                        return false;
+                    }
+                    return true;
+                }
+
+                private boolean setActiveOverlay(String overlayPackage,
                         LauncherInstrumentation.NavigationModel expectedMode) throws Exception {
+                    if (!packageExists(overlayPackage)) {
+                        Log.d(TAG, "setActiveOverlay: " + overlayPackage + " pkg does not exist");
+                        return false;
+                    }
+
                     setOverlayPackageEnabled(NAV_BAR_MODE_3BUTTON_OVERLAY,
                             overlayPackage == NAV_BAR_MODE_3BUTTON_OVERLAY);
                     setOverlayPackageEnabled(NAV_BAR_MODE_2BUTTON_OVERLAY,
@@ -175,6 +195,7 @@
                     Assert.assertTrue("Switching nav mode: " + error, error == null);
 
                     Thread.sleep(5000);
+                    return true;
                 }
 
                 private void setOverlayPackageEnabled(String overlayPackage, boolean enable)
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index b796140e..3b0432e 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -88,7 +88,7 @@
     <string name="notification_dots_title" msgid="9062440428204120317">"नई सूचनाएं बताने वाला गोल निशान"</string>
     <string name="notification_dots_desc_on" msgid="1679848116452218908">"चालू"</string>
     <string name="notification_dots_desc_off" msgid="1760796511504341095">"चालू"</string>
-    <string name="title_missing_notification_access" msgid="7503287056163941064">"सूचना के एक्सेस की ज़रूरत है"</string>
+    <string name="title_missing_notification_access" msgid="7503287056163941064">"सूचना के ऐक्सेस की ज़रूरत है"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"सूचना बिंदु दिखाने के लिए, <xliff:g id="NAME">%1$s</xliff:g> के ऐप्लिकेशन सूचना चालू करें"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"सेटिंग बदलें"</string>
     <string name="notification_dots_service_title" msgid="4284221181793592871">"नई सूचनाएं बताने वाला गोल निशान दिखाएं"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 672665e..c926bc1 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -89,7 +89,7 @@
     <string name="notification_dots_desc_on" msgid="1679848116452218908">"ஆன்"</string>
     <string name="notification_dots_desc_off" msgid="1760796511504341095">"ஆஃப்"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"அறிவிப்பிற்கான அணுகல் தேவை"</string>
-    <string name="msg_missing_notification_access" msgid="281113995110910548">"அறிவிப்புப் புள்ளிகளைக் காட்ட, <xliff:g id="NAME">%1$s</xliff:g> இன் பயன்பாட்டு அறிவிப்புகளை இயக்கவும்"</string>
+    <string name="msg_missing_notification_access" msgid="281113995110910548">"அறிவிப்புப் புள்ளிகளைக் காட்ட, <xliff:g id="NAME">%1$s</xliff:g> இன் ஆப்ஸ் அறிவிப்புகளை இயக்கவும்"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"அமைப்புகளை மாற்று"</string>
     <string name="notification_dots_service_title" msgid="4284221181793592871">"அறிவிப்புப் புள்ளிகளைக் காட்டு"</string>
     <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"முகப்புத் திரையில் ஐகானைச் சேர்"</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 881f65d..339aef5 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -104,6 +104,7 @@
     </style>
 
     <style name="LauncherTheme.Dark.DarkText" parent="@style/LauncherTheme.Dark">
+        <item name="android:colorControlHighlight">#75212121</item>
         <item name="allAppsInterimScrimAlpha">25</item>
         <item name="folderFillColor">#CDFFFFFF</item>
         <item name="folderTextColor">?attr/workspaceTextColor</item>
diff --git a/src/com/android/launcher3/FirstFrameAnimatorHelper.java b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
index c967a96..6c5bc40 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.Utilities.SINGLE_FRAME_MS;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
 
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
@@ -108,17 +108,20 @@
                     // For the second frame, if the first frame took more than 16ms,
                     // adjust the start time and pretend it took only 16ms anyway. This
                     // prevents a large jump in the animation due to an expensive first frame
-                } else if (frameNum == 1 && currentTime < mStartTime + MAX_DELAY &&
-                        !mAdjustedSecondFrameTime &&
-                        currentTime > mStartTime + SINGLE_FRAME_MS &&
-                        currentPlayTime > SINGLE_FRAME_MS) {
-                    animation.setCurrentPlayTime(SINGLE_FRAME_MS);
-                    mAdjustedSecondFrameTime = true;
                 } else {
-                    if (frameNum > 1) {
-                        mRootView.post(() -> animation.removeUpdateListener(this));
+                    int singleFrameMS = getSingleFrameMs(mRootView.getContext());
+                    if (frameNum == 1 && currentTime < mStartTime + MAX_DELAY &&
+                            !mAdjustedSecondFrameTime &&
+                            currentTime > mStartTime + singleFrameMS &&
+                            currentPlayTime > singleFrameMS) {
+                        animation.setCurrentPlayTime(singleFrameMS);
+                        mAdjustedSecondFrameTime = true;
+                    } else {
+                        if (frameNum > 1) {
+                            mRootView.post(() -> animation.removeUpdateListener(this));
+                        }
+                        if (DEBUG) print(animation);
                     }
-                    if (DEBUG) print(animation);
                 }
                 mHandlingOnAnimationUpdate = false;
             } else {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 8cd0822..269a591 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -81,10 +81,10 @@
 import com.android.launcher3.graphics.DragPreviewProvider;
 import com.android.launcher3.graphics.PreloadIconDrawable;
 import com.android.launcher3.graphics.RotationMode;
+import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.pageindicators.WorkspacePageIndicator;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.WorkspaceTouchListener;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -1053,8 +1053,11 @@
             // Not announcing the overlay page for accessibility since it announces itself.
         } else if (Float.compare(scroll, 0f) == 0) {
             if (mOverlayShown) {
-                mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE,
+                UserEventDispatcher ued = mLauncher.getUserEventDispatcher();
+                if (!ued.isPreviousHomeGesture()) {
+                    mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE,
                         Action.Direction.RIGHT, ContainerType.WORKSPACE, -1);
+                }
             } else if (Float.compare(mOverlayTranslation, 0f) != 0) {
                 // When arriving to 0 overscroll from non-zero overscroll, announce page for
                 // accessibility since default announcements were disabled while in overscroll
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index 8443231..c45cd85 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -16,8 +16,9 @@
 
 package com.android.launcher3.anim;
 
-import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
 
+import android.content.Context;
 import android.graphics.Path;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.view.animation.AccelerateInterpolator;
@@ -188,13 +189,13 @@
          * @param totalDistancePx The distance against which progress is calculated.
          */
         public OvershootParams(float startProgress, float overshootPastProgress,
-                float endProgress, float velocityPxPerMs, int totalDistancePx) {
+                float endProgress, float velocityPxPerMs, int totalDistancePx, Context context) {
             velocityPxPerMs = Math.abs(velocityPxPerMs);
             start = startProgress;
             int startPx = (int) (start * totalDistancePx);
             // Overshoot by about half a frame.
             float overshootBy = OVERSHOOT_FACTOR * velocityPxPerMs *
-                    SINGLE_FRAME_MS / totalDistancePx / 2;
+                    getSingleFrameMs(context) / totalDistancePx / 2;
             overshootBy = Utilities.boundToRange(overshootBy, 0.02f, 0.15f);
             end = overshootPastProgress + overshootBy;
             int endPx = (int) (end  * totalDistancePx);
diff --git a/src/com/android/launcher3/logging/StatsLogUtils.java b/src/com/android/launcher3/logging/StatsLogUtils.java
index 647f255..b02a050 100644
--- a/src/com/android/launcher3/logging/StatsLogUtils.java
+++ b/src/com/android/launcher3/logging/StatsLogUtils.java
@@ -1,9 +1,12 @@
 package com.android.launcher3.logging;
 
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType.DEFAULT_CONTAINERTYPE;
+
 import android.view.View;
 import android.view.ViewParent;
 
 import com.android.launcher3.ItemInfo;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 
 import androidx.annotation.Nullable;
@@ -64,4 +67,20 @@
         }
         return null;
     }
+
+    public static int getContainerTypeFromState(int state) {
+        int containerType = DEFAULT_CONTAINERTYPE;
+        switch (state) {
+            case StatsLogUtils.LAUNCHER_STATE_ALLAPPS:
+                containerType = ContainerType.ALLAPPS;
+                break;
+            case StatsLogUtils.LAUNCHER_STATE_HOME:
+                containerType = ContainerType.WORKSPACE;
+                break;
+            case StatsLogUtils.LAUNCHER_STATE_OVERVIEW:
+                containerType = ContainerType.OVERVIEW;
+                break;
+        }
+        return containerType;
+    }
 }
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index bd785a1..d81020e 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -115,6 +115,7 @@
     protected InstantAppResolver mInstantAppResolver;
     private boolean mAppOrTaskLaunch;
     private UserEventDelegate mDelegate;
+    private boolean mPreviousHomeGesture;
 
     //                      APP_ICON    SHORTCUT    WIDGET
     // --------------------------------------------------------------
@@ -399,11 +400,22 @@
         mElapsedContainerMillis = SystemClock.uptimeMillis();
     }
 
+    public final void setPreviousHomeGesture(boolean homeGesture) {
+        mPreviousHomeGesture = homeGesture;
+    }
+
+    public final boolean isPreviousHomeGesture() {
+        return mPreviousHomeGesture;
+    }
+
     public final void resetActionDurationMillis() {
         mActionDurationMillis = SystemClock.uptimeMillis();
     }
 
     public void dispatchUserEvent(LauncherEvent ev, Intent intent) {
+        if (mPreviousHomeGesture) {
+            mPreviousHomeGesture = false;
+        }
         mAppOrTaskLaunch = false;
         ev.elapsedContainerMillis = SystemClock.uptimeMillis() - mElapsedContainerMillis;
         ev.elapsedSessionMillis = SystemClock.uptimeMillis() - mElapsedSessionMillis;
@@ -426,6 +438,7 @@
                 ev.actionDurationMillis);
         log += "\n\n";
         Log.d(TAG, log);
+        return;
     }
 
     private static String getTargetsStr(Target[] targets) {
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index ae69f3b..49f515a 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -22,9 +22,9 @@
 import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
 import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_SCALE_COMPONENT;
 import static com.android.launcher3.LauncherStateManager.NON_ATOMIC_COMPONENT;
-import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -55,8 +55,6 @@
 public abstract class AbstractStateChangeTouchController
         implements TouchController, SwipeDetector.Listener {
 
-    private static final String TAG = "ASCTouchController";
-
     // Progress after which the transition is assumed to be a success in case user does not fling
     public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
 
@@ -396,8 +394,8 @@
                 duration = 0;
                 startProgress = 1;
             } else {
-                startProgress = Utilities.boundToRange(
-                        progress + velocity * SINGLE_FRAME_MS * mProgressMultiplier, 0f, 1f);
+                startProgress = Utilities.boundToRange(progress
+                        + velocity * getSingleFrameMs(mLauncher) * mProgressMultiplier, 0f, 1f);
                 duration = SwipeDetector.calculateDuration(velocity,
                         endProgress - Math.max(progress, 0)) * durationMultiplier;
             }
@@ -414,8 +412,8 @@
                 duration = 0;
                 startProgress = 0;
             } else {
-                startProgress = Utilities.boundToRange(
-                        progress + velocity * SINGLE_FRAME_MS * mProgressMultiplier, 0f, 1f);
+                startProgress = Utilities.boundToRange(progress
+                        + velocity * getSingleFrameMs(mLauncher) * mProgressMultiplier, 0f, 1f);
                 duration = SwipeDetector.calculateDuration(velocity,
                         Math.min(progress, 1) - endProgress) * durationMultiplier;
             }
diff --git a/src/com/android/launcher3/util/DefaultDisplay.java b/src/com/android/launcher3/util/DefaultDisplay.java
new file mode 100644
index 0000000..7719f08
--- /dev/null
+++ b/src/com/android/launcher3/util/DefaultDisplay.java
@@ -0,0 +1,166 @@
+/*
+ * 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 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.Log;
+import android.view.Display;
+import android.view.WindowManager;
+
+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;
+
+    private final Context mContext;
+    private final int mId;
+    private final ArrayList<DisplayInfoChangeListener> mListeners = new ArrayList<>();
+    private final Handler mChangeHandler;
+    private Info mInfo;
+
+    private DefaultDisplay(Context context) {
+        mContext = context;
+        mInfo = new Info(context);
+        mId = mInfo.id;
+        mChangeHandler = new Handler(this::onChange);
+
+        context.getSystemService(DisplayManager.class)
+                .registerDisplayListener(this, new Handler(UiThreadHelper.getBackgroundLooper()));
+    }
+
+    @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(mContext);
+
+        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;
+
+        private Info(Context context) {
+            Display display = context.getSystemService(WindowManager.class).getDefaultDisplay();
+
+            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);
+        }
+
+        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/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index f2f2f3b..799762d 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.Utilities.SINGLE_FRAME_MS;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
 
 import android.annotation.TargetApi;
 import android.content.Context;
@@ -41,7 +41,6 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.launcher3.util.TouchController;
@@ -219,7 +218,7 @@
             // This can happen if something goes wrong during a state change/transition.
             AbstractFloatingView floatingView = (AbstractFloatingView) child;
             if (floatingView.isOpen()) {
-                postDelayed(() -> floatingView.close(false), SINGLE_FRAME_MS);
+                postDelayed(() -> floatingView.close(false), getSingleFrameMs(getContext()));
             }
         }
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index f91e2ad..fe7401c 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -331,8 +331,10 @@
 
         final String systemHealth = getSystemHealthMessage();
         if (systemHealth != null) {
-            message = message + ", which might be a consequence of system health problems:\n<<<\n"
-                    + systemHealth + "\n>>>";
+            message = message
+                    + ", which might be a consequence of system health "
+                    + "problems:\n<<<<<<<<<<<<<<<<<<\n"
+                    + systemHealth + "\n>>>>>>>>>>>>>>>>>>";
         }
 
         log("Hierarchy dump for: " + message);
diff --git a/tests/tapl/com/android/launcher3/tapl/TestHelpers.java b/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
index ebe5eac..399c59d 100644
--- a/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
+++ b/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
@@ -109,6 +109,7 @@
         DropBoxManager.Entry entry;
         StringBuilder errorDetails = new StringBuilder();
         while (null != (entry = dropbox.getNextEntry(label, timestamp))) {
+            if (errorDetails.length() != 0) errorDetails.append("------------------------------");
             timestamp = entry.getTimeMillis();
             errorDetails.append(new Date(timestamp));
             errorDetails.append(": ");
@@ -135,7 +136,6 @@
                     "system_server_crash",
                     "system_server_native_crash",
                     "system_server_watchdog",
-                    "system_server_wtf",
             };
 
             for (String label : labels) {
@@ -143,7 +143,9 @@
                 if (crash != null) errors.append(crash);
             }
 
-            return errors.length() != 0 ? errors.toString() : null;
+            return errors.length() != 0
+                    ? "Current time: " + new Date(System.currentTimeMillis()) + "\n" + errors
+                    : null;
         } catch (Exception e) {
             return "Failed to get system health diags, maybe build your test via .bp instead of "
                     + ".mk? " + android.util.Log.getStackTraceString(e);