Merge "Do not set user active hints when Shell Transition is on." into sc-v2-dev
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index aba16c2..85daeff 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -108,6 +108,13 @@
         }
     };
 
+    private final Runnable mOpaquenessListener = new Runnable() {
+        @Override
+        public void run() {
+            dispatchTransactionSurface(mDepth);
+        }
+    };
+
     private final Launcher mLauncher;
     /**
      * Blur radius when completely zoomed out, in pixels.
@@ -158,23 +165,28 @@
                     if (windowToken != null) {
                         mWallpaperManager.setWallpaperZoomOut(windowToken, mDepth);
                     }
-                    CrossWindowBlurListeners.getInstance().addListener(mLauncher.getMainExecutor(),
-                            mCrossWindowBlurListener);
+                    onAttached();
                 }
 
                 @Override
                 public void onViewDetachedFromWindow(View view) {
                     CrossWindowBlurListeners.getInstance().removeListener(mCrossWindowBlurListener);
+                    mLauncher.getScrimView().removeOpaquenessListener(mOpaquenessListener);
                 }
             };
             mLauncher.getRootView().addOnAttachStateChangeListener(mOnAttachListener);
             if (mLauncher.getRootView().isAttachedToWindow()) {
-                CrossWindowBlurListeners.getInstance().addListener(mLauncher.getMainExecutor(),
-                        mCrossWindowBlurListener);
+                onAttached();
             }
         }
     }
 
+    private void onAttached() {
+        CrossWindowBlurListeners.getInstance().addListener(mLauncher.getMainExecutor(),
+                mCrossWindowBlurListener);
+        mLauncher.getScrimView().addOpaquenessListener(mOpaquenessListener);
+    }
+
     /**
      * Sets if the underlying activity is started or not
      */
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index b5c834d..56c28f0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -38,9 +38,9 @@
 import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
 import com.android.quickstep.RecentsAnimationController;
 import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
-
 /**
  * A data source which integrates with a Launcher instance
  */
@@ -159,8 +159,7 @@
      *                 automatically reset once the recents animation finishes
      */
     public Animator createAnimToLauncher(@NonNull LauncherState toState,
-            @NonNull RecentsAnimationCallbacks callbacks,
-            long duration) {
+            @NonNull RecentsAnimationCallbacks callbacks, long duration) {
         TaskbarStashController stashController = mControllers.taskbarStashController;
         ObjectAnimator animator = mIconAlignmentForGestureState
                 .animateToValue(1)
@@ -180,31 +179,15 @@
                 stashController.animateToIsStashed(false, duration);
             }
         });
-        callbacks.addListener(new RecentsAnimationListener() {
-            @Override
-            public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
-                endGestureStateOverride(true);
-            }
 
-            @Override
-            public void onRecentsAnimationFinished(RecentsAnimationController controller) {
-                endGestureStateOverride(!controller.getFinishTargetIsLauncher());
-            }
-
-            private void endGestureStateOverride(boolean finishedToApp) {
-                callbacks.removeListener(this);
-                mIsAnimatingToLauncherViaGesture = false;
-
-                mIconAlignmentForGestureState
-                        .animateToValue(0)
-                        .start();
-
-                if (finishedToApp) {
-                    // We only need this for the exiting live tile case.
-                    stashController.animateToIsStashed(stashController.isStashedInApp());
-                }
-            }
+        TaskBarRecentsAnimationListener listener = new TaskBarRecentsAnimationListener(callbacks);
+        callbacks.addListener(listener);
+        RecentsView recentsView = mLauncher.getOverviewPanel();
+        recentsView.setTaskLaunchListener(() -> {
+            listener.endGestureStateOverride(true);
+            callbacks.removeListener(listener);
         });
+
         return animator;
     }
 
@@ -249,4 +232,36 @@
         mIconAlphaForHome.setValue(isVisible ? 1 : 0);
         mLauncher.getHotseat().setIconsAlpha(isVisible ? 0f : 1f);
     }
+
+    private final class TaskBarRecentsAnimationListener implements RecentsAnimationListener {
+        private final RecentsAnimationCallbacks mCallbacks;
+
+        TaskBarRecentsAnimationListener(RecentsAnimationCallbacks callbacks) {
+            mCallbacks = callbacks;
+        }
+
+        @Override
+        public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+            endGestureStateOverride(true);
+        }
+
+        @Override
+        public void onRecentsAnimationFinished(RecentsAnimationController controller) {
+            endGestureStateOverride(!controller.getFinishTargetIsLauncher());
+        }
+
+        private void endGestureStateOverride(boolean finishedToApp) {
+            mCallbacks.removeListener(this);
+            mIsAnimatingToLauncherViaGesture = false;
+
+            mIconAlignmentForGestureState
+                    .animateToValue(0)
+                    .start();
+
+            TaskbarStashController controller = mControllers.taskbarStashController;
+            if (finishedToApp) {
+                controller.animateToIsStashed(controller.isStashedInApp());
+            }
+        }
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 2009cd7..069af96 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -36,9 +36,13 @@
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.res.Configuration;
+import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceStateManager;
 import android.view.HapticFeedbackConstants;
 import android.view.View;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
@@ -75,9 +79,14 @@
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskUtils;
+import com.android.quickstep.util.LauncherUnfoldAnimationController;
+import com.android.quickstep.util.ProxyScreenStatusProvider;
 import com.android.quickstep.util.QuickstepOnboardingPrefs;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
+import com.android.unfold.UnfoldTransitionFactory;
+import com.android.unfold.UnfoldTransitionProgressProvider;
+import com.android.unfold.config.UnfoldTransitionConfig;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -97,10 +106,51 @@
     private FixedContainerItems mAllAppsPredictions;
     private HotseatPredictionController mHotseatPredictionController;
 
+    @Nullable
+    private LauncherUnfoldAnimationController mLauncherUnfoldAnimationController;
+
     @Override
     protected void setupViews() {
         super.setupViews();
         mHotseatPredictionController = new HotseatPredictionController(this);
+
+        final UnfoldTransitionConfig config = UnfoldTransitionFactory.createConfig(this);
+        if (config.isEnabled()) {
+            final UnfoldTransitionProgressProvider unfoldTransitionProgressProvider =
+                    UnfoldTransitionFactory.createUnfoldTransitionProgressProvider(
+                            this,
+                            config,
+                            ProxyScreenStatusProvider.INSTANCE,
+                            getSystemService(DeviceStateManager.class),
+                            getSystemService(SensorManager.class),
+                            getMainThreadHandler(),
+                            getMainExecutor()
+                    );
+
+            mLauncherUnfoldAnimationController = new LauncherUnfoldAnimationController(
+                    this,
+                    getWindowManager(),
+                    unfoldTransitionProgressProvider
+            );
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        if (mLauncherUnfoldAnimationController != null) {
+            mLauncherUnfoldAnimationController.onResume();
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        if (mLauncherUnfoldAnimationController != null) {
+            mLauncherUnfoldAnimationController.onPause();
+        }
+
+        super.onPause();
     }
 
     @Override
@@ -231,6 +281,10 @@
     public void onDestroy() {
         super.onDestroy();
         mHotseatPredictionController.destroy();
+
+        if (mLauncherUnfoldAnimationController != null) {
+            mLauncherUnfoldAnimationController.onDestroy();
+        }
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index d822c8c..f8c9fd1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -70,7 +70,9 @@
 
     @Override
     protected float getDepthUnchecked(Context context) {
-        return 1f;
+        // The scrim fades in at approximately 50% of the swipe gesture.
+        // This means that the depth should be greater than 1, in order to fully zoom out.
+        return 2f;
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 61622ee..4979206 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -100,6 +100,7 @@
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.AssistantUtilities;
 import com.android.quickstep.util.ProtoTracer;
+import com.android.quickstep.util.ProxyScreenStatusProvider;
 import com.android.quickstep.util.SplitScreenBounds;
 import com.android.systemui.plugins.OverscrollPlugin;
 import com.android.systemui.plugins.PluginListener;
@@ -268,6 +269,12 @@
             MAIN_EXECUTOR.execute(() -> SplitScreenBounds.INSTANCE.setSecondaryWindowBounds(wb));
         }
 
+        @BinderThread
+        @Override
+        public void onScreenTurnedOn() {
+            MAIN_EXECUTOR.execute(ProxyScreenStatusProvider.INSTANCE::onScreenTurnedOn);
+        }
+
         @Override
         public void onRotationProposal(int rotation, boolean isValid) {
             executeForTaskbarManager(() -> mTaskbarManager.onRotationProposal(rotation, isValid));
diff --git a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
index f1b4e3d..baca76c 100644
--- a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
+++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
@@ -48,15 +48,16 @@
 public class AnimatorControllerWithResistance {
 
     private enum RecentsResistanceParams {
-        FROM_APP(0.75f, 0.5f, 1f),
-        FROM_APP_TABLET(0.9f, 0.75f, 1f),
-        FROM_OVERVIEW(1f, 0.75f, 0.5f);
+        FROM_APP(0.75f, 0.5f, 1f, false),
+        FROM_APP_TABLET(1f, 0.7f, 1f, true),
+        FROM_OVERVIEW(1f, 0.75f, 0.5f, false);
 
         RecentsResistanceParams(float scaleStartResist, float scaleMaxResist,
-                float translationFactor) {
+                float translationFactor, boolean stopScalingAtTop) {
             this.scaleStartResist = scaleStartResist;
             this.scaleMaxResist = scaleMaxResist;
             this.translationFactor = translationFactor;
+            this.stopScalingAtTop = stopScalingAtTop;
         }
 
         /**
@@ -74,6 +75,12 @@
          * where 0 will keep it centered and 1 will have it barely touch the top of the screen.
          */
         public final float translationFactor;
+
+        /**
+         * Whether to end scaling effect when the scaled down version of TaskView's top reaches the
+         * non-scaled version of TaskView's top.
+         */
+        public final boolean stopScalingAtTop;
     }
 
     private static final TimeInterpolator RECENTS_SCALE_RESIST_INTERPOLATOR = DEACCEL;
@@ -161,26 +168,6 @@
         PointF pivot = new PointF();
         float fullscreenScale = params.recentsOrientedState.getFullScreenScaleAndPivot(
                 startRect, params.dp, pivot);
-        float prevScaleRate = (fullscreenScale - params.startScale)
-                / (params.dp.heightPx - startRect.bottom);
-        // This is what the scale would be at the end of the drag if we didn't apply resistance.
-        float endScale = params.startScale - prevScaleRate * distanceToCover;
-        // Create an interpolator that resists the scale so the scale doesn't get smaller than
-        // RECENTS_SCALE_MAX_RESIST.
-        float startResist = Utilities.getProgress(params.resistanceParams.scaleStartResist,
-                params.startScale, endScale);
-        float maxResist = Utilities.getProgress(params.resistanceParams.scaleMaxResist,
-                params.startScale, endScale);
-        final TimeInterpolator scaleInterpolator = t -> {
-            if (t < startResist) {
-                return t;
-            }
-            float resistProgress = Utilities.getProgress(t, startResist, 1);
-            resistProgress = RECENTS_SCALE_RESIST_INTERPOLATOR.getInterpolation(resistProgress);
-            return startResist + resistProgress * (maxResist - startResist);
-        };
-        resistAnim.addFloat(params.scaleTarget, params.scaleProperty, params.startScale, endScale,
-                scaleInterpolator);
 
         // Compute where the task view would be based on the end scale.
         RectF endRectF = new RectF(startRect);
@@ -195,6 +182,32 @@
         resistAnim.addFloat(params.translationTarget, params.translationProperty,
                 params.startTranslation, endTranslation, RECENTS_TRANSLATE_RESIST_INTERPOLATOR);
 
+        float prevScaleRate = (fullscreenScale - params.startScale)
+                / (params.dp.heightPx - startRect.bottom);
+        // This is what the scale would be at the end of the drag if we didn't apply resistance.
+        float endScale = params.startScale - prevScaleRate * distanceToCover;
+        // Create an interpolator that resists the scale so the scale doesn't get smaller than
+        // RECENTS_SCALE_MAX_RESIST.
+        float startResist = Utilities.getProgress(params.resistanceParams.scaleStartResist,
+                params.startScale, endScale);
+        float maxResist = Utilities.getProgress(params.resistanceParams.scaleMaxResist,
+                params.startScale, endScale);
+        float stopResist =
+                params.resistanceParams.stopScalingAtTop ? 1f - startRect.top / endRectF.top : 1f;
+        final TimeInterpolator scaleInterpolator = t -> {
+            if (t < startResist) {
+                return t;
+            }
+            if (t > stopResist) {
+                return maxResist;
+            }
+            float resistProgress = Utilities.getProgress(t, startResist, stopResist);
+            resistProgress = RECENTS_SCALE_RESIST_INTERPOLATOR.getInterpolation(resistProgress);
+            return startResist + resistProgress * (maxResist - startResist);
+        };
+        resistAnim.addFloat(params.scaleTarget, params.scaleProperty, params.startScale, endScale,
+                scaleInterpolator);
+
         return resistAnim;
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
new file mode 100644
index 0000000..ea1ece8
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2021 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.quickstep.util;
+
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+
+import com.android.launcher3.Launcher;
+import com.android.unfold.UnfoldTransitionProgressProvider;
+import com.android.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
+
+/**
+ * Controls animations that are happening during unfolding foldable devices
+ */
+public class LauncherUnfoldAnimationController {
+
+    private final Launcher mLauncher;
+    private final UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
+    private final UnfoldMoveFromCenterWorkspaceAnimator mMoveFromCenterWorkspaceAnimation;
+
+    private final AnimationListener mAnimationListener = new AnimationListener();
+
+    private boolean mIsTransitionRunning = false;
+    private boolean mIsReadyToPlayAnimation = false;
+
+    public LauncherUnfoldAnimationController(
+            Launcher launcher,
+            WindowManager windowManager,
+            UnfoldTransitionProgressProvider unfoldTransitionProgressProvider) {
+        mLauncher = launcher;
+        mUnfoldTransitionProgressProvider = unfoldTransitionProgressProvider;
+        mMoveFromCenterWorkspaceAnimation = new UnfoldMoveFromCenterWorkspaceAnimator(launcher,
+                windowManager);
+        mUnfoldTransitionProgressProvider.addCallback(mAnimationListener);
+    }
+
+    /**
+     * Called when launcher is resumed
+     */
+    public void onResume() {
+        final ViewTreeObserver obs = mLauncher.getWorkspace().getViewTreeObserver();
+        obs.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+            @Override
+            public boolean onPreDraw() {
+                if (obs.isAlive()) {
+                    onPreDrawAfterResume();
+                    obs.removeOnPreDrawListener(this);
+                }
+                return true;
+            }
+        });
+    }
+
+    /**
+     * Called when launcher activity is paused
+     */
+    public void onPause() {
+        mIsReadyToPlayAnimation = false;
+
+        if (mIsTransitionRunning) {
+            mIsTransitionRunning = false;
+            mMoveFromCenterWorkspaceAnimation.onTransitionFinished();
+        }
+    }
+
+    /**
+     * Called when launcher activity is destroyed
+     */
+    public void onDestroy() {
+        mUnfoldTransitionProgressProvider.removeCallback(mAnimationListener);
+    }
+
+    /**
+     * Called after performing layouting of the views after configuration change
+     */
+    private void onPreDrawAfterResume() {
+        mIsReadyToPlayAnimation = true;
+
+        if (mIsTransitionRunning) {
+            mMoveFromCenterWorkspaceAnimation.onTransitionStarted();
+        }
+    }
+
+    private class AnimationListener implements TransitionProgressListener {
+
+        @Override
+        public void onTransitionStarted() {
+            mIsTransitionRunning = true;
+
+            if (mIsReadyToPlayAnimation) {
+                mMoveFromCenterWorkspaceAnimation.onTransitionStarted();
+            }
+        }
+
+        @Override
+        public void onTransitionFinished() {
+            if (mIsReadyToPlayAnimation) {
+                mMoveFromCenterWorkspaceAnimation.onTransitionFinished();
+            }
+
+            mIsTransitionRunning = false;
+        }
+
+        @Override
+        public void onTransitionProgress(float progress) {
+            mMoveFromCenterWorkspaceAnimation.onTransitionProgress(progress);
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index 65be624..b83e26e 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -17,7 +17,6 @@
 
 import android.content.Context;
 import android.content.res.Resources;
-import android.util.Log;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 
@@ -86,12 +85,7 @@
         mSpeedSomewhatFast = res.getDimension(R.dimen.motion_pause_detector_speed_somewhat_fast);
         mSpeedFast = res.getDimension(R.dimen.motion_pause_detector_speed_fast);
         mForcePauseTimeout = new Alarm();
-        mForcePauseTimeout.setOnAlarmListener(alarm -> {
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.MOTION_PAUSE_TIMEOUT, "onAlarm");
-            }
-            updatePaused(true /* isPaused */);
-        });
+        mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */));
         mMakePauseHarderToTrigger = makePauseHarderToTrigger;
         mVelocityProvider = new SystemVelocityProvider(axis);
     }
@@ -128,9 +122,6 @@
         long timeoutMs = TestProtocol.sForcePauseTimeout != null
                 ? TestProtocol.sForcePauseTimeout
                 : mMakePauseHarderToTrigger ? HARDER_TRIGGER_TIMEOUT : FORCE_PAUSE_TIMEOUT;
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.MOTION_PAUSE_TIMEOUT, "setAlarm: " + timeoutMs);
-        }
         mForcePauseTimeout.setAlarm(timeoutMs);
         float newVelocity = mVelocityProvider.addMotionEvent(ev, ev.getPointerId(pointerIndex));
         if (mPreviousVelocity != null) {
@@ -172,9 +163,6 @@
                 }
             }
         }
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.MOTION_PAUSE_TIMEOUT, "checkMotionPaused: " + isPaused);
-        }
         updatePaused(isPaused);
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/ProxyScreenStatusProvider.java b/quickstep/src/com/android/quickstep/util/ProxyScreenStatusProvider.java
new file mode 100644
index 0000000..9eda8f4
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ProxyScreenStatusProvider.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2021 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.quickstep.util;
+
+import androidx.annotation.NonNull;
+
+import com.android.unfold.updates.screen.ScreenStatusProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Screen status provider implementation that exposes methods to provide screen
+ * status updates to listeners. It is used to receive screen turned on event from
+ * SystemUI to Launcher.
+ */
+public class ProxyScreenStatusProvider implements ScreenStatusProvider {
+
+    public static final ProxyScreenStatusProvider INSTANCE = new ProxyScreenStatusProvider();
+    private final List<ScreenListener> mListeners = new ArrayList<>();
+
+    /**
+     * Called when the screen is on and ready (windows are drawn and screen blocker is removed)
+     */
+    public void onScreenTurnedOn() {
+        mListeners.forEach(ScreenListener::onScreenTurnedOn);
+    }
+
+    @Override
+    public void addCallback(@NonNull ScreenListener listener) {
+        mListeners.add(listener);
+    }
+
+    @Override
+    public void removeCallback(@NonNull ScreenListener listener) {
+        mListeners.remove(listener);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
new file mode 100644
index 0000000..126e044
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2021 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.quickstep.util;
+
+import android.annotation.NonNull;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.Hotseat;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.ShortcutAndWidgetContainer;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.widget.NavigableAppWidgetHostView;
+import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator;
+import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator.TranslationApplier;
+import com.android.unfold.UnfoldTransitionProgressProvider;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Animation that moves launcher icons and widgets from center to the sides (final position)
+ */
+public class UnfoldMoveFromCenterWorkspaceAnimator
+        implements UnfoldTransitionProgressProvider.TransitionProgressListener {
+
+    private final Launcher mLauncher;
+    private final UnfoldMoveFromCenterAnimator mMoveFromCenterAnimation;
+
+    private final Map<ViewGroup, Boolean> mOriginalClipToPadding = new HashMap<>();
+    private final Map<ViewGroup, Boolean> mOriginalClipChildren = new HashMap<>();
+
+    public UnfoldMoveFromCenterWorkspaceAnimator(Launcher launcher, WindowManager windowManager) {
+        mLauncher = launcher;
+        mMoveFromCenterAnimation = new UnfoldMoveFromCenterAnimator(windowManager,
+                new WorkspaceViewsTranslationApplier());
+    }
+
+    @Override
+    public void onTransitionStarted() {
+        mMoveFromCenterAnimation.updateDisplayProperties();
+
+        Workspace workspace = mLauncher.getWorkspace();
+        Hotseat hotseat = mLauncher.getHotseat();
+
+        // App icons and widgets
+        workspace
+                .forEachVisiblePage(page -> {
+                    final CellLayout cellLayout = (CellLayout) page;
+                    ShortcutAndWidgetContainer itemsContainer = cellLayout
+                            .getShortcutsAndWidgets();
+                    disableClipping(cellLayout);
+
+                    for (int i = 0; i < itemsContainer.getChildCount(); i++) {
+                        View child = itemsContainer.getChildAt(i);
+                        mMoveFromCenterAnimation.registerViewForAnimation(child);
+                    }
+                });
+
+        disableClipping(workspace);
+
+        // Hotseat icons
+        ViewGroup hotseatIcons = hotseat.getShortcutsAndWidgets();
+        disableClipping(hotseat);
+
+        for (int i = 0; i < hotseatIcons.getChildCount(); i++) {
+            View child = hotseatIcons.getChildAt(i);
+            mMoveFromCenterAnimation.registerViewForAnimation(child);
+        }
+
+        onTransitionProgress(0f);
+    }
+
+    @Override
+    public void onTransitionProgress(float progress) {
+        mMoveFromCenterAnimation.onTransitionProgress(progress);
+    }
+
+    @Override
+    public void onTransitionFinished() {
+        mMoveFromCenterAnimation.onTransitionFinished();
+        mMoveFromCenterAnimation.clearRegisteredViews();
+
+        restoreClipping(mLauncher.getWorkspace());
+        mLauncher.getWorkspace().forEachVisiblePage(page -> restoreClipping((CellLayout) page));
+        restoreClipping(mLauncher.getHotseat());
+
+        mOriginalClipChildren.clear();
+        mOriginalClipToPadding.clear();
+    }
+
+    private void disableClipping(ViewGroup view) {
+        mOriginalClipToPadding.put(view, view.getClipToPadding());
+        mOriginalClipChildren.put(view, view.getClipChildren());
+        view.setClipToPadding(false);
+        view.setClipChildren(false);
+    }
+
+    private void restoreClipping(ViewGroup view) {
+        final Boolean originalClipToPadding = mOriginalClipToPadding.get(view);
+        if (originalClipToPadding != null) {
+            view.setClipToPadding(originalClipToPadding);
+        }
+        final Boolean originalClipChildren = mOriginalClipChildren.get(view);
+        if (originalClipChildren != null) {
+            view.setClipChildren(originalClipChildren);
+        }
+    }
+
+    private static class WorkspaceViewsTranslationApplier implements TranslationApplier {
+
+        @Override
+        public void apply(@NonNull View view, float x, float y) {
+            if (view instanceof NavigableAppWidgetHostView) {
+                ((NavigableAppWidgetHostView) view).setTranslationForMoveFromCenterAnimation(x, y);
+            } else if (view instanceof BubbleTextView) {
+                ((BubbleTextView) view).setTranslationForMoveFromCenterAnimation(x, y);
+            } else {
+                view.setTranslationX(x);
+                view.setTranslationY(y);
+            }
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index b077ca6..3aed7cc 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -42,6 +42,7 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN;
 import static com.android.launcher3.statehandlers.DepthController.DEPTH;
+import static com.android.launcher3.testing.TestProtocol.TASK_VIEW_ID_CRASH;
 import static com.android.launcher3.touch.PagedOrientationHandler.CANVAS_TRANSLATE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
@@ -82,6 +83,7 @@
 import android.text.TextPaint;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
+import android.util.Log;
 import android.util.SparseBooleanArray;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
@@ -600,6 +602,7 @@
             };
 
     private RunnableList mSideTaskLaunchCallback;
+    private TaskLaunchListener mTaskLaunchListener;
 
     public RecentsView(Context context, AttributeSet attrs, int defStyleAttr,
             BaseActivityInterface sizeStrategy) {
@@ -880,6 +883,21 @@
         mSideTaskLaunchCallback.add(callback::executeAllAndDestroy);
     }
 
+    /**
+     * This is a one-time callback when touching in live tile mode. It's reset to null right
+     * after it's called.
+     */
+    public void setTaskLaunchListener(TaskLaunchListener taskLaunchListener) {
+        mTaskLaunchListener = taskLaunchListener;
+    }
+
+    public void onTaskLaunchedInLiveTileMode() {
+        if (mTaskLaunchListener != null) {
+            mTaskLaunchListener.onTaskLaunched();
+            mTaskLaunchListener = null;
+        }
+    }
+
     private void executeSideTaskLaunchCallback() {
         if (mSideTaskLaunchCallback != null) {
             mSideTaskLaunchCallback.executeAllAndDestroy();
@@ -1170,7 +1188,7 @@
         mMovingTaskView = focusedTaskView;
         removeView(focusedTaskView);
         mMovingTaskView = null;
-        focusedTaskView.onRecycle();
+        focusedTaskView.resetPersistentViewTransforms();
         addView(focusedTaskView, mTaskViewStartIndex);
         setCurrentPage(mTaskViewStartIndex);
 
@@ -1222,6 +1240,9 @@
         // TODO set these type to array and check all taskIDs? Maybe we can get away w/ only one
         int runningTaskId = getTaskIdsForTaskViewId(mRunningTaskViewId)[0];
         int focusedTaskId = getTaskIdsForTaskViewId(mFocusedTaskViewId)[0];
+        Log.d(TASK_VIEW_ID_CRASH, "runningTaskId beforeBind: " + runningTaskId
+                + " runningTaskViewId: " + mRunningTaskViewId
+                + " forTaskView: " + getTaskViewFromTaskViewId(mRunningTaskViewId));
 
         // Rebind and reset all task views
         for (int i = requiredTaskCount - 1; i >= 0; i--) {
@@ -1246,6 +1267,18 @@
             // Update mRunningTaskViewId to be the new TaskView that was assigned by binding
             // the full list of tasks to taskViews
             newRunningTaskView = getTaskViewByTaskId(runningTaskId);
+            if (newRunningTaskView == null) {
+                StringBuilder sb = new StringBuilder();
+                for (int i = requiredTaskCount - 1; i >= 0; i--) {
+                    final int pageIndex = requiredTaskCount - i - 1 + mTaskViewStartIndex;
+                    final TaskView taskView = (TaskView) getChildAt(pageIndex);
+                    int taskViewId = taskView.getTaskViewId();
+                    sb.append(" taskViewId: " + taskViewId
+                            + " taskId: " + getTaskIdsForTaskViewId(taskViewId)[0]
+                            + " for taskView: " + taskView + "\n");
+                }
+                Log.d(TASK_VIEW_ID_CRASH, sb.toString());
+            }
             mRunningTaskViewId = newRunningTaskView.getTaskViewId();
         }
 
@@ -4193,4 +4226,8 @@
         // Set locus context is a binder call, don't want it to happen during a transition
         UI_HELPER_EXECUTOR.post(() -> mActivity.setLocusContext(id, Bundle.EMPTY));
     }
+
+    public interface TaskLaunchListener {
+        void onTaskLaunched();
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 02c5d84..2c33b6d 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -584,6 +584,7 @@
                 }
             });
             anim.start();
+            recentsView.onTaskLaunchedInLiveTileMode();
         } else {
             launchTaskAnimated();
         }
@@ -614,12 +615,14 @@
             ActivityOptionsWrapper opts =  mActivity.getActivityLaunchOptions(this, null);
             if (ActivityManagerWrapper.getInstance()
                     .startActivityFromRecents(mTask.key, opts.options)) {
-                if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
-                        getRecentsView().getRunningTaskViewId() != -1) {
+                RecentsView recentsView = getRecentsView();
+                if (ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskViewId() != -1) {
+                    recentsView.onTaskLaunchedInLiveTileMode();
+
                     // Return a fresh callback in the live tile case, so that it's not accidentally
                     // triggered by QuickstepTransitionManager.AppLaunchAnimationRunner.
                     RunnableList callbackList = new RunnableList();
-                    getRecentsView().addSideTaskLaunchCallback(callbackList);
+                    recentsView.addSideTaskLaunchCallback(callbackList);
                     return callbackList;
                 }
                 return opts.onEndCallback;
@@ -871,6 +874,12 @@
         setIconAndDimTransitionProgress(iconScale, invert);
     }
 
+    protected void resetPersistentViewTransforms() {
+        mNonGridTranslationX = mNonGridTranslationY =
+                mGridTranslationX = mGridTranslationY = mBoxTranslationY = 0f;
+        resetViewTransforms();
+    }
+
     protected void resetViewTransforms() {
         // fullscreenTranslation and accumulatedTranslation should not be reset, as
         // resetViewTransforms is called during Quickswitch scrolling.
@@ -894,9 +903,7 @@
 
     @Override
     public void onRecycle() {
-        mNonGridTranslationX = mNonGridTranslationY =
-                mGridTranslationX = mGridTranslationY = mBoxTranslationY = 0f;
-        resetViewTransforms();
+        resetPersistentViewTransforms();
         // Clear any references to the thumbnail (it will be re-read either from the cache or the
         // system on next bind)
         mSnapshotView.setThumbnail(mTask, null);
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 164e755..e5e560a 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -37,7 +37,6 @@
 import com.android.launcher3.tapl.OverviewActions;
 import com.android.launcher3.tapl.OverviewTask;
 import com.android.launcher3.ui.TaplTestsLauncher3;
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 import com.android.quickstep.views.RecentsView;
 
@@ -158,7 +157,6 @@
     @Test
     @NavigationModeSwitch
     @PortraitLandscape
-    @ScreenRecord //b/193125090
     public void testOverviewActions() throws Exception {
         // Experimenting for b/165029151:
         final Overview overview = mLauncher.pressHome().switchToOverview();
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 80a6f10..6893888 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -68,7 +68,7 @@
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"షార్ట్‌కట్‌ను తరలించడానికి లేదా అనుకూల చర్యలను ఉపయోగించడానికి రెండుసార్లు నొక్కండి &amp; హోల్డ్ చేయండి."</string>
     <string name="out_of_space" msgid="6692471482459245734">"ఈ మొదటి స్క్రీన్‌లో స్థలం లేదు"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"ఇష్టమైనవి ట్రేలో ఖాళీ లేదు"</string>
-    <string name="all_apps_button_label" msgid="8130441508702294465">"అనువర్తనాల జాబితా"</string>
+    <string name="all_apps_button_label" msgid="8130441508702294465">"యాప్‌ల జాబితా"</string>
     <string name="all_apps_button_personal_label" msgid="1315764287305224468">"వ్యక్తిగత యాప్‌ల జాబితా"</string>
     <string name="all_apps_button_work_label" msgid="7270707118948892488">"కార్యాలయ యాప్‌ల జాబితా"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"తీసివేయి"</string>
@@ -83,7 +83,7 @@
     <string name="permdesc_read_settings" msgid="5833423719057558387">"హోమ్‌లో సెట్టింగ్‌లు మరియు సత్వరమార్గాలను చదవడానికి యాప్‌ను అనుమతిస్తుంది."</string>
     <string name="permlab_write_settings" msgid="3574213698004620587">"హోమ్ సెట్టింగ్‌లు మరియు సత్వరమార్గాలను వ్రాయడం"</string>
     <string name="permdesc_write_settings" msgid="5440712911516509985">"హోమ్‌లో సెట్టింగ్‌లు మరియు సత్వరమార్గాలను మార్చడానికి యాప్‌ను అనుమతిస్తుంది."</string>
-    <string name="msg_no_phone_permission" msgid="9208659281529857371">"ఫోన్ కాల్‌లను చేసేందుకు <xliff:g id="APP_NAME">%1$s</xliff:g>కి అనుమతి లేదు"</string>
+    <string name="msg_no_phone_permission" msgid="9208659281529857371">"ఫోన్ కాల్స్‌ను చేసేందుకు <xliff:g id="APP_NAME">%1$s</xliff:g>కి అనుమతి లేదు"</string>
     <string name="gadget_error_text" msgid="740356548025791839">"విడ్జెట్‌ను లోడ్ చేయడం సాధ్యం కాలేదు"</string>
     <string name="gadget_setup_text" msgid="1745356155479272374">"సెటప్‌ను పూర్తి చేయడానికి ట్యాప్ చేయండి"</string>
     <string name="uninstall_system_app_text" msgid="4172046090762920660">"ఇది సిస్టమ్ యాప్ మరియు దీన్ని అన్‌ఇన్‌స్టాల్ చేయడం సాధ్యపడదు."</string>
diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml
index c2ebeff..55cedf4 100644
--- a/res/values-v31/colors.xml
+++ b/res/values-v31/colors.xml
@@ -41,6 +41,8 @@
     <color name="wallpaper_popup_scrim">@android:color/system_neutral1_900</color>
 
     <color name="folder_dot_color">@android:color/system_accent2_50</color>
+    <color name="folder_pagination_color_light">@android:color/system_accent1_600</color>
+    <color name="folder_pagination_color_dark">@android:color/system_accent2_100</color>
 
     <color name="home_settings_header_accent">@android:color/system_accent1_600</color>
     <color name="home_settings_header_collapsed">@android:color/system_neutral1_100</color>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 00cf31c..d05b69e 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -42,6 +42,7 @@
     <attr name="popupNotificationDotColor" format="color" />
 
     <attr name="folderDotColor" format="color" />
+    <attr name="folderPaginationColor" format="color" />
     <attr name="folderFillColor" format="color" />
     <attr name="folderIconRadius" format="float" />
     <attr name="folderIconBorderColor" format="color" />
@@ -176,11 +177,20 @@
         <attr name="borderSpacingDps" format="float" />
 
         <attr name="iconImageSize" format="float" />
-        <!-- landscapeIconSize defaults to iconSize, if not specified -->
+        <!-- landscapeIconSize defaults to iconImageSize, if not specified -->
         <attr name="landscapeIconSize" format="float" />
+        <!-- twoPanelPortraitIconSize defaults to iconImageSize, if not specified -->
+        <attr name="twoPanelPortraitIconSize" format="float" />
+        <!-- twoPanelLandscapeIconSize defaults to landscapeIconSize, if not specified -->
+        <attr name="twoPanelLandscapeIconSize" format="float" />
+
         <attr name="iconTextSize" format="float" />
         <!-- landscapeIconTextSize defaults to iconTextSize, if not specified -->
         <attr name="landscapeIconTextSize" format="float" />
+        <!-- twoPanelPortraitIconTextSize defaults to iconTextSize, if not specified -->
+        <attr name="twoPanelPortraitIconTextSize" format="float" />
+        <!-- twoPanelLandscapeIconTextSize defaults to landscapeIconTextSize, if not specified -->
+        <attr name="twoPanelLandscapeIconTextSize" format="float" />
 
         <!-- If set, this display option is used to determine the default grid -->
         <attr name="canBeDefault" format="boolean|integer" >
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 1b68fb6..cc5c5a3 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -73,6 +73,8 @@
     <color name="folder_background_dark">#464746</color>
 
     <color name="folder_dot_color">?attr/colorPrimary</color>
+    <color name="folder_pagination_color_light">#ff006c5f</color>
+    <color name="folder_pagination_color_dark">#ffbfebe3</color>
 
     <color name="text_color_primary_dark">#FFFFFFFF</color>
     <color name="text_color_secondary_dark">#FFFFFFFF</color>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index b7661b9..8ad4fcd 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -50,6 +50,7 @@
         <item name="workspaceStatusBarScrim">@drawable/workspace_bg</item>
         <item name="widgetsTheme">@style/WidgetContainerTheme</item>
         <item name="folderDotColor">@color/folder_dot_color</item>
+        <item name="folderPaginationColor">@color/folder_pagination_color_light</item>
         <item name="folderFillColor">@color/folder_background_light</item>
         <item name="folderIconBorderColor">?android:attr/colorPrimary</item>
         <item name="folderTextColor">@color/workspace_text_color_dark</item>
@@ -108,6 +109,7 @@
         <item name="popupShadeThird">@color/popup_shade_third_dark</item>
         <item name="widgetsTheme">@style/WidgetContainerTheme.Dark</item>
         <item name="folderDotColor">@color/folder_dot_color</item>
+        <item name="folderPaginationColor">@color/folder_pagination_color_dark</item>
         <item name="folderFillColor">@color/folder_background_dark</item>
         <item name="folderIconBorderColor">?android:attr/colorPrimary</item>
         <item name="folderTextColor">@color/workspace_text_color_light</item>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 02fe7d9..353e52b 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -90,6 +90,8 @@
     private final PointF mTranslationForReorderBounce = new PointF(0, 0);
     private final PointF mTranslationForReorderPreview = new PointF(0, 0);
 
+    private final PointF mTranslationForMoveFromCenterAnimation = new PointF(0, 0);
+
     private float mScaleForReorderBounce = 1f;
 
     private static final Property<BubbleTextView, Float> DOT_SCALE_PROPERTY
@@ -800,8 +802,10 @@
     }
 
     private void updateTranslation() {
-        super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x);
-        super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y);
+        super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x
+                + mTranslationForMoveFromCenterAnimation.x);
+        super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y
+                + mTranslationForMoveFromCenterAnimation.y);
     }
 
     public void setReorderBounceOffset(float x, float y) {
@@ -834,6 +838,11 @@
         return mScaleForReorderBounce;
     }
 
+    public void setTranslationForMoveFromCenterAnimation(float x, float y) {
+        mTranslationForMoveFromCenterAnimation.set(x, y);
+        updateTranslation();
+    }
+
     public View getView() {
         return this;
     }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 7acec1f..eb058e8 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -612,12 +612,30 @@
         iconScale = Math.min(1f, scale);
         cellScaleToFit = scale;
 
-
         // Workspace
         final boolean isVerticalLayout = isVerticalBarLayout();
-        float invIconSizeDp = isLandscape ? inv.landscapeIconSize : inv.iconSize;
+        float invIconSizeDp;
+        float invIconTextSizeSp;
+
+        if (isTwoPanels) {
+            if (isLandscape) {
+                invIconSizeDp = inv.twoPanelLandscapeIconSize;
+                invIconTextSizeSp = inv.twoPanelLandscapeIconTextSize;
+            } else {
+                invIconSizeDp = inv.twoPanelPortraitIconSize;
+                invIconTextSizeSp = inv.twoPanelPortraitIconTextSize;
+            }
+        } else {
+            if (isLandscape) {
+                invIconSizeDp = inv.landscapeIconSize;
+                invIconTextSizeSp = inv.landscapeIconTextSize;
+            } else {
+                invIconSizeDp = inv.iconSize;
+                invIconTextSizeSp = inv.iconTextSize;
+            }
+        }
+
         iconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mMetrics, iconScale));
-        float invIconTextSizeSp = isLandscape ? inv.landscapeIconTextSize : inv.iconTextSize;
         iconTextSizePx = (int) (pxFromSp(invIconTextSizeSp, mMetrics) * iconScale);
         iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * iconScale);
 
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 10b3f98..b93bfd6 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -94,7 +94,11 @@
     public int numFolderColumns;
     public float iconSize;
     public float landscapeIconSize;
+    public float twoPanelPortraitIconSize;
+    public float twoPanelLandscapeIconSize;
     public float landscapeIconTextSize;
+    public float twoPanelPortraitIconTextSize;
+    public float twoPanelLandscapeIconTextSize;
     public int iconBitmapSize;
     public int fillResIconDpi;
     public float iconTextSize;
@@ -157,9 +161,13 @@
         numFolderColumns = p.numFolderColumns;
         iconSize = p.iconSize;
         landscapeIconSize = p.landscapeIconSize;
+        twoPanelPortraitIconSize = p.twoPanelPortraitIconSize;
+        twoPanelLandscapeIconSize = p.twoPanelLandscapeIconSize;
         iconBitmapSize = p.iconBitmapSize;
         iconTextSize = p.iconTextSize;
         landscapeIconTextSize = p.landscapeIconTextSize;
+        twoPanelPortraitIconTextSize = p.twoPanelPortraitIconTextSize;
+        twoPanelLandscapeIconTextSize = p.twoPanelLandscapeIconTextSize;
         numShownHotseatIcons = p.numShownHotseatIcons;
         numDatabaseHotseatIcons = p.numDatabaseHotseatIcons;
         numAllAppsColumns = p.numAllAppsColumns;
@@ -277,9 +285,13 @@
 
         iconSize = displayOption.iconSize;
         landscapeIconSize = displayOption.landscapeIconSize;
+        twoPanelPortraitIconSize = displayOption.twoPanelPortraitIconSize;
+        twoPanelLandscapeIconSize = displayOption.twoPanelLandscapeIconSize;
         iconBitmapSize = ResourceUtils.pxFromDp(iconSize, metrics);
         iconTextSize = displayOption.iconTextSize;
         landscapeIconTextSize = displayOption.landscapeIconTextSize;
+        twoPanelPortraitIconTextSize = displayOption.twoPanelPortraitIconTextSize;
+        twoPanelLandscapeIconTextSize = displayOption.twoPanelLandscapeIconTextSize;
         fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
 
         minCellHeight = displayOption.minCellHeight;
@@ -653,7 +665,11 @@
         private float iconSize;
         private float iconTextSize;
         private float landscapeIconSize;
+        private float twoPanelPortraitIconSize;
+        private float twoPanelLandscapeIconSize;
         private float landscapeIconTextSize;
+        private float twoPanelPortraitIconTextSize;
+        private float twoPanelLandscapeIconTextSize;
         private float allAppsIconSize;
         private float allAppsIconTextSize;
 
@@ -676,9 +692,19 @@
             iconSize = a.getFloat(R.styleable.ProfileDisplayOption_iconImageSize, 0);
             landscapeIconSize = a.getFloat(R.styleable.ProfileDisplayOption_landscapeIconSize,
                     iconSize);
+            twoPanelPortraitIconSize = a.getFloat(
+                    R.styleable.ProfileDisplayOption_twoPanelPortraitIconSize, iconSize);
+            twoPanelLandscapeIconSize = a.getFloat(
+                    R.styleable.ProfileDisplayOption_twoPanelLandscapeIconSize,
+                    landscapeIconSize);
             iconTextSize = a.getFloat(R.styleable.ProfileDisplayOption_iconTextSize, 0);
             landscapeIconTextSize = a.getFloat(
                     R.styleable.ProfileDisplayOption_landscapeIconTextSize, iconTextSize);
+            twoPanelPortraitIconTextSize = a.getFloat(
+                    R.styleable.ProfileDisplayOption_twoPanelPortraitIconTextSize, iconTextSize);
+            twoPanelLandscapeIconTextSize = a.getFloat(
+                    R.styleable.ProfileDisplayOption_twoPanelLandscapeIconTextSize,
+                    landscapeIconTextSize);
 
             allAppsIconSize = a.getFloat(R.styleable.ProfileDisplayOption_allAppsIconSize,
                     iconSize);
@@ -704,9 +730,13 @@
         private DisplayOption multiply(float w) {
             iconSize *= w;
             landscapeIconSize *= w;
+            twoPanelPortraitIconSize *= w;
+            twoPanelLandscapeIconSize *= w;
             allAppsIconSize *= w;
             iconTextSize *= w;
             landscapeIconTextSize *= w;
+            twoPanelPortraitIconTextSize *= w;
+            twoPanelLandscapeIconTextSize *= w;
             allAppsIconTextSize *= w;
             minCellHeight *= w;
             minCellWidth *= w;
@@ -717,9 +747,13 @@
         private DisplayOption add(DisplayOption p) {
             iconSize += p.iconSize;
             landscapeIconSize += p.landscapeIconSize;
+            twoPanelPortraitIconSize += p.twoPanelPortraitIconSize;
+            twoPanelLandscapeIconSize += p.twoPanelLandscapeIconSize;
             allAppsIconSize += p.allAppsIconSize;
             iconTextSize += p.iconTextSize;
             landscapeIconTextSize += p.landscapeIconTextSize;
+            twoPanelPortraitIconTextSize += p.twoPanelPortraitIconTextSize;
+            twoPanelLandscapeIconTextSize += p.twoPanelLandscapeIconTextSize;
             allAppsIconTextSize += p.allAppsIconTextSize;
             minCellHeight += p.minCellHeight;
             minCellWidth += p.minCellWidth;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 696c308..1ebfda1 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1298,7 +1298,7 @@
             }
 
             if (!foundCellSpan) {
-                mWorkspace.onNoCellFound(layout);
+                mWorkspace.onNoCellFound(layout, info, /* logInstanceId= */ null);
                 return;
             }
 
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index ae42d74..3bfa1e2 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -63,6 +63,8 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.Toast;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
 import com.android.launcher3.anim.Interpolators;
@@ -82,6 +84,7 @@
 import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.logger.LauncherAtom;
+import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
 import com.android.launcher3.model.data.AppInfo;
@@ -1604,7 +1607,7 @@
 
             // Don't accept the drop if there's no room for the item
             if (!foundCell) {
-                onNoCellFound(dropTargetLayout);
+                onNoCellFound(dropTargetLayout, d.dragInfo, d.logInstanceId);
                 return false;
             }
         }
@@ -1906,7 +1909,7 @@
                             lp.cellX, lp.cellY, item.spanX, item.spanY);
                 } else {
                     if (!returnToOriginalCellToPreventShuffling) {
-                        onNoCellFound(dropTargetLayout);
+                        onNoCellFound(dropTargetLayout, d.dragInfo, d.logInstanceId);
                     }
                     if (mDragInfo.cell instanceof LauncherAppWidgetHostView) {
                         d.dragView.detachContentView(/* reattachToPreviousParent= */ true);
@@ -1974,10 +1977,16 @@
         }
     }
 
-    public void onNoCellFound(View dropTargetLayout) {
+    public void onNoCellFound(
+            View dropTargetLayout, ItemInfo itemInfo, @Nullable InstanceId logInstanceId) {
         int strId = mLauncher.isHotseatLayout(dropTargetLayout)
                 ? R.string.hotseat_out_of_space : R.string.out_of_space;
         Toast.makeText(mLauncher, mLauncher.getString(strId), Toast.LENGTH_SHORT).show();
+        StatsLogManager.StatsLogger logger = mStatsLogManager.logger().withItemInfo(itemInfo);
+        if (logInstanceId != null) {
+            logger = logger.withInstanceId(logInstanceId);
+        }
+        logger.log(LauncherEvent.LAUNCHER_ITEM_DROP_FAILED_INSUFFICIENT_SPACE);
     }
 
     /**
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 79e5b5d..d959ee2 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -486,7 +486,10 @@
         LAUNCHER_TURN_ON_WORK_APPS_TAP(838),
 
         @UiEvent(doc = "User tapped on 'Turn off work apps' button in all apps window.")
-        LAUNCHER_TURN_OFF_WORK_APPS_TAP(839)
+        LAUNCHER_TURN_OFF_WORK_APPS_TAP(839),
+
+        @UiEvent(doc = "Launcher item drop failed since there was not enough room on the screen.")
+        LAUNCHER_ITEM_DROP_FAILED_INSUFFICIENT_SPACE(872)
         ;
 
         // ADD MORE
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index f7c730a..29eefe2 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -53,6 +53,9 @@
     private static final int ENTER_ANIMATION_STAGGERED_DELAY = 150;
     private static final int ENTER_ANIMATION_DURATION = 400;
 
+    private static final int DOT_ACTIVE_ALPHA = 255;
+    private static final int DOT_INACTIVE_ALPHA = 128;
+
     // This value approximately overshoots to 1.5 times the original size.
     private static final float ENTER_ANIMATION_OVERSHOOT_TENSION = 4.9f;
 
@@ -75,8 +78,6 @@
 
     private final Paint mCirclePaint;
     private final float mDotRadius;
-    private final int mActiveColor;
-    private final int mInActiveColor;
     private final boolean mIsRtl;
 
     private int mNumPages;
@@ -110,12 +111,10 @@
 
         mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
         mCirclePaint.setStyle(Style.FILL);
+        mCirclePaint.setColor(Themes.getAttrColor(context, R.attr.folderPaginationColor));
         mDotRadius = getResources().getDimension(R.dimen.page_indicator_dot_size) / 2;
         setOutlineProvider(new MyOutlineProver());
 
-        mActiveColor = Themes.getColorAccent(context);
-        mInActiveColor = Themes.getAttrColor(context, android.R.attr.colorControlHighlight);
-
         mIsRtl = Utilities.isRtl(getResources());
     }
 
@@ -253,18 +252,18 @@
                 circleGap = -circleGap;
             }
             for (int i = 0; i < mEntryAnimationRadiusFactors.length; i++) {
-                mCirclePaint.setColor(i == mActivePage ? mActiveColor : mInActiveColor);
+                mCirclePaint.setAlpha(i == mActivePage ? DOT_ACTIVE_ALPHA : DOT_INACTIVE_ALPHA);
                 canvas.drawCircle(x, y, mDotRadius * mEntryAnimationRadiusFactors[i], mCirclePaint);
                 x += circleGap;
             }
         } else {
-            mCirclePaint.setColor(mInActiveColor);
+            mCirclePaint.setAlpha(DOT_INACTIVE_ALPHA);
             for (int i = 0; i < mNumPages; i++) {
                 canvas.drawCircle(x, y, mDotRadius, mCirclePaint);
                 x += circleGap;
             }
 
-            mCirclePaint.setColor(mActiveColor);
+            mCirclePaint.setAlpha(DOT_ACTIVE_ALPHA);
             canvas.drawRoundRect(getActiveRect(), mDotRadius, mDotRadius, mCirclePaint);
         }
     }
diff --git a/src/com/android/launcher3/testing/TestLogging.java b/src/com/android/launcher3/testing/TestLogging.java
index 6cdd3ca..103b565 100644
--- a/src/com/android/launcher3/testing/TestLogging.java
+++ b/src/com/android/launcher3/testing/TestLogging.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.testing;
 
 import android.util.Log;
+import android.view.InputEvent;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 
@@ -48,17 +49,24 @@
         }
     }
 
+    private static void registerEventNotFromTest(InputEvent event) {
+        if (!sHadEventsNotFromTest && event.getDeviceId() != -1) {
+            sHadEventsNotFromTest = true;
+            Log.d(TestProtocol.PERMANENT_DIAG_TAG, "First event not from test: " + event);
+        }
+    }
+
     public static void recordKeyEvent(String sequence, String message, KeyEvent event) {
         if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
             recordEventSlow(sequence, message + ": " + event);
-            if (event.getDeviceId() != -1) sHadEventsNotFromTest = true;
+            registerEventNotFromTest(event);
         }
     }
 
     public static void recordMotionEvent(String sequence, String message, MotionEvent event) {
         if (Utilities.IS_RUNNING_IN_TEST_HARNESS && event.getAction() != MotionEvent.ACTION_MOVE) {
             recordEventSlow(sequence, message + ": " + event);
-            if (event.getDeviceId() != -1) sHadEventsNotFromTest = true;
+            registerEventNotFromTest(event);
         }
     }
 
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 686dfde..1c5b31b 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -118,5 +118,5 @@
     public static final String WORK_PROFILE_REMOVED = "b/159671700";
     public static final String FALLBACK_ACTIVITY_NO_SET = "b/181019015";
     public static final String THIRD_PARTY_LAUNCHER_NOT_SET = "b/187080582";
-    public static final String MOTION_PAUSE_TIMEOUT = "b/194114179";
+    public static final String TASK_VIEW_ID_CRASH = "b/195430732";
 }
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index 1eb79ad..4c0bfde 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -25,23 +25,27 @@
 import android.util.AttributeSet;
 import android.view.View;
 
+import androidx.annotation.NonNull;
 import androidx.core.graphics.ColorUtils;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.util.SystemUiController;
 
+import java.util.ArrayList;
+
 /**
  * Simple scrim which draws a flat color
  */
 public class ScrimView extends View implements Insettable {
     private static final float STATUS_BAR_COLOR_FORCE_UPDATE_THRESHOLD = 0.9f;
 
+    private final ArrayList<Runnable> mOpaquenessListeners = new ArrayList<>(1);
     private SystemUiController mSystemUiController;
-
     private ScrimDrawingController mDrawingController;
     private int mBackgroundColor;
     private boolean mIsVisible = true;
+    private boolean mLastDispatchedOpaqueness;
 
     public ScrimView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -60,6 +64,7 @@
     @Override
     protected boolean onSetAlpha(int alpha) {
         updateSysUiColors();
+        dispatchVisibilityListenersIfNeeded();
         return super.onSetAlpha(alpha);
     }
 
@@ -67,6 +72,7 @@
     public void setBackgroundColor(int color) {
         mBackgroundColor = color;
         updateSysUiColors();
+        dispatchVisibilityListenersIfNeeded();
         super.setBackgroundColor(color);
     }
 
@@ -74,6 +80,7 @@
     public void onVisibilityAggregated(boolean isVisible) {
         super.onVisibilityAggregated(isVisible);
         mIsVisible = isVisible;
+        dispatchVisibilityListenersIfNeeded();
     }
 
     public boolean isFullyOpaque() {
@@ -108,6 +115,17 @@
         }
     }
 
+    private void dispatchVisibilityListenersIfNeeded() {
+        boolean fullyOpaque = isFullyOpaque();
+        if (mLastDispatchedOpaqueness == fullyOpaque) {
+            return;
+        }
+        mLastDispatchedOpaqueness = fullyOpaque;
+        for (int i = 0; i < mOpaquenessListeners.size(); i++) {
+            mOpaquenessListeners.get(i).run();
+        }
+    }
+
     private SystemUiController getSystemUiController() {
         if (mSystemUiController == null) {
             mSystemUiController = BaseActivity.fromContext(getContext()).getSystemUiController();
@@ -136,6 +154,22 @@
     }
 
     /**
+     * Registers a listener to be notified of whether the scrim is occluding other UI elements.
+     * @see #isFullyOpaque()
+     */
+    public void addOpaquenessListener(@NonNull Runnable listener) {
+        mOpaquenessListeners.add(listener);
+    }
+
+    /**
+     * Removes previously registered listener.
+     * @see #addOpaquenessListener(Runnable)
+     */
+    public void removeOpaquenessListener(@NonNull Runnable listener) {
+        mOpaquenessListeners.remove(listener);
+    }
+
+    /**
      * A Utility interface allowing for other surfaces to draw on ScrimView
      */
     public interface ScrimDrawingController {
diff --git a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
index d12fe74..241c937 100644
--- a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
@@ -49,6 +49,8 @@
      */
     private final PointF mTranslationForCentering = new PointF(0, 0);
 
+    private final PointF mTranslationForMoveFromCenterAnimation = new PointF(0, 0);
+
     private final PointF mTranslationForReorderBounce = new PointF(0, 0);
     private final PointF mTranslationForReorderPreview = new PointF(0, 0);
     private float mScaleForReorderBounce = 1f;
@@ -167,9 +169,9 @@
 
     private void updateTranslation() {
         super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x
-                + mTranslationForCentering.x);
+                + mTranslationForCentering.x + mTranslationForMoveFromCenterAnimation.x);
         super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y
-                + mTranslationForCentering.y);
+                + mTranslationForCentering.y + mTranslationForMoveFromCenterAnimation.y);
     }
 
     public void setTranslationForCentering(float x, float y) {
@@ -177,6 +179,11 @@
         updateTranslation();
     }
 
+    public void setTranslationForMoveFromCenterAnimation(float x, float y) {
+        mTranslationForMoveFromCenterAnimation.set(x, y);
+        updateTranslation();
+    }
+
     public void setReorderBounceOffset(float x, float y) {
         mTranslationForReorderBounce.set(x, y);
         updateTranslation();
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index d3a0190..57a6d3f 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -85,7 +85,7 @@
         setBackgroundResource(R.drawable.pending_widget_bg);
         setWillNotDraw(false);
 
-        updateAppWidget(null);
+        super.updateAppWidget(null);
         setOnClickListener(ItemClickHandler.INSTANCE);
 
         if (info.pendingItemInfo == null) {
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 167eb09..bd444db 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -325,10 +325,10 @@
             return;
         }
         if (drawable != null) {
+            // Scale down the preview size if it's wider than the cell.
             float scale = 1f;
-            if (getWidth() > 0 && getHeight() > 0) {
-                // Scale down the preview size if it's wider than the cell.
-                float maxWidth = getWidth();
+            if (mTargetPreviewWidth > 0) {
+                float maxWidth = mTargetPreviewWidth;
                 float previewWidth = drawable.getIntrinsicWidth() * mPreviewContainerScale;
                 scale = Math.min(maxWidth / previewWidth, 1);
             }
@@ -483,6 +483,20 @@
         mAppWidgetHostViewPreview.measure(
                 makeMeasureSpec(MAX_MEASURE_SPEC_DIMENSION, MeasureSpec.UNSPECIFIED),
                 makeMeasureSpec(MAX_MEASURE_SPEC_DIMENSION, MeasureSpec.UNSPECIFIED));
+        if (mRemoteViewsPreview != null) {
+            // If RemoteViews contains multiple sizes, the best fit sized RemoteViews will be
+            // selected in onLayout. To work out the right measurement, let's layout and then
+            // measure again.
+            mAppWidgetHostViewPreview.layout(
+                    /* left= */ 0,
+                    /* top= */ 0,
+                    /* right= */ mTargetPreviewWidth,
+                    /* bottom= */ mTargetPreviewHeight);
+            mAppWidgetHostViewPreview.measure(
+                    makeMeasureSpec(mTargetPreviewWidth, MeasureSpec.UNSPECIFIED),
+                    makeMeasureSpec(mTargetPreviewHeight, MeasureSpec.UNSPECIFIED));
+
+        }
         View widgetContent = mAppWidgetHostViewPreview.getChildAt(0);
         int appWidgetContentWidth = widgetContent.getMeasuredWidth();
         int appWidgetContentHeight = widgetContent.getMeasuredHeight();
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index 2e8048a..dad4f2b 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -27,7 +27,6 @@
 import com.android.launcher3.tapl.Widget;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.TestViewHelpers;
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
 import com.android.launcher3.util.rule.ShellCommandRule;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 
@@ -81,7 +80,6 @@
      */
     @Test
     @PortraitLandscape
-    @ScreenRecord //b/195263971
     public void testDragCustomShortcut() throws Throwable {
         clearHomescreen();
         mDevice.pressHome();
diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
index 60529a4..0b60ffc 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
@@ -44,7 +44,7 @@
             @Override
             public void evaluate() throws Throwable {
                 try {
-                    base.evaluate();
+                    FailureWatcher.super.apply(base, description).evaluate();
                 } finally {
                     if (mLauncher.hadNontestEvents()) {
                         throw new AssertionError(
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index ded4282..065af00 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -66,7 +66,6 @@
 
 import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.testing.TestProtocol;
-import com.android.systemui.shared.system.ContextUtils;
 import com.android.systemui.shared.system.QuickStepContract;
 
 import org.junit.Assert;
@@ -80,6 +79,7 @@
 import java.util.Deque;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -96,7 +96,7 @@
     private static final String TAG = "Tapl";
     private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 20;
     private static final int GESTURE_STEP_MS = 16;
-    private static final long FORCE_PAUSE_TIMEOUT_MS = 700;
+    private static final long FORCE_PAUSE_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(2);
 
     static final Pattern EVENT_TOUCH_DOWN = getTouchEventPattern("ACTION_DOWN");
     static final Pattern EVENT_TOUCH_UP = getTouchEventPattern("ACTION_UP");
@@ -245,16 +245,12 @@
         ComponentName cn = new ComponentName(pi.packageName, pi.name);
 
         if (pm.getComponentEnabledSetting(cn) != COMPONENT_ENABLED_STATE_ENABLED) {
-            if (TestHelpers.isInLauncherProcess()) {
+            mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(
+                    android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
+            try {
                 pm.setComponentEnabledSetting(cn, COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
-            } else {
-                try {
-                    final int userId = ContextUtils.getUserId(getContext());
-                    mDevice.executeShellCommand(
-                            "pm enable --user " + userId + " " + cn.flattenToString());
-                } catch (IOException e) {
-                    fail(e.toString());
-                }
+            } finally {
+                mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
             }
         }
     }
@@ -304,7 +300,7 @@
 
     public boolean isTwoPanels() {
         return getTestInfo(TestProtocol.REQUEST_IS_TWO_PANELS)
-            .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+                .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
     private void setForcePauseTimeout(long timeout) {