Merging ub-launcher3-qt-dev, build 5582981

Test: Manual

Bug:118758696 Define grid display options
Bug:123900446 App to home animation should zoom into the app icon
Bug:124510042 [Gesture Nav] Home animation polish
Bug:129985827 [Fully Gestural Navigation] Delay Recents animation when swiping up
Bug:130020567 Handle fullscreen correctly for quick switch
Bug:130193889 Half screenshot in the app switcher
Bug:130292844 App close animation is slightly off for Squircle adaptive icons
Bug:130451254 QSB/Folder icon color depends on three variants of wallpaper shade on light/dark theme
Bug:130521490 Dock bar is too close to the edge when in landscape home screen
Bug:130689544 Unable to quickswitch apps under landscape mode
Bug:131231579 Long-swipe up to app drawer goes to overview
Bug:131360075 [Gesture Nav] Polish/finish landscape
Bug:132269977 Pixel launcher crashed while swiping up home button after disabling an app
Bug:132309376 Launcher held ION buffers after clearing all apps in Recent Apps
Bug:132458092 Recents Go: Transparent apps have double vision for apps to recents
Bug:132584688 Disallowed association between launcher and aiai
Bug:132816938 [Please fix ASAP] Failed test: sometimes home screen doesn't have all_apps
Bug:132892578 Split screen option missing for recent apps after restarting / upgrading the device.
Bug:132898688 NPE in 'com.android.quickstep.views.TaskThumbnailView com.android.quickstep.views.TaskView.getThumbnail()'
Bug:132908798 Gesture haptic double triggers
Bug:132916535 Handle multi-touch
Bug:74500048 Fix launcher transition while in startActivityForResult

Change-Id: I68d6af2ec47f2b379f5607685b2257029ab07a91
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 819e6bc..ef5bb26 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -154,13 +154,13 @@
 
         <!--
         The content provider for exposing various launcher grid options.
-        TODO: Enable when all apps columns are correct
         TODO: Add proper permissions
+        -->
         <provider
             android:name="com.android.launcher3.graphics.GridOptionsProvider"
             android:authorities="${packageName}.grid_control"
+            android:enabled="false"
             android:exported="true" />
-        -->
 
         <!--
         The settings activity. To extend point settings_fragment_name to appropriate fragment class
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 70f0c01..b031ffb 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -20,7 +20,7 @@
 <manifest
     xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.launcher3">
-    <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="25"/>
+    <uses-sdk android:targetSdkVersion="29" android:minSdkVersion="25"/>
     <!--
     Manifest entries specific to Launcher3. This is merged with AndroidManifest-common.xml.
     Refer comments around specific entries on how to extend individual components.
diff --git a/go/quickstep/res/layout/task_item_view.xml b/go/quickstep/res/layout/task_item_view.xml
index 699178d..ab2cf28 100644
--- a/go/quickstep/res/layout/task_item_view.xml
+++ b/go/quickstep/res/layout/task_item_view.xml
@@ -18,7 +18,8 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="@dimen/task_item_height"
-    android:orientation="horizontal">
+    android:orientation="horizontal"
+    android:clipChildren="false">
     <com.android.quickstep.views.TaskThumbnailIconView
         android:id="@+id/task_icon_and_thumbnail"
         android:layout_width="match_parent"
diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
index bcb6343..87b4d4e 100644
--- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
+++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
@@ -38,6 +38,7 @@
 import android.content.res.Resources;
 import android.graphics.Matrix;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.util.ArraySet;
 import android.util.AttributeSet;
@@ -768,6 +769,7 @@
         Rect endRect = new Rect();
         thumbnailView.getGlobalVisibleRect(endRect);
         Rect appBounds = appTarget.sourceContainerBounds;
+        RectF currentAppRect = new RectF();
 
         SyncRtSurfaceTransactionApplierCompat surfaceApplier =
                 new SyncRtSurfaceTransactionApplierCompat(this);
@@ -810,17 +812,30 @@
 
             @Override
             public void onUpdate(float percent) {
-                appMatrix.preScale(mScaleX.value, mScaleY.value,
+                Matrix m = new Matrix();
+                m.preScale(mScaleX.value, mScaleY.value,
                         appBounds.width() / 2.0f, appBounds.height() / 2.0f);
-                appMatrix.postTranslate(mTranslationX.value, mTranslationY.value);
-
+                m.postTranslate(mTranslationX.value, mTranslationY.value);
+                appMatrix.preConcat(m);
                 params[1] = new SurfaceParams(appTarget.leash, mAlpha.value, appMatrix,
                         null /* windowCrop */, getLayer(appTarget, boostedMode),
                         0 /* cornerRadius */);
                 surfaceApplier.scheduleApply(params);
+
+                m.mapRect(currentAppRect, new RectF(appBounds));
+                setViewToRect(thumbnailView, new RectF(endRect), currentAppRect);
                 appMatrix.reset();
             }
         });
+        remoteAppAnim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                thumbnailView.setTranslationY(0);
+                thumbnailView.setTranslationX(0);
+                thumbnailView.setScaleX(1);
+                thumbnailView.setScaleY(1);
+            }
+        });
         anim.play(remoteAppAnim);
     }
 
@@ -886,6 +901,27 @@
         }
     }
 
+    /**
+     * Set view properties so that the view fits to the target rect.
+     *
+     * @param view view to set
+     * @param origRect original rect that view was located
+     * @param targetRect rect to set to
+     */
+    private void setViewToRect(View view, RectF origRect, RectF targetRect) {
+        float dX = targetRect.left - origRect.left;
+        float dY = targetRect.top - origRect.top;
+        view.setTranslationX(dX);
+        view.setTranslationY(dY);
+
+        float scaleX = targetRect.width() / origRect.width();
+        float scaleY = targetRect.height() / origRect.height();
+        view.setPivotX(0);
+        view.setPivotY(0);
+        view.setScaleX(scaleX);
+        view.setScaleY(scaleY);
+    }
+
     @Override
     public void setInsets(Rect insets) {
         mInsets = insets;
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 97fc284..be275e0 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -85,6 +85,13 @@
             android:name="com.android.launcher3.uioverrides.dynamicui.WallpaperManagerCompatVL$ColorExtractionService"
             tools:node="remove" />
 
+        <activity
+            android:name="com.android.launcher3.proxy.ProxyActivityStarter"
+            android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen"
+            android:launchMode="singleTask"
+            android:clearTaskOnLaunch="true"
+            android:exported="false" />
+
     </application>
 
 </manifest>
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
index 948f39e..d3042cf 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
@@ -19,7 +19,6 @@
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.quickstep.logging.UserEventDispatcherExtension.ALL_APPS_PREDICTION_TIPS;
 
-import android.app.ActivityManager;
 import android.content.Context;
 import android.graphics.CornerPathEffect;
 import android.graphics.Paint;
@@ -39,6 +38,7 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.FloatingHeaderView;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.compat.UserManagerCompat;
@@ -152,7 +152,7 @@
                 || !launcher.isInState(ALL_APPS)
                 || hasSeenAllAppsTip(launcher)
                 || UserManagerCompat.getInstance(launcher).isDemoUser()
-                || ActivityManager.isRunningInTestHarness()) {
+                || Utilities.IS_RUNNING_IN_TEST_HARNESS) {
             return false;
         }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
index fa28106..af67e1b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
@@ -27,11 +27,13 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
 import android.os.UserHandle;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
 import com.android.launcher3.model.AppLaunchTracker;
@@ -97,6 +99,7 @@
                 new AppPredictionContext.Builder(mContext)
                         .setUiSurface(client.id)
                         .setPredictedTargetCount(count)
+                        .setExtras(getAppPredictionContextExtras(client))
                         .build());
         predictor.registerPredictionUpdates(mContext.getMainExecutor(),
                 PredictionUiStateManager.INSTANCE.get(mContext).appPredictorCallback(client));
@@ -104,6 +107,15 @@
         return predictor;
     }
 
+    /**
+     * Override to add custom extras.
+     */
+    @WorkerThread
+    @Nullable
+    public Bundle getAppPredictionContextExtras(Client client){
+        return null;
+    }
+
     @WorkerThread
     private boolean handleMessage(Message msg) {
         switch (msg.what) {
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 3a2958d..ebae1cd 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
@@ -42,6 +42,7 @@
 import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.QuickSwitchTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
@@ -148,6 +149,9 @@
             if (launcher.getDeviceProfile().isVerticalBarLayout()) {
                 list.add(new OverviewToAllAppsTouchController(launcher));
                 list.add(new LandscapeEdgeSwipeController(launcher));
+                if (mode.hasGestures) {
+                    list.add(new TransposedQuickSwitchTouchController(launcher));
+                }
             } else {
                 list.add(new PortraitStatesTouchController(launcher,
                         mode.hasGestures /* allowDragToOverview */));
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index c3a7698..1d36d1a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -18,6 +18,7 @@
 import static com.android.launcher3.LauncherState.RECENTS_CLEAR_ALL_BUTTON;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
+import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
 
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
@@ -64,6 +65,7 @@
             }
         }
         setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, state.getVisibleElements(mLauncher));
+        mRecentsView.setFullscreenProgress(state.getOverviewFullscreenProgress());
     }
 
     @Override
@@ -95,7 +97,10 @@
             builder.addOnFinishRunnable(() -> mRecentsView.setHintVisibility(1f));
         }
 
-        setAlphas(config.getPropertySetter(builder), toState.getVisibleElements(mLauncher));
+        PropertySetter propertySetter = config.getPropertySetter(builder);
+        setAlphas(propertySetter, toState.getVisibleElements(mLauncher));
+        float fullscreenProgress = toState.getOverviewFullscreenProgress();
+        propertySetter.setFloat(mRecentsView, FULLSCREEN_PROGRESS, fullscreenProgress, LINEAR);
     }
 
     private void setAlphas(PropertySetter propertySetter, int visibleElements) {
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 140e45c..f429ce5 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
@@ -17,14 +17,13 @@
 
 import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
 
-import android.os.RemoteException;
-
 import com.android.launcher3.Launcher;
 import com.android.launcher3.allapps.AllAppsTransitionController;
-import com.android.quickstep.RecentsModel;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.views.RecentsView;
-import com.android.systemui.shared.recents.ISystemUiProxy;
+import com.android.quickstep.views.TaskView;
 
 /**
  * State indicating that the Launcher is behind an app
@@ -35,7 +34,11 @@
             FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI | FLAG_DISABLE_ACCESSIBILITY;
 
     public BackgroundAppState(int id) {
-        super(id, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
+        this(id, LauncherLogProto.ContainerType.TASKSWITCHER);
+    }
+
+    protected BackgroundAppState(int id, int logContainer) {
+        super(id, logContainer, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
     }
 
     @Override
@@ -55,23 +58,17 @@
     public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
         // Initialize the recents view scale to what it would be when starting swipe up
         RecentsView recentsView = launcher.getOverviewPanel();
-        recentsView.getTaskSize(sTempRect);
-        int appWidth = launcher.getDragLayer().getWidth();
-        if (recentsView.shouldUseMultiWindowTaskSizeStrategy()) {
-            ISystemUiProxy sysUiProxy = RecentsModel.INSTANCE.get(launcher).getSystemUiProxy();
-            if (sysUiProxy != null) {
-                try {
-                    // Try to use the actual non-minimized app width (launcher will be resized to
-                    // the non-minimized bounds, which differs from the app width in landscape
-                    // multi-window mode
-                    appWidth = sysUiProxy.getNonMinimizedSplitScreenSecondaryBounds().width();
-                } catch (RemoteException e) {
-                    // Ignore, fall back to just using the drag layer width
-                }
-            }
+        if (recentsView.getTaskViewCount() == 0) {
+            return super.getOverviewScaleAndTranslation(launcher);
         }
-        float scale = (float) appWidth / sTempRect.width();
-        return new ScaleAndTranslation(scale, 0f, 0f);
+        TaskView dummyTask = recentsView.getTaskViewAt(recentsView.getCurrentPage());
+        return recentsView.getTempClipAnimationHelper().updateForFullscreenOverview(dummyTask)
+                .getScaleAndTranslation();
+    }
+
+    @Override
+    public float getOverviewFullscreenProgress() {
+        return 1;
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
index c26a1d0..ed511f5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
@@ -15,11 +15,8 @@
  */
 package com.android.launcher3.uioverrides.states;
 
-import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
-
 import com.android.launcher3.Launcher;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 
@@ -28,23 +25,10 @@
  * quick switching from launcher; quick switching from an app uses WindowTransformSwipeHelper.
  * @see com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget#NEW_TASK
  */
-public class QuickSwitchState extends OverviewState {
-    private static final int STATE_FLAGS =
-            FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI | FLAG_DISABLE_ACCESSIBILITY;
+public class QuickSwitchState extends BackgroundAppState {
 
     public QuickSwitchState(int id) {
-        super(id, LauncherLogProto.ContainerType.APP, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
-    }
-
-    @Override
-    public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
-        RecentsView recentsView = launcher.getOverviewPanel();
-        if (recentsView.getTaskViewCount() == 0) {
-            return super.getOverviewScaleAndTranslation(launcher);
-        }
-        TaskView dummyTask = recentsView.getTaskViewAt(0);
-        ClipAnimationHelper clipAnimationHelper = new ClipAnimationHelper(launcher);
-        return clipAnimationHelper.getOverviewFullscreenScaleAndTranslation(dummyTask);
+        super(id, LauncherLogProto.ContainerType.APP);
     }
 
     @Override
@@ -56,11 +40,6 @@
     }
 
     @Override
-    public float getVerticalProgress(Launcher launcher) {
-        return BACKGROUND_APP.getVerticalProgress(launcher);
-    }
-
-    @Override
     public int getVisibleElements(Launcher launcher) {
         return NONE;
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
index 6358109..7a6cd2d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
@@ -43,9 +43,11 @@
 public class FlingAndHoldTouchController extends PortraitStatesTouchController {
 
     private static final long PEEK_ANIM_DURATION = 100;
+    private static final float MAX_DISPLACEMENT_PERCENT = 0.75f;
 
     private final MotionPauseDetector mMotionPauseDetector;
     private final float mMotionPauseMinDisplacement;
+    private final float mMotionPauseMaxDisplacement;
 
     private AnimatorSet mPeekAnim;
 
@@ -53,6 +55,7 @@
         super(l, false /* allowDragToOverview */);
         mMotionPauseDetector = new MotionPauseDetector(l);
         mMotionPauseMinDisplacement = ViewConfiguration.get(l).getScaledTouchSlop();
+        mMotionPauseMaxDisplacement = getShiftRange() * MAX_DISPLACEMENT_PERCENT;
     }
 
     @Override
@@ -101,7 +104,9 @@
 
     @Override
     public boolean onDrag(float displacement, MotionEvent event) {
-        mMotionPauseDetector.setDisallowPause(-displacement < mMotionPauseMinDisplacement);
+        float upDisplacement = -displacement;
+        mMotionPauseDetector.setDisallowPause(upDisplacement < mMotionPauseMinDisplacement
+                || upDisplacement > mMotionPauseMaxDisplacement);
         mMotionPauseDetector.addPosition(displacement, event.getEventTime());
         return super.onDrag(displacement, event);
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java
index 82ab34b..73f328b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java
@@ -24,6 +24,7 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.TouchInteractionService;
 import com.android.quickstep.views.RecentsView;
@@ -52,7 +53,7 @@
             // In all-apps only listen if the container cannot scroll itself
             return mLauncher.getAppsView().shouldContainerScroll(ev);
         } else if (mLauncher.isInState(NORMAL)) {
-            return true;
+            return (ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) == 0;
         } else if (mLauncher.isInState(OVERVIEW)) {
             RecentsView rv = mLauncher.getOverviewPanel();
             return ev.getY() > (rv.getBottom() - rv.getPaddingBottom());
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index a1a790c..e1dd124 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -57,7 +57,11 @@
     private @Nullable TaskView mTaskToLaunch;
 
     public QuickSwitchTouchController(Launcher launcher) {
-        super(launcher, SwipeDetector.HORIZONTAL);
+        this(launcher, SwipeDetector.HORIZONTAL);
+    }
+
+    protected QuickSwitchTouchController(Launcher l, SwipeDetector.Direction dir) {
+        super(l, dir);
     }
 
     @Override
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 8b4aa07..8e32bb3 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
@@ -18,8 +18,8 @@
 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.QUICKSTEP_SPRINGS;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -231,7 +231,8 @@
         } else {
             mFlingBlockCheck.onEvent();
         }
-        mCurrentAnimation.setPlayFraction(totalDisplacement * mProgressMultiplier);
+        mCurrentAnimation.setPlayFraction(Utilities.boundToRange(
+                totalDisplacement * mProgressMultiplier, 0, 1));
 
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             if (mRecentsView.getCurrentPage() != 0 || isGoingUp) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java
new file mode 100644
index 0000000..f1e4041
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java
@@ -0,0 +1,44 @@
+/*
+ * 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.uioverrides.touchcontrollers;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.touch.SwipeDetector;
+
+public class TransposedQuickSwitchTouchController extends QuickSwitchTouchController {
+
+    public TransposedQuickSwitchTouchController(Launcher launcher) {
+        super(launcher, SwipeDetector.VERTICAL);
+    }
+
+    @Override
+    protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
+        return super.getTargetState(fromState,
+                isDragTowardPositive ^ mLauncher.getDeviceProfile().isSeascape());
+    }
+
+    @Override
+    protected float initCurrentAnimation(int animComponents) {
+        float multiplier = super.initCurrentAnimation(animComponents);
+        return mLauncher.getDeviceProfile().isSeascape() ? multiplier : -multiplier;
+    }
+
+    @Override
+    protected float getShiftRange() {
+        return mLauncher.getDeviceProfile().heightPx / 2f;
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
index c33d25c..90b5536 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -55,7 +55,6 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.SysUINavigationMode.Mode;
-import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
@@ -118,16 +117,15 @@
         }
         final RectF iconLocation = new RectF();
         boolean canUseWorkspaceView = workspaceView != null && workspaceView.isAttachedToWindow();
-        final FloatingIconView floatingView = canUseWorkspaceView
-                ? FloatingIconView.getFloatingIconView(activity, workspaceView,
-                true /* hideOriginal */, iconLocation, false /* isOpening */, null /* recycle */)
+        FloatingIconView floatingIconView = canUseWorkspaceView
+                ? recentsView.getFloatingIconView(activity, workspaceView, iconLocation)
                 : null;
 
         return new HomeAnimationFactory() {
             @Nullable
             @Override
             public View getFloatingView() {
-                return floatingView;
+                return floatingIconView;
             }
 
             @NonNull
@@ -186,6 +184,12 @@
             @Override
             public void createActivityController(long transitionLength) {
                 createActivityControllerInternal(activity, fromState, transitionLength, callback);
+                // Creating the activity controller animation sometimes reapplies the launcher state
+                // (because we set the animation as the current state animation), so we reapply the
+                // attached state here as well to ensure recents is shown/hidden appropriately.
+                if (SysUINavigationMode.getMode(activity) == Mode.NO_BUTTON) {
+                    setRecentsAttachedToAppWindow(mIsAttachedToWindow, false);
+                }
             }
 
             @Override
@@ -267,7 +271,7 @@
                     endState.getVerticalProgress(activity));
             anim.play(shiftAnim);
         }
-        playScaleDownAnim(anim, activity, endState);
+        playScaleDownAnim(anim, activity, fromState, endState);
 
         anim.setDuration(transitionLength * 2);
         AnimatorPlaybackController controller =
@@ -293,7 +297,7 @@
     /**
      * Scale down recents from the center task being full screen to being in overview.
      */
-    private void playScaleDownAnim(AnimatorSet anim, Launcher launcher,
+    private void playScaleDownAnim(AnimatorSet anim, Launcher launcher, LauncherState fromState,
             LauncherState endState) {
         RecentsView recentsView = launcher.getOverviewPanel();
         TaskView v = recentsView.getTaskViewAt(recentsView.getCurrentPage());
@@ -301,19 +305,23 @@
             return;
         }
 
-        ClipAnimationHelper clipHelper = new ClipAnimationHelper(launcher);
         LauncherState.ScaleAndTranslation fromScaleAndTranslation
-                = clipHelper.getOverviewFullscreenScaleAndTranslation(v);
+                = fromState.getOverviewScaleAndTranslation(launcher);
         LauncherState.ScaleAndTranslation endScaleAndTranslation
                 = endState.getOverviewScaleAndTranslation(launcher);
+        float fromFullscreenProgress = fromState.getOverviewFullscreenProgress();
+        float endFullscreenProgress = endState.getOverviewFullscreenProgress();
 
         Animator scale = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY,
                 fromScaleAndTranslation.scale, endScaleAndTranslation.scale);
         Animator translateY = ObjectAnimator.ofFloat(recentsView, TRANSLATION_Y,
                 fromScaleAndTranslation.translationY, endScaleAndTranslation.translationY);
+        Animator applyFullscreenProgress = ObjectAnimator.ofFloat(recentsView,
+                RecentsView.FULLSCREEN_PROGRESS, fromFullscreenProgress, endFullscreenProgress);
         scale.setInterpolator(LINEAR);
         translateY.setInterpolator(LINEAR);
-        anim.playTogether(scale, translateY);
+        applyFullscreenProgress.setInterpolator(LINEAR);
+        anim.playTogether(scale, translateY, applyFullscreenProgress);
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
index 194d073..5a039cd 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
@@ -22,6 +22,7 @@
 import com.android.quickstep.util.RecentsAnimationListenerSet;
 import com.android.quickstep.util.SwipeAnimationTargetSet;
 import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
+import java.io.PrintWriter;
 
 /**
  * Utility class used to store state information shared across multiple transitions.
@@ -134,4 +135,13 @@
         nextRunningTaskId = -1;
         goingToLauncher = false;
     }
+
+    public void dump(String prefix, PrintWriter pw) {
+        pw.println(prefix + "goingToLauncher=" + goingToLauncher);
+        pw.println(prefix + "canGestureBeContinued=" + canGestureBeContinued);
+        pw.println(prefix + "recentsAnimationFinishInterrupted=" + recentsAnimationFinishInterrupted);
+        pw.println(prefix + "nextRunningTaskId=" + nextRunningTaskId);
+        pw.println(prefix + "lastAnimationCancelled=" + mLastAnimationCancelled);
+        pw.println(prefix + "lastAnimationRunning=" + mLastAnimationRunning);
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
index 2c919b3..213c5d3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
@@ -226,7 +226,7 @@
             // TODO(b/118266305): Temporarily disable splitscreen for secondary display while new
             // implementation is enabled
             return !activity.getDeviceProfile().isMultiWindowMode
-                    && displayId == DEFAULT_DISPLAY;
+                    && (displayId == -1 || displayId == DEFAULT_DISPLAY);
         }
 
         @Override
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 128fd45..0fd74bb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -84,9 +84,32 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
 import java.util.List;
 
 /**
+ * Wrapper around a list for processing arguments.
+ */
+class ArgList extends LinkedList<String> {
+    public ArgList(List<String> l) {
+        super(l);
+    }
+
+    public String peekArg() {
+        return peekFirst();
+    }
+
+    public String nextArg() {
+        return pollFirst().toLowerCase();
+    }
+
+    public String nextArgExact() {
+        return pollFirst();
+    }
+}
+
+/**
  * Service connected by system-UI for handling touch interaction.
  */
 @TargetApi(Build.VERSION_CODES.Q)
@@ -439,12 +462,18 @@
         mUncheckedConsumer.onMotionEvent(event);
     }
 
+    private boolean validSystemUiFlags() {
+        return (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
+                && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0;
+    }
+
+    private boolean topTaskLocked() {
+        return ActivityManagerWrapper.getInstance().isLockToAppActive();
+    }
 
     private InputConsumer newConsumer(boolean useSharedState, MotionEvent event) {
-        boolean validSystemUIFlags = (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
-                && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0;
-        boolean topTaskLocked = ActivityManagerWrapper.getInstance().isLockToAppActive();
-        boolean isInValidSystemUiState = validSystemUIFlags && !topTaskLocked;
+        boolean topTaskLocked = topTaskLocked();
+        boolean isInValidSystemUiState = validSystemUiFlags() && !topTaskLocked;
 
         if (!mIsUserUnlocked) {
             if (isInValidSystemUiState) {
@@ -476,7 +505,7 @@
             if ((mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0) {
                 base = new AccessibilityInputConsumer(this, mISystemUiProxy,
                         (mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0, base,
-                        mInputMonitorCompat);
+                        mInputMonitorCompat, mSwipeTouchRegion);
             }
         }
         return base;
@@ -527,7 +556,7 @@
         return new OtherActivityInputConsumer(this, runningTaskInfo, mRecentsModel,
                 mOverviewComponentObserver.getOverviewIntent(), activityControl,
                 shouldDefer, mOverviewCallbacks, mInputConsumer, this::onConsumerInactive,
-                mSwipeSharedState, mInputMonitorCompat);
+                mSwipeSharedState, mInputMonitorCompat, mSwipeTouchRegion);
     }
 
     /**
@@ -540,7 +569,55 @@
     }
 
     @Override
-    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        TOUCH_INTERACTION_LOG.dump("", pw);
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] rawArgs) {
+        if (rawArgs.length > 0 && Utilities.IS_DEBUG_DEVICE) {
+            ArgList args = new ArgList(Arrays.asList(rawArgs));
+            switch (args.nextArg()) {
+                case "cmd":
+                    if (args.peekArg() == null) {
+                        printAvailableCommands(pw);
+                    } else {
+                        onCommand(pw, args);
+                    }
+                    break;
+            }
+        } else {
+            // Dump everything
+            pw.println("TouchState:");
+            pw.println("  navMode=" + mMode);
+            pw.println("  validSystemUiFlags=" + validSystemUiFlags()
+                    + " flags=" + mSystemUiStateFlags);
+            pw.println("  topTaskLocked=" + topTaskLocked());
+            pw.println("  isDeviceLocked=" + mKM.isDeviceLocked());
+            pw.println("  screenPinned=" +
+                    ActivityManagerWrapper.getInstance().isScreenPinningActive());
+            pw.println("  assistantAvailable=" + mAssistantAvailable);
+            pw.println("  a11yClickable="
+                    + ((mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0));
+            pw.println("  a11yLongClickable="
+                    + ((mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0));
+            pw.println("  resumed="
+                    + mOverviewComponentObserver.getActivityControlHelper().isResumed());
+            pw.println("  useSharedState=" + mConsumer.useSharedSwipeState());
+            if (mConsumer.useSharedSwipeState()) {
+                mSwipeSharedState.dump("    ", pw);
+            }
+            pw.println("  mConsumer=" + mConsumer.getName());
+            TOUCH_INTERACTION_LOG.dump("", pw);
+
+        }
+    }
+
+    private void printAvailableCommands(PrintWriter pw) {
+        pw.println("Available commands:");
+        pw.println("  clear-touch-log: Clears the touch interaction log");
+    }
+
+    private void onCommand(PrintWriter pw, ArgList args) {
+        switch (args.nextArg()) {
+            case "clear-touch-log":
+                TOUCH_INTERACTION_LOG.clear();
+                break;
+        }
     }
 }
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 404cfe6..d69262e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -175,7 +175,8 @@
         RECENTS(1, STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
                 | STATE_SCREENSHOT_VIEW_SHOWN, true, false, ContainerType.TASKSWITCHER, true),
 
-        NEW_TASK(0, STATE_START_NEW_TASK, false, true, ContainerType.APP, true),
+        NEW_TASK(0, STATE_START_NEW_TASK | STATE_CAPTURE_SCREENSHOT, false, true,
+                ContainerType.APP, true),
 
         LAST_TASK(0, STATE_RESUME_LAST_TASK, false, true, ContainerType.APP, false);
 
@@ -319,7 +320,7 @@
 
         mStateCallback.addCallback(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
                 this::resumeLastTask);
-        mStateCallback.addCallback(STATE_START_NEW_TASK | STATE_APP_CONTROLLER_RECEIVED,
+        mStateCallback.addCallback(STATE_START_NEW_TASK | STATE_SCREENSHOT_CAPTURED,
                 this::startNewTask);
 
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
@@ -503,6 +504,7 @@
 
     private void setupRecentsViewUi() {
         if (mContinuingLastGesture) {
+            updateSysUiFlags(mCurrentShift.value);
             return;
         }
         mRecentsView.onGestureAnimationStart(mRunningTaskId);
@@ -676,15 +678,6 @@
                     HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
             }
         }
-        // Update insets of the non-running tasks, as we might switch to them.
-        int runningTaskIndex = mRecentsView == null ? -1 : mRecentsView.getRunningTaskIndex();
-        if (runningTaskIndex >= 0) {
-            for (int i = 0; i < mRecentsView.getTaskViewCount(); i++) {
-                if (i != runningTaskIndex || !mRecentsAnimationWrapper.hasTargets()) {
-                    mRecentsView.getTaskViewAt(i).setFullscreenProgress(1 - mCurrentShift.value);
-                }
-            }
-        }
 
         if (mLauncherTransitionController == null || mLauncherTransitionController
                 .getAnimationPlayer().isStarted()) {
@@ -705,12 +698,15 @@
 
     private void updateSysUiFlags(float windowProgress) {
         if (mRecentsView != null) {
+            TaskView centermostTask = mRecentsView.getTaskViewAt(mRecentsView
+                    .getPageNearestToCenterOfScreen());
+            int centermostTaskFlags = centermostTask == null ? 0
+                    : centermostTask.getThumbnail().getSysUiStatusNavFlags();
+            boolean useHomeScreenFlags = windowProgress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD;
             // We will handle the sysui flags based on the centermost task view.
-            mRecentsAnimationWrapper.setWindowThresholdCrossed(true);
-            int sysuiFlags = windowProgress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD
-                    ? 0
-                    : mRecentsView.getTaskViewAt(mRecentsView.getPageNearestToCenterOfScreen())
-                            .getThumbnail().getSysUiStatusNavFlags();
+            mRecentsAnimationWrapper.setWindowThresholdCrossed(centermostTaskFlags != 0
+                    || useHomeScreenFlags);
+            int sysuiFlags = useHomeScreenFlags ? 0 : centermostTaskFlags;
             mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, sysuiFlags);
         }
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
index f8475ca..1f73a28 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
@@ -23,6 +23,7 @@
 import static android.view.MotionEvent.ACTION_UP;
 
 import android.content.Context;
+import android.graphics.RectF;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.Display;
@@ -46,6 +47,7 @@
     private final VelocityTracker mVelocityTracker;
     private final MotionPauseDetector mMotionPauseDetector;
     private final boolean mAllowLongClick;
+    private final RectF mSwipeTouchRegion;
 
     private final float mMinGestureDistance;
     private final float mMinFlingVelocity;
@@ -55,13 +57,15 @@
     private float mTotalY;
 
     public AccessibilityInputConsumer(Context context, ISystemUiProxy systemUiProxy,
-            boolean allowLongClick, InputConsumer delegate, InputMonitorCompat inputMonitor) {
+            boolean allowLongClick, InputConsumer delegate, InputMonitorCompat inputMonitor,
+            RectF swipeTouchRegion) {
         super(delegate, inputMonitor);
         mSystemUiProxy = systemUiProxy;
         mVelocityTracker = VelocityTracker.obtain();
         mMinGestureDistance = context.getResources()
                 .getDimension(R.dimen.accessibility_gesture_min_swipe_distance);
         mMinFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
+        mSwipeTouchRegion = swipeTouchRegion;
 
         mMotionPauseDetector = new MotionPauseDetector(context);
         mAllowLongClick = allowLongClick;
@@ -98,10 +102,11 @@
             }
             case ACTION_POINTER_DOWN: {
                 if (mState == STATE_INACTIVE) {
-                    if (mDelegate.allowInterceptByParent()) {
+                    int pointerIndex = ev.getActionIndex();
+                    if (mSwipeTouchRegion.contains(ev.getX(pointerIndex), ev.getY(pointerIndex))
+                            && mDelegate.allowInterceptByParent()) {
                         setActive(ev);
 
-                        int pointerIndex = ev.getActionIndex();
                         mActivePointerId = ev.getPointerId(pointerIndex);
                         mDownY = ev.getY(pointerIndex);
                     } else {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java
index 0448fd1..20ea3a1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java
@@ -19,8 +19,10 @@
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_POINTER_DOWN;
 import static android.view.MotionEvent.ACTION_POINTER_UP;
 import static android.view.MotionEvent.ACTION_UP;
+
 import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction.UPLEFT;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction.UPRIGHT;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.FLING;
@@ -38,6 +40,7 @@
 import android.util.Log;
 import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
+
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
@@ -117,6 +120,12 @@
                 mTimeFraction = 0;
                 break;
             }
+            case ACTION_POINTER_DOWN: {
+                if (mState != STATE_ACTIVE) {
+                    mState = STATE_DELEGATE_ACTIVE;
+                    break;
+                }
+            }
             case ACTION_POINTER_UP: {
                 int ptrIdx = ev.getActionIndex();
                 int ptrId = ev.getPointerId(ptrIdx);
@@ -257,7 +266,7 @@
 
     @Override
     public void onDragEnd(float velocity, boolean fling) {
-        if (fling) {
+        if (fling && !mLaunchedAssistant) {
             mLastProgress = 1;
             updateAssistant(FLING);
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
index 2e8880d..6e7cb8f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
@@ -63,4 +63,23 @@
             onKeyEvent((KeyEvent) ev);
         }
     }
+
+    default String getName() {
+        switch (getType()) {
+            case TYPE_OVERVIEW:
+                return "OVERVIEW";
+            case TYPE_OTHER_ACTIVITY:
+                return "OTHER_ACTIVITY";
+            case TYPE_ASSISTANT:
+                return "ASSISTANT";
+            case TYPE_DEVICE_LOCKED:
+                return "DEVICE_LOCKED";
+            case TYPE_ACCESSIBILITY:
+                return "ACCESSIBILITY";
+            case TYPE_SCREEN_PINNED:
+                return "SCREEN_PINNED";
+            default:
+                return "NO_OP";
+        }
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 35b96cc..eb5366c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -18,6 +18,7 @@
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_POINTER_DOWN;
 import static android.view.MotionEvent.ACTION_POINTER_UP;
 import static android.view.MotionEvent.ACTION_UP;
 import static android.view.MotionEvent.INVALID_POINTER_ID;
@@ -36,6 +37,7 @@
 import android.content.ContextWrapper;
 import android.content.Intent;
 import android.graphics.PointF;
+import android.graphics.RectF;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
@@ -90,6 +92,7 @@
     private final SwipeSharedState mSwipeSharedState;
     private final InputMonitorCompat mInputMonitorCompat;
     private final SysUINavigationMode.Mode mMode;
+    private final RectF mSwipeTouchRegion;
 
     private final int mDisplayRotation;
 
@@ -127,7 +130,8 @@
             boolean isDeferredDownTarget, OverviewCallbacks overviewCallbacks,
             InputConsumerController inputConsumer,
             Consumer<OtherActivityInputConsumer> onCompleteCallback,
-            SwipeSharedState swipeSharedState, InputMonitorCompat inputMonitorCompat) {
+            SwipeSharedState swipeSharedState, InputMonitorCompat inputMonitorCompat,
+            RectF swipeTouchRegion) {
         super(base);
 
         mMainThreadHandler = new Handler(Looper.getMainLooper());
@@ -135,6 +139,7 @@
         mRecentsModel = recentsModel;
         mHomeIntent = homeIntent;
         mMode = SysUINavigationMode.getMode(base);
+        mSwipeTouchRegion = swipeTouchRegion;
 
         mMotionPauseDetector = new MotionPauseDetector(base);
         mMotionPauseMinDisplacement = base.getResources().getDimension(
@@ -204,6 +209,19 @@
                 RaceConditionTracker.onEvent(DOWN_EVT, EXIT);
                 break;
             }
+            case ACTION_POINTER_DOWN: {
+                if (!mPassedTouchSlop) {
+                    // Cancel interaction in case of multi-touch interaction
+                    int ptrIdx = ev.getActionIndex();
+                    if (!mSwipeTouchRegion.contains(ev.getX(ptrIdx), ev.getY(ptrIdx))) {
+                        int action = ev.getAction();
+                        ev.setAction(ACTION_CANCEL);
+                        finishTouchTracking(ev);
+                        ev.setAction(action);
+                    }
+                }
+                break;
+            }
             case ACTION_POINTER_UP: {
                 int ptrIdx = ev.getActionIndex();
                 int ptrId = ev.getPointerId(ptrIdx);
@@ -273,13 +291,8 @@
                 break;
             }
             case ACTION_CANCEL:
-                // TODO: Should be different than ACTION_UP
             case ACTION_UP: {
-                RaceConditionTracker.onEvent(UP_EVT, ENTER);
-                TraceHelper.endSection("TouchInt");
-
                 finishTouchTracking(ev);
-                RaceConditionTracker.onEvent(UP_EVT, EXIT);
                 break;
             }
         }
@@ -342,6 +355,9 @@
      * the animation can still be running.
      */
     private void finishTouchTracking(MotionEvent ev) {
+        RaceConditionTracker.onEvent(UP_EVT, ENTER);
+        TraceHelper.endSection("TouchInt");
+
         if (mPassedDragSlop && mInteractionHandler != null) {
             if (ev.getActionMasked() == ACTION_CANCEL) {
                 mInteractionHandler.onGestureCancelled();
@@ -374,6 +390,7 @@
         mVelocityTracker.recycle();
         mVelocityTracker = null;
         mMotionPauseDetector.clear();
+        RaceConditionTracker.onEvent(UP_EVT, EXIT);
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
index 3121dc3..805cf33 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -285,13 +285,20 @@
     /**
      * Compute scale and translation y such that the specified task view fills the screen.
      */
-    public LauncherState.ScaleAndTranslation getOverviewFullscreenScaleAndTranslation(TaskView v) {
+    public ClipAnimationHelper updateForFullscreenOverview(TaskView v) {
         TaskThumbnailView thumbnailView = v.getThumbnail();
         RecentsView recentsView = v.getRecentsView();
         fromTaskThumbnailView(thumbnailView, recentsView);
         Rect taskSize = new Rect();
         recentsView.getTaskSize(taskSize);
         updateTargetRect(taskSize);
+        return this;
+    }
+
+    /**
+     * @return The source rect's scale and translation relative to the target rect.
+     */
+    public LauncherState.ScaleAndTranslation getScaleAndTranslation() {
         float scale = mSourceRect.width() / mTargetRect.width();
         float translationY = mSourceRect.centerY() - mSourceRect.top - mTargetRect.centerY();
         return new LauncherState.ScaleAndTranslation(scale, 0, translationY);
@@ -332,35 +339,10 @@
         mSourceStackBounds.offset(left, insets.top + fullDp.availableHeightPx - taskHeight);
     }
 
-    public void drawForProgress(TaskThumbnailView ttv, Canvas canvas, float progress) {
-        RectF currentRect =  mRectFEvaluator.evaluate(progress, mSourceRect, mTargetRect);
-        canvas.translate(mSourceStackBounds.left - mHomeStackBounds.left,
-                mSourceStackBounds.top - mHomeStackBounds.top);
-        mTmpMatrix.setRectToRect(mTargetRect, currentRect, ScaleToFit.FILL);
-
-        canvas.concat(mTmpMatrix);
-        canvas.translate(mTargetRect.left, mTargetRect.top);
-
-        float scale = mTargetRect.width() / mSourceRect.width();
-        float insetProgress = (1 - progress);
-        float windowCornerRadius = mUseRoundedCornersOnWindows
-                ? mWindowCornerRadius : 0;
-        ttv.drawOnCanvas(canvas,
-                -mSourceWindowClipInsets.left * insetProgress,
-                -mSourceWindowClipInsets.top * insetProgress,
-                ttv.getMeasuredWidth() + mSourceWindowClipInsets.right * insetProgress,
-                ttv.getMeasuredHeight() + mSourceWindowClipInsets.bottom * insetProgress,
-                Utilities.mapRange(progress, windowCornerRadius * scale, ttv.getCornerRadius()));
-    }
-
     public RectF getTargetRect() {
         return mTargetRect;
     }
 
-    public RectF getSourceRect() {
-        return mSourceRect;
-    }
-
     public float getCurrentCornerRadius() {
         return mCurrentCornerRadius;
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewDrawable.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewDrawable.java
deleted file mode 100644
index bb41e5d..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewDrawable.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2018 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.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.PixelFormat;
-import android.graphics.drawable.Drawable;
-import android.util.FloatProperty;
-import android.view.View;
-
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.Utilities;
-import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskThumbnailView;
-import com.android.quickstep.views.TaskView;
-
-public class TaskViewDrawable extends Drawable {
-
-    public static final FloatProperty<TaskViewDrawable> PROGRESS =
-            new FloatProperty<TaskViewDrawable>("progress") {
-                @Override
-                public void setValue(TaskViewDrawable taskViewDrawable, float v) {
-                    taskViewDrawable.setProgress(v);
-                }
-
-                @Override
-                public Float get(TaskViewDrawable taskViewDrawable) {
-                    return taskViewDrawable.mProgress;
-                }
-            };
-
-    /**
-     * The progress at which we play the atomic icon scale animation.
-     */
-    private static final float ICON_SCALE_THRESHOLD = 0.95f;
-
-    private final RecentsView mParent;
-    private final View mIconView;
-    private final float[] mIconPos;
-    private final TaskView mTaskView;
-
-    private final TaskThumbnailView mThumbnailView;
-
-    private final ClipAnimationHelper mClipAnimationHelper;
-
-    private float mProgress = 1;
-    private boolean mPassedIconScaleThreshold;
-    private ValueAnimator mIconScaleAnimator;
-    private float mIconScale;
-
-    public TaskViewDrawable(TaskView tv, RecentsView parent) {
-        mParent = parent;
-        mTaskView = tv;
-        mIconView = tv.getIconView();
-        mIconPos = new float[2];
-        mIconScale = mIconView.getScaleX();
-        Utilities.getDescendantCoordRelativeToAncestor(mIconView, parent, mIconPos, true);
-
-        mThumbnailView = tv.getThumbnail();
-        mClipAnimationHelper = new ClipAnimationHelper(parent.getContext());
-        mClipAnimationHelper.fromTaskThumbnailView(mThumbnailView, parent);
-        mClipAnimationHelper.prepareAnimation(
-                BaseActivity.fromContext(tv.getContext()).getDeviceProfile(), true /* isOpening */);
-    }
-
-    public void setProgress(float progress) {
-        mProgress = progress;
-        mParent.invalidate();
-        boolean passedIconScaleThreshold = progress <= ICON_SCALE_THRESHOLD;
-        if (mPassedIconScaleThreshold != passedIconScaleThreshold) {
-            mPassedIconScaleThreshold = passedIconScaleThreshold;
-            animateIconScale(mPassedIconScaleThreshold ? 0 : 1);
-        }
-    }
-
-    private void animateIconScale(float toScale) {
-        if (mIconScaleAnimator != null) {
-            mIconScaleAnimator.cancel();
-        }
-        mIconScaleAnimator = ValueAnimator.ofFloat(mIconScale, toScale);
-        mIconScaleAnimator.addUpdateListener(valueAnimator -> {
-            mIconScale = (float) valueAnimator.getAnimatedValue();
-            if (mProgress > ICON_SCALE_THRESHOLD) {
-                // Speed up the icon scale to ensure it is 1 when progress is 1.
-                float iconProgress = (mProgress - ICON_SCALE_THRESHOLD) / (1 - ICON_SCALE_THRESHOLD);
-                if (iconProgress > mIconScale) {
-                    mIconScale = iconProgress;
-                }
-            }
-            invalidateSelf();
-        });
-        mIconScaleAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mIconScaleAnimator = null;
-            }
-        });
-        mIconScaleAnimator.setDuration(TaskView.SCALE_ICON_DURATION);
-        mIconScaleAnimator.start();
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        canvas.save();
-        canvas.translate(mParent.getScrollX(), mParent.getScrollY());
-        mClipAnimationHelper.drawForProgress(mThumbnailView, canvas, mProgress);
-        canvas.restore();
-
-        canvas.save();
-        canvas.translate(mIconPos[0], mIconPos[1]);
-        canvas.scale(mIconScale, mIconScale, mIconView.getWidth() / 2, mIconView.getHeight() / 2);
-        mIconView.draw(canvas);
-        canvas.restore();
-    }
-
-    public ClipAnimationHelper getClipAnimationHelper() {
-        return mClipAnimationHelper;
-    }
-
-    @Override
-    public void setAlpha(int i) { }
-
-    @Override
-    public void setColorFilter(ColorFilter colorFilter) { }
-
-    @Override
-    public int getOpacity() {
-        return PixelFormat.TRANSLUCENT;
-    }
-
-    public TaskView getTaskView() {
-        return mTaskView;
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index bdac750..deedd21 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -30,6 +30,7 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.Build;
 import android.util.AttributeSet;
 import android.view.View;
@@ -179,6 +180,21 @@
     }
 
     @Override
+    protected void onTaskLaunchAnimationUpdate(float progress, TaskView tv) {
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            if (mRecentsAnimationWrapper.targetSet != null && tv.isRunningTask()) {
+                mTransformParams.setProgress(1 - progress)
+                        .setSyncTransactionApplier(mSyncTransactionApplier)
+                        .setForLiveTile(true);
+                mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet,
+                        mTransformParams);
+            } else {
+                redrawLiveTile(true);
+            }
+        }
+    }
+
+    @Override
     public PendingAnimation createTaskDismissAnimation(TaskView taskView, boolean animateTaskView,
             boolean shouldRemoveTask, long duration) {
         PendingAnimation anim = super.createTaskDismissAnimation(taskView, animateTaskView,
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index 525ead8..a835680 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -18,6 +18,7 @@
 
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
 import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
@@ -30,7 +31,6 @@
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CLEAR_ALL_BUTTON;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
-import static com.android.quickstep.util.ClipAnimationHelper.TransformParams;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -75,7 +75,9 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils.ViewProgressProperty;
+import com.android.launcher3.LauncherState;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -91,13 +93,13 @@
 import com.android.launcher3.util.PendingAnimation;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.ViewPool;
+import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.RecentsAnimationWrapper;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.RecentsModel.TaskThumbnailChangeListener;
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.util.ClipAnimationHelper;
-import com.android.quickstep.util.TaskViewDrawable;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -140,6 +142,19 @@
                 }
             };
 
+    public static final FloatProperty<RecentsView> FULLSCREEN_PROGRESS =
+            new FloatProperty<RecentsView>("fullscreenProgress") {
+                @Override
+                public void setValue(RecentsView recentsView, float v) {
+                    recentsView.setFullscreenProgress(v);
+                }
+
+                @Override
+                public Float get(RecentsView recentsView) {
+                    return recentsView.mFullscreenProgress;
+                }
+            };
+
     protected RecentsAnimationWrapper mRecentsAnimationWrapper;
     protected ClipAnimationHelper mClipAnimationHelper;
     protected SyncRtSurfaceTransactionApplierCompat mSyncTransactionApplier;
@@ -163,6 +178,7 @@
     private final ClearAllButton mClearAllButton;
     private final Rect mClearAllButtonDeadZoneRect = new Rect();
     private final Rect mTaskViewDeadZoneRect = new Rect();
+    protected final ClipAnimationHelper mTempClipAnimationHelper;
 
     private final ScrollState mScrollState = new ScrollState();
     // Keeps track of the previously known visible tasks for purposes of loading/unloading task data
@@ -274,6 +290,8 @@
 
     @ViewDebug.ExportedProperty(category = "launcher")
     private float mContentAlpha = 1;
+    @ViewDebug.ExportedProperty(category = "launcher")
+    private float mFullscreenProgress = 0;
 
     // Keeps track of task id whose visual state should not be reset
     private int mIgnoreResetTaskId = -1;
@@ -288,6 +306,8 @@
     private Layout mEmptyTextLayout;
     private LiveTileOverlay mLiveTileOverlay;
 
+    private FloatingIconView mFloatingIconView;
+
     private BaseActivity.MultiWindowModeChangedListener mMultiWindowModeChangedListener =
             (inMultiWindowMode) -> {
         if (!inMultiWindowMode && mOverviewStateEnabled) {
@@ -306,6 +326,7 @@
         mActivity = (T) BaseActivity.fromContext(context);
         mModel = RecentsModel.INSTANCE.get(context);
         mIdp = InvariantDeviceProfile.INSTANCE.get(context);
+        mTempClipAnimationHelper = new ClipAnimationHelper(context);
 
         mClearAllButton = (ClearAllButton) LayoutInflater.from(context)
                 .inflate(R.layout.overview_clear_all_button, this, false);
@@ -594,6 +615,14 @@
         loadVisibleTaskData();
     }
 
+    public void setFullscreenProgress(float fullscreenProgress) {
+        mFullscreenProgress = fullscreenProgress;
+        int taskCount = getTaskViewCount();
+        for (int i = 0; i < taskCount; i++) {
+            getTaskViewAt(i).setFullscreenProgress(mFullscreenProgress);
+        }
+    }
+
     private void updateTaskStackListenerState() {
         boolean handleTaskStackChanges = mOverviewStateEnabled && isAttachedToWindow()
                 && getWindowVisibility() == VISIBLE;
@@ -1282,15 +1311,6 @@
         setVisibility(alpha > 0 ? VISIBLE : GONE);
     }
 
-    private float[] getAdjacentScaleAndTranslation(TaskView currTask,
-            float currTaskToScale, float currTaskToTranslationY) {
-        float displacement = currTask.getWidth() * (currTaskToScale - currTask.getCurveScale());
-        sTempFloatArray[0] = currTaskToScale;
-        sTempFloatArray[1] = mIsRtl ? -displacement : displacement;
-        sTempFloatArray[2] = currTaskToTranslationY;
-        return sTempFloatArray;
-    }
-
     @Override
     public void onViewAdded(View child) {
         super.onViewAdded(child);
@@ -1419,27 +1439,15 @@
         int centerTaskIndex = getCurrentPage();
         boolean launchingCenterTask = taskIndex == centerTaskIndex;
 
-        float toScale = clipAnimationHelper.getSourceRect().width()
-                / clipAnimationHelper.getTargetRect().width();
-        float toTranslationY = clipAnimationHelper.getSourceRect().centerY()
-                - clipAnimationHelper.getTargetRect().centerY();
+        LauncherState.ScaleAndTranslation toScaleAndTranslation = clipAnimationHelper
+                .getScaleAndTranslation();
+        float toScale = toScaleAndTranslation.scale;
+        float toTranslationY = toScaleAndTranslation.translationY;
         if (launchingCenterTask) {
-            TaskView centerTask = getTaskViewAt(centerTaskIndex);
-            if (taskIndex - 1 >= 0) {
-                TaskView adjacentTask = getTaskViewAt(taskIndex - 1);
-                float[] scaleAndTranslation = getAdjacentScaleAndTranslation(centerTask,
-                        toScale, toTranslationY);
-                scaleAndTranslation[1] = -scaleAndTranslation[1];
-                anim.play(createAnimForChild(adjacentTask, scaleAndTranslation));
-                anim.play(ObjectAnimator.ofFloat(adjacentTask, TaskView.FULLSCREEN_PROGRESS, 1));
-            }
-            if (taskIndex + 1 < getTaskViewCount()) {
-                TaskView adjacentTask = getTaskViewAt(taskIndex + 1);
-                float[] scaleAndTranslation = getAdjacentScaleAndTranslation(centerTask,
-                        toScale, toTranslationY);
-                anim.play(createAnimForChild(adjacentTask, scaleAndTranslation));
-                anim.play(ObjectAnimator.ofFloat(adjacentTask, TaskView.FULLSCREEN_PROGRESS, 1));
-            }
+            RecentsView recentsView = tv.getRecentsView();
+            anim.play(ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, toScale));
+            anim.play(ObjectAnimator.ofFloat(recentsView, TRANSLATION_Y, toTranslationY));
+            anim.play(ObjectAnimator.ofFloat(recentsView, FULLSCREEN_PROGRESS, 1));
         } else {
             // We are launching an adjacent task, so parallax the center and other adjacent task.
             float displacementX = tv.getWidth() * (toScale - tv.getCurveScale());
@@ -1457,16 +1465,6 @@
         return anim;
     }
 
-    private Animator createAnimForChild(TaskView child, float[] toScaleAndTranslation) {
-        AnimatorSet anim = new AnimatorSet();
-        anim.play(ObjectAnimator.ofFloat(child, TaskView.ZOOM_SCALE, toScaleAndTranslation[0]));
-        anim.play(new PropertyListBuilder()
-                .translationX(toScaleAndTranslation[1])
-                .translationY(toScaleAndTranslation[2])
-                .build(child));
-        return anim;
-    }
-
     public PendingAnimation createTaskLauncherAnimation(TaskView tv, long duration) {
         if (FeatureFlags.IS_DOGFOOD_BUILD && mPendingAnimation != null) {
             throw new IllegalStateException("Another pending animation is still running");
@@ -1477,60 +1475,38 @@
             return new PendingAnimation(new AnimatorSet());
         }
 
-        tv.setVisibility(INVISIBLE);
         int targetSysUiFlags = tv.getThumbnail().getSysUiStatusNavFlags();
-        TaskViewDrawable drawable = new TaskViewDrawable(tv, this);
-        getOverlay().add(drawable);
-
         final boolean[] passedOverviewThreshold = new boolean[] {false};
-        ObjectAnimator drawableAnim =
-                ObjectAnimator.ofFloat(drawable, TaskViewDrawable.PROGRESS, 1, 0);
-        drawableAnim.setInterpolator(LINEAR);
-        drawableAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            TransformParams mParams = new TransformParams();
+        ValueAnimator progressAnim = ValueAnimator.ofFloat(0, 1);
+        progressAnim.setInterpolator(LINEAR);
+        progressAnim.addUpdateListener(animator -> {
+            // Once we pass a certain threshold, update the sysui flags to match the target
+            // tasks' flags
+            mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW,
+                    animator.getAnimatedFraction() > UPDATE_SYSUI_FLAGS_THRESHOLD
+                            ? targetSysUiFlags
+                            : 0);
 
-            @Override
-            public void onAnimationUpdate(ValueAnimator animator) {
-                // Once we pass a certain threshold, update the sysui flags to match the target
-                // tasks' flags
-                mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW,
-                        animator.getAnimatedFraction() > UPDATE_SYSUI_FLAGS_THRESHOLD
-                                ? targetSysUiFlags
-                                : 0);
-                if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-                    if (mRecentsAnimationWrapper.targetSet != null
-                            && drawable.getTaskView().isRunningTask()) {
-                        mParams.setProgress(1 - animator.getAnimatedFraction())
-                                .setSyncTransactionApplier(mSyncTransactionApplier)
-                                .setForLiveTile(true);
-                        drawable.getClipAnimationHelper().applyTransform(
-                                mRecentsAnimationWrapper.targetSet, mParams);
-                    } else {
-                        redrawLiveTile(true);
-                    }
-                }
+            onTaskLaunchAnimationUpdate(animator.getAnimatedFraction(), tv);
 
-                // Passing the threshold from taskview to fullscreen app will vibrate
-                final boolean passed = animator.getAnimatedFraction() >=
-                        SUCCESS_TRANSITION_PROGRESS;
-                if (passed != passedOverviewThreshold[0]) {
-                    passedOverviewThreshold[0] = passed;
-                    performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
-                            HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
-                }
+            // Passing the threshold from taskview to fullscreen app will vibrate
+            final boolean passed = animator.getAnimatedFraction() >=
+                    SUCCESS_TRANSITION_PROGRESS;
+            if (passed != passedOverviewThreshold[0]) {
+                passedOverviewThreshold[0] = passed;
+                performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
+                        HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
             }
         });
 
-        AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv,
-                drawable.getClipAnimationHelper());
-        anim.play(drawableAnim);
+        ClipAnimationHelper clipAnimationHelper = new ClipAnimationHelper(mActivity);
+        clipAnimationHelper.fromTaskThumbnailView(tv.getThumbnail(), this);
+        clipAnimationHelper.prepareAnimation(mActivity.getDeviceProfile(), true /* isOpening */);
+        AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv, clipAnimationHelper);
+        anim.play(progressAnim);
         anim.setDuration(duration);
 
-        Consumer<Boolean> onTaskLaunchFinish = (result) -> {
-            onTaskLaunched(result);
-            tv.setVisibility(VISIBLE);
-            getOverlay().remove(drawable);
-        };
+        Consumer<Boolean> onTaskLaunchFinish = this::onTaskLaunched;
 
         mPendingAnimation = new PendingAnimation(anim);
         mPendingAnimation.addEndListener((onEndListener) -> {
@@ -1556,6 +1532,9 @@
         return mPendingAnimation;
     }
 
+    protected void onTaskLaunchAnimationUpdate(float progress, TaskView tv) {
+    }
+
     public abstract boolean shouldUseMultiWindowTaskSizeStrategy();
 
     protected void onTaskLaunched(boolean success) {
@@ -1704,4 +1683,14 @@
             return super::onTouchEvent;
         }
     }
+
+    public FloatingIconView getFloatingIconView(Launcher launcher, View view, RectF iconLocation) {
+        mFloatingIconView = FloatingIconView.getFloatingIconView(launcher, view,
+                true /* hideOriginal */, iconLocation, false /* isOpening */, mFloatingIconView);
+        return  mFloatingIconView;
+    }
+
+    public ClipAnimationHelper getTempClipAnimationHelper() {
+        return mTempClipAnimationHelper;
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
index a9184ec..df5831b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -75,8 +75,6 @@
                 }
             };
 
-    private final float mCornerRadius;
-
     private final BaseActivity mActivity;
     private final TaskOverlay mOverlay;
     private final boolean mIsDarkTextTheme;
@@ -110,7 +108,6 @@
 
     public TaskThumbnailView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        mCornerRadius = TaskCornerRadius.get(context);
         mOverlay = TaskOverlayFactory.INSTANCE.get(context).createOverlay(this);
         mPaint.setFilterBitmap(true);
         mBackgroundPaint.setColor(Color.WHITE);
@@ -118,7 +115,7 @@
         mDimmingPaintAfterClearing.setColor(Color.BLACK);
         mActivity = BaseActivity.fromContext(context);
         mIsDarkTextTheme = Themes.getAttrBoolean(mActivity, R.attr.isWorkspaceDarkText);
-        mFullscreenParams = new TaskView.FullscreenDrawParams(mCornerRadius);
+        mFullscreenParams = new TaskView.FullscreenDrawParams(TaskCornerRadius.get(context));
     }
 
     public void bind(Task task) {
@@ -225,10 +222,6 @@
         invalidate();
     }
 
-    public float getCornerRadius() {
-        return mCornerRadius;
-    }
-
     public void drawOnCanvas(Canvas canvas, float x, float y, float width, float height,
             float cornerRadius) {
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 2b86f5e..053b738 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -103,19 +103,6 @@
     private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
             Collections.singletonList(new Rect());
 
-    public static final Property<TaskView, Float> ZOOM_SCALE =
-            new FloatProperty<TaskView>("zoomScale") {
-                @Override
-                public void setValue(TaskView taskView, float v) {
-                    taskView.setZoomScale(v);
-                }
-
-                @Override
-                public Float get(TaskView taskView) {
-                    return taskView.mZoomScale;
-                }
-            };
-
     public static final FloatProperty<TaskView> FULLSCREEN_PROGRESS =
             new FloatProperty<TaskView>("fullscreenProgress") {
                 @Override
@@ -165,7 +152,6 @@
     private IconView mIconView;
     private DigitalWellBeingToast mDigitalWellBeingToast;
     private float mCurveScale;
-    private float mZoomScale;
     private float mFullscreenProgress;
     private final FullscreenDrawParams mCurrentFullscreenParams;
     private final float mCornerRadius;
@@ -459,7 +445,6 @@
 
     private void resetViewTransforms() {
         setCurveScale(1);
-        setZoomScale(1);
         setTranslationX(0f);
         setTranslationY(0f);
         setTranslationZ(0);
@@ -475,7 +460,12 @@
     @Override
     public void onRecycle() {
         resetViewTransforms();
-        setFullscreenProgress(0);
+        // 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);
+        if (mTask != null) {
+            mTask.thumbnail = null;
+        }
     }
 
     @Override
@@ -522,13 +512,8 @@
         return mCurveScale;
     }
 
-    public void setZoomScale(float adjacentScale) {
-        mZoomScale = adjacentScale;
-        onScaleChanged();
-    }
-
     private void onScaleChanged() {
-        float scale = mCurveScale * mZoomScale;
+        float scale = mCurveScale;
         setScaleX(scale);
         setScaleY(scale);
     }
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index d1ef631..ecf1b0a 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -35,40 +35,42 @@
         android:focusable="false"
         android:importantForAccessibility="no" />
 
-    <com.android.quickstep.views.DigitalWellBeingToast
-        android:id="@+id/digital_well_being_toast"
+    <LinearLayout
         android:layout_width="match_parent"
-        android:layout_height="48dp"
-        android:importantForAccessibility="noHideDescendants"
-        android:background="@drawable/bg_wellbeing_toast"
-        android:layout_gravity="bottom"
-        android:gravity="center"
-        android:visibility="gone">
-        <ImageView
-            android:id="@+id/digital_well_being_hourglass"
-            android:layout_width="24dp"
-            android:layout_height="24dp"
-            android:layout_marginEnd="8dp"
-            android:src="@drawable/ic_hourglass_top"
-        />
-        <TextView
-            android:id="@+id/digital_well_being_remaining_time"
-            android:layout_width="wrap_content"
-            android:layout_height="24dp"
-            android:fontFamily="sans-serif"
-            android:textSize="14sp"
-            android:textColor="@android:color/white"
-            android:gravity="center_vertical"
-        />
-    </com.android.quickstep.views.DigitalWellBeingToast>
-
-    <FrameLayout
-        android:id="@+id/proactive_suggest_container"
-        android:layout_width="match_parent"
-        android:layout_height="36dp"
-        android:gravity="center"
-        android:layout_gravity="bottom|center"
-        android:translationY="20dp"
-        android:elevation="4dp"
-     />
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:layout_gravity="bottom|center_horizontal">
+        <FrameLayout
+            android:id="@+id/proactive_suggest_container"
+            android:layout_width="match_parent"
+            android:layout_height="36dp"
+            android:gravity="center"
+            android:visibility="gone"
+            />
+        <com.android.quickstep.views.DigitalWellBeingToast
+            android:id="@+id/digital_well_being_toast"
+            android:layout_width="match_parent"
+            android:layout_height="48dp"
+            android:importantForAccessibility="noHideDescendants"
+            android:background="@drawable/bg_wellbeing_toast"
+            android:gravity="center"
+            android:visibility="gone">
+            <ImageView
+                android:id="@+id/digital_well_being_hourglass"
+                android:layout_width="24dp"
+                android:layout_height="24dp"
+                android:layout_marginEnd="8dp"
+                android:src="@drawable/ic_hourglass_top"
+            />
+            <TextView
+                android:id="@+id/digital_well_being_remaining_time"
+                android:layout_width="wrap_content"
+                android:layout_height="24dp"
+                android:fontFamily="sans-serif"
+                android:textSize="14sp"
+                android:textColor="@android:color/white"
+                android:gravity="center_vertical"
+            />
+        </com.android.quickstep.views.DigitalWellBeingToast>
+    </LinearLayout>
 </com.android.quickstep.views.TaskView>
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index 7dd4df7..91c4601 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -476,6 +476,16 @@
 
         float shapeRevealDuration = APP_LAUNCH_DURATION * SHAPE_PROGRESS_DURATION;
 
+        final float startCrop;
+        final float endCrop;
+        if (mDeviceProfile.isVerticalBarLayout()) {
+            startCrop = windowTargetBounds.height();
+            endCrop = windowTargetBounds.width();
+        } else {
+            startCrop = windowTargetBounds.width();
+            endCrop = windowTargetBounds.height();
+        }
+
         final float windowRadius = mDeviceProfile.isMultiWindowMode
                 ? 0 : getWindowCornerRadius(mLauncher.getResources());
         appAnimator.addUpdateListener(new MultiValueUpdateListener() {
@@ -485,10 +495,10 @@
                     EXAGGERATED_EASE);
             FloatProp mIconAlpha = new FloatProp(1f, 0f, APP_LAUNCH_ALPHA_START_DELAY,
                     alphaDuration, LINEAR);
-            FloatProp mCropHeight = new FloatProp(windowTargetBounds.width(),
-                    windowTargetBounds.height(), 0, APP_LAUNCH_DURATION, EXAGGERATED_EASE);
-            FloatProp mWindowRadius = new FloatProp(windowTargetBounds.width() / 2f,
-                    windowRadius, 0, APP_LAUNCH_DURATION, EXAGGERATED_EASE);
+            FloatProp mCroppedSize = new FloatProp(startCrop, endCrop, 0, APP_LAUNCH_DURATION,
+                    EXAGGERATED_EASE);
+            FloatProp mWindowRadius = new FloatProp(startCrop / 2f, windowRadius, 0,
+                    APP_LAUNCH_DURATION, EXAGGERATED_EASE);
 
             @Override
             public void onUpdate(float percent) {
@@ -496,10 +506,16 @@
                 float iconWidth = bounds.width() * mIconScale.value;
                 float iconHeight = bounds.height() * mIconScale.value;
 
-                // Animate the window crop so that it starts off as a square, and then reveals
-                // horizontally.
-                int windowWidth = windowTargetBounds.width();
-                int windowHeight = (int) mCropHeight.value;
+                // Animate the window crop so that it starts off as a square.
+                final int windowWidth;
+                final int windowHeight;
+                if (mDeviceProfile.isVerticalBarLayout()) {
+                    windowWidth = (int) mCroppedSize.value;
+                    windowHeight = windowTargetBounds.height();
+                } else {
+                    windowWidth = windowTargetBounds.width();
+                    windowHeight = (int) mCroppedSize.value;
+                }
                 crop.set(0, 0, windowWidth, windowHeight);
 
                 // Scale the app window to match the icon size.
@@ -522,6 +538,7 @@
                 float transY0 = temp.top - offsetY;
 
                 float croppedHeight = (windowTargetBounds.height() - crop.height()) * scale;
+                float croppedWidth = (windowTargetBounds.width() - crop.width()) * scale;
                 SurfaceParams[] params = new SurfaceParams[targets.length];
                 for (int i = targets.length - 1; i >= 0; i--) {
                     RemoteAnimationTargetCompat target = targets[i];
@@ -535,7 +552,11 @@
                         alpha = 1f - mIconAlpha.value;
                         cornerRadius = mWindowRadius.value;
                         matrix.mapRect(currentBounds, targetBounds);
-                        currentBounds.bottom -= croppedHeight;
+                        if (mDeviceProfile.isVerticalBarLayout()) {
+                            currentBounds.right -= croppedWidth;
+                        } else {
+                            currentBounds.bottom -= croppedHeight;
+                        }
                         mFloatingView.update(currentBounds, mIconAlpha.value, percent, 0f,
                                 cornerRadius * scale, true /* isOpening */);
                     } else {
diff --git a/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java b/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java
new file mode 100644
index 0000000..e302b4f
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java
@@ -0,0 +1,82 @@
+/*
+ * 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.proxy;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender.SendIntentException;
+import android.os.Bundle;
+import android.util.Log;
+
+public class ProxyActivityStarter extends Activity {
+
+    private static final String TAG = "ProxyActivityStarter";
+
+    public static final String EXTRA_PARAMS = "start-activity-params";
+
+    private StartActivityParams mParams;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setVisible(false);
+
+        mParams = getIntent().getParcelableExtra(EXTRA_PARAMS);
+        if (mParams == null) {
+            Log.d(TAG, "Proxy activity started without params");
+            finishAndRemoveTask();
+            return;
+        }
+
+        if (savedInstanceState != null) {
+            // Already started the activity. Just wait for the result.
+            return;
+        }
+
+        if (mParams.intent != null) {
+            startActivityForResult(mParams.intent, mParams.requestCode, mParams.options);
+            return;
+        } else if (mParams.intentSender != null) {
+            try {
+                startIntentSenderForResult(mParams.intentSender, mParams.requestCode,
+                        mParams.fillInIntent, mParams.flagsMask, mParams.flagsValues,
+                        mParams.extraFlags,
+                        mParams.options);
+                return;
+            } catch (SendIntentException e) {
+                mParams.deliverResult(this, RESULT_CANCELED, null);
+            }
+        }
+        finishAndRemoveTask();
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode == mParams.requestCode) {
+            mParams.deliverResult(this, resultCode, data);
+        }
+        finishAndRemoveTask();
+    }
+
+    public static Intent getLaunchIntent(Context context, StartActivityParams params) {
+        return new Intent(context, ProxyActivityStarter.class)
+                .putExtra(EXTRA_PARAMS, params)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+                        | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/proxy/StartActivityParams.java b/quickstep/src/com/android/launcher3/proxy/StartActivityParams.java
new file mode 100644
index 0000000..1e8bd93
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/proxy/StartActivityParams.java
@@ -0,0 +1,103 @@
+/*
+ * 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.proxy;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+public class StartActivityParams implements Parcelable {
+
+    private static final String TAG = "StartActivityParams";
+
+    private final PendingIntent mCallback;
+    public final int requestCode;
+
+    public Intent intent;
+
+    public IntentSender intentSender;
+    public Intent fillInIntent;
+    public int flagsMask;
+    public int flagsValues;
+    public int extraFlags;
+    public Bundle options;
+
+    public StartActivityParams(Activity activity, int requestCode) {
+        mCallback = activity.createPendingResult(requestCode, new Intent(),
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
+        this.requestCode = requestCode;
+    }
+
+    private StartActivityParams(Parcel parcel) {
+        mCallback = parcel.readTypedObject(PendingIntent.CREATOR);
+        requestCode = parcel.readInt();
+        intent = parcel.readTypedObject(Intent.CREATOR);
+
+        intentSender = parcel.readTypedObject(IntentSender.CREATOR);
+        fillInIntent = parcel.readTypedObject(Intent.CREATOR);
+        flagsMask = parcel.readInt();
+        flagsValues = parcel.readInt();
+        extraFlags = parcel.readInt();
+        options = parcel.readBundle();
+    }
+
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeTypedObject(mCallback, flags);
+        parcel.writeInt(requestCode);
+        parcel.writeTypedObject(intent, flags);
+
+        parcel.writeTypedObject(intentSender, flags);
+        parcel.writeTypedObject(fillInIntent, flags);
+        parcel.writeInt(flagsMask);
+        parcel.writeInt(flagsValues);
+        parcel.writeInt(extraFlags);
+        parcel.writeBundle(options);
+    }
+
+    public void deliverResult(Context context, int resultCode, Intent data) {
+        try {
+            mCallback.send(context, resultCode, data);
+        } catch (CanceledException e) {
+            Log.e(TAG, "Unable to send back result", e);
+        }
+    }
+
+    public static final Parcelable.Creator<StartActivityParams> CREATOR =
+            new Parcelable.Creator<StartActivityParams>() {
+                public StartActivityParams createFromParcel(Parcel source) {
+                    return new StartActivityParams(source);
+                }
+
+                public StartActivityParams[] newArray(int size) {
+                    return new StartActivityParams[size];
+                }
+            };
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index 77ac35c..4891746 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.uioverrides;
 
+import static android.app.Activity.RESULT_CANCELED;
+
 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
 import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON;
 import static com.android.launcher3.LauncherState.ALL_APPS;
@@ -31,6 +33,9 @@
 import android.animation.ValueAnimator;
 import android.app.Activity;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.util.Base64;
 
@@ -43,6 +48,8 @@
 import com.android.launcher3.QuickstepAppTransitionManagerImpl;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.proxy.ProxyActivityStarter;
+import com.android.launcher3.proxy.StartActivityParams;
 import com.android.quickstep.OverviewInteractionState;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.SysUINavigationMode;
@@ -192,6 +199,40 @@
         return true;
     }
 
+    public static boolean startIntentSenderForResult(Activity activity, IntentSender intent,
+            int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+            Bundle options) {
+        StartActivityParams params = new StartActivityParams(activity, requestCode);
+        params.intentSender = intent;
+        params.fillInIntent = fillInIntent;
+        params.flagsMask = flagsMask;
+        params.flagsValues = flagsValues;
+        params.extraFlags = extraFlags;
+        params.options = options;
+        ((Context) activity).startActivity(ProxyActivityStarter.getLaunchIntent(activity, params));
+        return true;
+    }
+
+    public static boolean startActivityForResult(Activity activity, Intent intent, int requestCode,
+            Bundle options) {
+        StartActivityParams params = new StartActivityParams(activity, requestCode);
+        params.intent = intent;
+        params.options = options;
+        activity.startActivity(ProxyActivityStarter.getLaunchIntent(activity, params));
+        return true;
+    }
+
+    /**
+     * Removes any active ProxyActivityStarter task and sends RESULT_CANCELED to Launcher.
+     *
+     * ProxyActivityStarter is started with clear task to reset the task after which it removes the
+     * task itself.
+     */
+    public static void resetPendingActivityResults(Launcher launcher, int requestCode) {
+        launcher.onActivityResult(requestCode, RESULT_CANCELED, null);
+        launcher.startActivity(ProxyActivityStarter.getLaunchIntent(launcher, null));
+    }
+
     public static ScaleAndTranslation getOverviewScaleAndTranslationForNormalState(Launcher l) {
         if (SysUINavigationMode.getMode(l) == Mode.NO_BUTTON) {
             float offscreenTranslationX = l.getDeviceProfile().widthPx
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 06a36c9..3538373 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -120,6 +120,16 @@
     }
 
     @Override
+    public void onTaskRemoved(int taskId) {
+        for (int i = mTasks.size() - 1; i >= 0; i--) {
+            if (mTasks.get(i).key.id == taskId) {
+                mTasks.remove(i);
+                return;
+            }
+        }
+    }
+
+    @Override
     public synchronized void onActivityPinned(String packageName, int userId, int taskId,
             int stackId) {
         mChangeId++;
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 675cfe2..9f12484 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -166,6 +166,12 @@
         }
     }
 
+    @Override
+    public void onTaskRemoved(int taskId) {
+        Task.TaskKey dummyKey = new Task.TaskKey(taskId, 0, null, null, 0, 0);
+        mThumbnailCache.remove(dummyKey);
+    }
+
     public void setSystemUiProxy(ISystemUiProxy systemUiProxy) {
         mSystemUiProxy = systemUiProxy;
     }
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
index d05196b..57c5a27 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
@@ -187,6 +187,13 @@
     }
 
     /**
+     * Removes the cached thumbnail for the given task.
+     */
+    public void remove(Task.TaskKey key) {
+        mCache.remove(key);
+    }
+
+    /**
      * @return The cache size.
      */
     public int getCacheSize() {
diff --git a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
index 47ce44c..5e20e56 100644
--- a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
+++ b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
@@ -79,6 +79,8 @@
 
     @After
     public void tearDown() throws Throwable {
+        AppLaunchTracker.INSTANCE.initializeForTesting(null);
+        PredictionUiStateManager.INSTANCE.initializeForTesting(null);
         mDevice.unfreezeRotation();
     }
 
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 4b6b3ee..43d6311 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -44,17 +44,12 @@
 
 import org.junit.Before;
 import org.junit.Ignore;
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.TestWatcher;
 import org.junit.runner.RunWith;
 
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class TaplTestsQuickstep extends AbstractQuickStepTest {
-    @Rule
-    public TestWatcher mFailureWatcher = new TaplTestsLauncher3.FailureWatcher(mDevice);
-
     @Before
     public void setUp() throws Exception {
         super.setUp();
diff --git a/res/values-v29/styles.xml b/res/values-v29/styles.xml
new file mode 100644
index 0000000..7590594
--- /dev/null
+++ b/res/values-v29/styles.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* 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.
+*/
+-->
+
+<resources>
+    <!-- Launcher theme -->
+    <style name="BaseLauncherTheme" parent="@android:style/Theme.DeviceDefault.DayNight">
+        <item name="android:colorBackgroundCacheHint">@null</item>
+        <item name="android:colorEdgeEffect">#FF757575</item>
+        <item name="android:windowActionBar">false</item>
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowShowWallpaper">true</item>
+        <item name="folderTextColor">?attr/workspaceTextColor</item>
+        <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
+        <item name="android:enforceStatusBarContrast">false</item>
+        <item name="android:enforceNavigationBarContrast">false</item>
+    </style>
+</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 8116e30..881f65d 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -19,7 +19,7 @@
 
 <resources>
     <!-- Launcher theme -->
-    <style name="BaseLauncherTheme" parent="@android:style/Theme.DeviceDefault.DayNight">
+    <style name="BaseLauncherTheme" parent="@android:style/Theme.DeviceDefault.Light">
         <item name="android:colorBackgroundCacheHint">@null</item>
         <item name="android:colorEdgeEffect">#FF757575</item>
         <item name="android:windowActionBar">false</item>
diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml
index 7e72208..3455cb8 100644
--- a/res/xml/launcher_preferences.xml
+++ b/res/xml/launcher_preferences.xml
@@ -44,6 +44,12 @@
         android:defaultValue="@bool/allow_rotation"
         android:persistent="true" />
 
+    <SwitchPreference
+        android:key="pref_grid_options"
+        android:title="Enable grid options"
+        android:defaultValue="false"
+        android:persistent="true" />
+
     <androidx.preference.PreferenceScreen
         android:key="pref_developer_options"
         android:persistent="false"
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 3eb01e6..09fb244 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -845,7 +845,8 @@
      * width in {@link DeviceProfile#calculateCellWidth(int, int)}.
      */
     public int getUnusedHorizontalSpace() {
-        return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth);
+        return (mRotationMode.isTransposed ? getMeasuredHeight() : getMeasuredWidth())
+                - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth);
     }
 
     public Drawable getScrimBackground() {
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index d098b8c..c1f898c 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -210,9 +210,8 @@
                 + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
         hotseatBarSidePaddingEndPx =
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding);
-        // Add a bit of space between nav bar and hotseat in multi-window vertical bar layout.
-        hotseatBarSidePaddingStartPx = isMultiWindowMode && isVerticalBarLayout()
-                ? edgeMarginPx : 0;
+        // Add a bit of space between nav bar and hotseat in vertical bar layout.
+        hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? verticalDragHandleSizePx : 0;
         hotseatBarSizePx = ResourceUtils.pxFromDp(inv.iconSize, dm) + (isVerticalBarLayout()
                 ? (hotseatBarSidePaddingStartPx + hotseatBarSidePaddingEndPx)
                 : (res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size)
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 823fb6b..40eb912 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -72,7 +72,6 @@
 import android.view.Menu;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.OvershootInterpolator;
 import android.widget.Toast;
@@ -191,6 +190,8 @@
     private static final String RUNTIME_STATE = "launcher.state";
     // Type: PendingRequestArgs
     private static final String RUNTIME_STATE_PENDING_REQUEST_ARGS = "launcher.request_args";
+    // Type: int
+    private static final String RUNTIME_STATE_PENDING_REQUEST_CODE = "launcher.request_code";
     // Type: ActivityResultInfo
     private static final String RUNTIME_STATE_PENDING_ACTIVITY_RESULT = "launcher.activity_result";
     // Type: SparseArray<Parcelable>
@@ -264,6 +265,8 @@
      * {@link #startActivityForResult(Intent, int)} or {@link #requestPermissions(String[], int)}
      */
     private PendingRequestArgs mPendingRequestArgs;
+    // Request id for any pending activity result
+    private int mPendingActivityRequestCode = -1;
 
     public ViewGroupFocusHelper mFocusHandler;
 
@@ -303,6 +306,7 @@
         LauncherAppState app = LauncherAppState.getInstance(this);
         mOldConfig = new Configuration(getResources().getConfiguration());
         mModel = app.setLauncher(this);
+        mRotationHelper = new RotationHelper(this);
         InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
         initDeviceProfile(idp);
         idp.addOnChangeListener(this);
@@ -325,7 +329,6 @@
         setupViews();
         mPopupDataProvider = new PopupDataProvider(this);
 
-        mRotationHelper = new RotationHelper(this);
         mAppTransitionManager = LauncherAppTransitionManager.newInstance(this);
 
         boolean internalStateHandled = InternalStateHandler.handleCreate(this, getIntent());
@@ -396,12 +399,6 @@
                 }
             }
         });
-
-        if (FeatureFlags.FAKE_LANDSCAPE_UI.get()) {
-            WindowManager.LayoutParams lp = getWindow().getAttributes();
-            lp.rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
-            getWindow().setAttributes(lp);
-        }
     }
 
     @Override
@@ -428,9 +425,13 @@
         super.onConfigurationChanged(newConfig);
     }
 
+    private boolean supportsFakeLandscapeUI() {
+        return FeatureFlags.FAKE_LANDSCAPE_UI.get() && !mRotationHelper.homeScreenCanRotate();
+    }
+
     @Override
-    protected void reapplyUi() {
-        if (FeatureFlags.FAKE_LANDSCAPE_UI.get()) {
+    public void reapplyUi() {
+        if (supportsFakeLandscapeUI()) {
             mRotationMode = mStableDeviceProfile == null
                     ? RotationMode.NORMAL : UiFactory.getRotationMode(mDeviceProfile);
         }
@@ -486,7 +487,8 @@
             mDeviceProfile = mDeviceProfile.getMultiWindowProfile(this, mwSize);
         }
 
-        if (FeatureFlags.FAKE_LANDSCAPE_UI.get() && mDeviceProfile.isVerticalBarLayout()
+        if (supportsFakeLandscapeUI()
+                && mDeviceProfile.isVerticalBarLayout()
                 && !mDeviceProfile.isMultiWindowMode) {
             mStableDeviceProfile = mDeviceProfile.inv.portraitProfile;
             mRotationMode = UiFactory.getRotationMode(mDeviceProfile);
@@ -763,6 +765,7 @@
     @Override
     public void onActivityResult(
             final int requestCode, final int resultCode, final Intent data) {
+        mPendingActivityRequestCode = -1;
         handleActivityResult(requestCode, resultCode, data);
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onActivityResult(requestCode, resultCode, data);
@@ -891,9 +894,21 @@
 
             UiFactory.onLauncherStateOrResumeChanged(this);
             AppLaunchTracker.INSTANCE.get(this).onReturnedToHome();
+            resetPendingActivityResultIfNeeded();
         }
     }
 
+    private void resetPendingActivityResultIfNeeded() {
+        if (hasBeenResumed() && mPendingActivityRequestCode != -1 && isInState(NORMAL)) {
+            UiFactory.resetPendingActivityResults(this, mPendingActivityRequestCode);
+        }
+    }
+
+    protected void onStateSet(LauncherState state) {
+        getAppWidgetHost().setResumed(state == LauncherState.NORMAL);
+        resetPendingActivityResultIfNeeded();
+    }
+
     @Override
     protected void onResume() {
         RaceConditionTracker.onEvent(ON_RESUME_EVT, ENTER);
@@ -1010,6 +1025,7 @@
         if (requestArgs != null) {
             setWaitingForResult(requestArgs);
         }
+        mPendingActivityRequestCode = savedState.getInt(RUNTIME_STATE_PENDING_REQUEST_CODE);
 
         mPendingActivityResult = savedState.getParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT);
 
@@ -1393,6 +1409,8 @@
         if (mPendingRequestArgs != null) {
             outState.putParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS, mPendingRequestArgs);
         }
+        outState.putInt(RUNTIME_STATE_PENDING_REQUEST_CODE, mPendingActivityRequestCode);
+
         if (mPendingActivityResult != null) {
             outState.putParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT, mPendingActivityResult);
         }
@@ -1449,17 +1467,29 @@
 
     @Override
     public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
-        super.startActivityForResult(intent, requestCode, options);
+        if (requestCode != -1) {
+            mPendingActivityRequestCode = requestCode;
+        }
+        if (requestCode == -1
+                || !UiFactory.startActivityForResult(this, intent, requestCode, options)) {
+            super.startActivityForResult(intent, requestCode, options);
+        }
     }
 
     @Override
-    public void startIntentSenderForResult (IntentSender intent, int requestCode,
+    public void startIntentSenderForResult(IntentSender intent, int requestCode,
             Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) {
-        try {
-            super.startIntentSenderForResult(intent, requestCode,
-                fillInIntent, flagsMask, flagsValues, extraFlags, options);
-        } catch (IntentSender.SendIntentException e) {
-            throw new ActivityNotFoundException();
+        if (requestCode != -1) {
+            mPendingActivityRequestCode = requestCode;
+        }
+        if (requestCode == -1 || !UiFactory.startIntentSenderForResult(this, intent, requestCode,
+                fillInIntent, flagsMask, flagsValues, extraFlags, options)) {
+            try {
+                super.startIntentSenderForResult(intent, requestCode,
+                        fillInIntent, flagsMask, flagsValues, extraFlags, options);
+            } catch (IntentSender.SendIntentException e) {
+                throw new ActivityNotFoundException();
+            }
         }
     }
 
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 51079b0..eff58a7 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -18,7 +18,6 @@
 import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
 import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
-
 import static com.android.launcher3.TestProtocol.ALL_APPS_STATE_ORDINAL;
 import static com.android.launcher3.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
 import static com.android.launcher3.TestProtocol.NORMAL_STATE_ORDINAL;
@@ -203,6 +202,10 @@
         return UiFactory.getOverviewScaleAndTranslationForNormalState(launcher);
     }
 
+    public float getOverviewFullscreenProgress() {
+        return 0;
+    }
+
     public void onStateEnabled(Launcher launcher) {
         dispatchWindowStateChanged(launcher);
     }
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index f5040b3..b1a3fc9 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -452,7 +452,7 @@
         }
         mState = state;
         mState.onStateEnabled(mLauncher);
-        mLauncher.getAppWidgetHost().setResumed(state == LauncherState.NORMAL);
+        mLauncher.onStateSet(mState);
 
         if (state.disablePageClipping) {
             // Only disable clipping if needed, otherwise leave it as previous value.
diff --git a/src/com/android/launcher3/TestProtocol.java b/src/com/android/launcher3/TestProtocol.java
index ecbaa5b..eefecda 100644
--- a/src/com/android/launcher3/TestProtocol.java
+++ b/src/com/android/launcher3/TestProtocol.java
@@ -24,6 +24,7 @@
     public static final String SCROLL_Y_FIELD = "scrollY";
     public static final String STATE_FIELD = "state";
     public static final String SWITCHED_TO_STATE_MESSAGE = "TAPL_SWITCHED_TO_STATE";
+    public static final String SCROLL_FINISHED_MESSAGE = "TAPL_SCROLL_FINISHED";
     public static final String RESPONSE_MESSAGE_POSTFIX = "_RESPONSE";
     public static final int NORMAL_STATE_ORDINAL = 0;
     public static final int SPRING_LOADED_STATE_ORDINAL = 1;
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 02fc84b..796fd25 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -17,6 +17,7 @@
 package com.android.launcher3;
 
 import android.animation.ValueAnimator;
+import android.annotation.TargetApi;
 import android.app.ActivityManager;
 import android.app.WallpaperManager;
 import android.content.BroadcastReceiver;
@@ -32,12 +33,16 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ShortcutInfo;
 import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.DeadObjectException;
@@ -45,6 +50,7 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.TransactionTooLargeException;
+import android.provider.Settings;
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.TextUtils;
@@ -62,9 +68,11 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
 import com.android.launcher3.graphics.RotationMode;
+import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.views.Transposable;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 
@@ -82,6 +90,8 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_ICON_BADGED;
+
 /**
  * Various utilities shared amongst the Launcher's classes.
  */
@@ -97,9 +107,7 @@
     private static final Matrix sMatrix = new Matrix();
     private static final Matrix sInverseMatrix = new Matrix();
 
-    public static final boolean ATLEAST_Q = Build.VERSION.CODENAME.length() == 1
-            && Build.VERSION.CODENAME.charAt(0) >= 'Q'
-            && Build.VERSION.CODENAME.charAt(0) <= 'Z';
+    public static final boolean ATLEAST_Q = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
 
     public static final boolean ATLEAST_P =
             Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
@@ -125,6 +133,11 @@
             Build.TYPE.toLowerCase(Locale.ROOT).contains("debug") ||
             Build.TYPE.toLowerCase(Locale.ROOT).equals("eng");
 
+    public static boolean isDevelopersOptionsEnabled(Context context) {
+        return Settings.Global.getInt(context.getApplicationContext().getContentResolver(),
+                        Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
+    }
+
     // An intent extra to indicate the horizontal scroll of the wallpaper.
     public static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET";
     public static final String EXTRA_WALLPAPER_FLAVOR = "com.android.launcher3.WALLPAPER_FLAVOR";
@@ -152,6 +165,12 @@
         return Log.isLoggable(propertyName, Log.VERBOSE);
     }
 
+    public static boolean existsStyleWallpapers(Context context) {
+        ResolveInfo ri = context.getPackageManager().resolveActivity(
+                PackageManagerHelper.getStyleWallpapersIntent(context), 0);
+        return ri != null;
+    }
+
     /**
      * Given a coordinate relative to the descendant, find the coordinate in a parent view's
      * coordinates.
@@ -654,6 +673,40 @@
         }
     }
 
+    /**
+     * For apps icons and shortcut icons that have badges, this method creates a drawable that can
+     * later on be rendered on top of the layers for the badges. For app icons, work profile badges
+     * can only be applied. For deep shortcuts, when dragged from the pop up container, there's no
+     * badge. When dragged from workspace or folder, it may contain app AND/OR work profile badge
+     **/
+    @TargetApi(Build.VERSION_CODES.O)
+    public static Drawable getBadge(Launcher launcher, ItemInfo info, Object obj) {
+        LauncherAppState appState = LauncherAppState.getInstance(launcher);
+        int iconSize = appState.getInvariantDeviceProfile().iconBitmapSize;
+        if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+            boolean iconBadged = (info instanceof ItemInfoWithIcon)
+                    && (((ItemInfoWithIcon) info).runtimeStatusFlags & FLAG_ICON_BADGED) > 0;
+            if ((info.id == ItemInfo.NO_ID && !iconBadged)
+                    || !(obj instanceof ShortcutInfo)) {
+                // The item is not yet added on home screen.
+                return new FixedSizeEmptyDrawable(iconSize);
+            }
+            ShortcutInfo si = (ShortcutInfo) obj;
+            LauncherIcons li = LauncherIcons.obtain(appState.getContext());
+            Bitmap badge = li.getShortcutInfoBadge(si, appState.getIconCache()).iconBitmap;
+            li.recycle();
+            float badgeSize = launcher.getResources().getDimension(R.dimen.profile_badge_size);
+            float insetFraction = (iconSize - badgeSize) / iconSize;
+            return new InsetDrawable(new FastBitmapDrawable(badge),
+                    insetFraction, insetFraction, 0, 0);
+        } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
+            return ((FolderAdaptiveIcon) obj).getBadge();
+        } else {
+            return launcher.getPackageManager()
+                    .getUserBadgedIcon(new FixedSizeEmptyDrawable(iconSize), info.user);
+        }
+    }
+
     public static int[] getIntArrayFromString(String tokenized) {
         StringTokenizer tokenizer = new StringTokenizer(tokenized, ",");
         int[] array = new int[tokenizer.countTokens()];
@@ -672,4 +725,24 @@
         }
         return str.toString();
     }
+
+    private static class FixedSizeEmptyDrawable extends ColorDrawable {
+
+        private final int mSize;
+
+        public FixedSizeEmptyDrawable(int size) {
+            super(Color.TRANSPARENT);
+            mSize = size;
+        }
+
+        @Override
+        public int getIntrinsicHeight() {
+            return mSize;
+        }
+
+        @Override
+        public int getIntrinsicWidth() {
+            return mSize;
+        }
+    }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 180ca48..548d5de 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -32,7 +32,8 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
-import com.android.launcher3.graphics.DrawableFactory;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -419,4 +420,13 @@
     public boolean hasOverlappingRendering() {
         return false;
     }
+
+    @Override
+    public void onScrollStateChanged(int state) {
+        super.onScrollStateChanged(state);
+
+        if (state == SCROLL_STATE_IDLE && Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
+        }
+    }
 }
diff --git a/src/com/android/launcher3/allapps/DiscoveryBounce.java b/src/com/android/launcher3/allapps/DiscoveryBounce.java
index 7467119..1d62b43 100644
--- a/src/com/android/launcher3/allapps/DiscoveryBounce.java
+++ b/src/com/android/launcher3/allapps/DiscoveryBounce.java
@@ -24,7 +24,6 @@
 import android.animation.Animator;
 import android.animation.AnimatorInflater;
 import android.animation.AnimatorListenerAdapter;
-import android.app.ActivityManager;
 import android.content.SharedPreferences;
 import android.os.Handler;
 import android.view.MotionEvent;
@@ -32,6 +31,7 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.states.InternalStateHandler;
 
@@ -134,7 +134,7 @@
                 && !shouldShowForWorkProfile(launcher))
                 || AbstractFloatingView.getTopOpenView(launcher) != null
                 || UserManagerCompat.getInstance(launcher).isDemoUser()
-                || ActivityManager.isRunningInTestHarness()) {
+                || Utilities.IS_RUNNING_IN_TEST_HARNESS) {
             return;
         }
 
@@ -159,7 +159,7 @@
                 || (launcher.getSharedPrefs().getBoolean(SHELF_BOUNCE_SEEN, false)
                 && !shouldShowForWorkProfile(launcher))
                 || UserManagerCompat.getInstance(launcher).isDemoUser()
-                || ActivityManager.isRunningInTestHarness()) {
+                || Utilities.IS_RUNNING_IN_TEST_HARNESS) {
             return;
         }
 
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index b4d0c54..86f773f 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -62,6 +62,13 @@
         sendEventToTest(accessibilityManager, TestProtocol.SWITCHED_TO_STATE_MESSAGE, parcel);
     }
 
+    public static void sendScrollFinishedEventToTest(Context context) {
+        final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
+        if (accessibilityManager == null) return;
+
+        sendEventToTest(accessibilityManager, TestProtocol.SCROLL_FINISHED_MESSAGE, null);
+    }
+
     private static void sendEventToTest(
             AccessibilityManager accessibilityManager, String eventTag, Bundle data) {
         final AccessibilityEvent e = AccessibilityEvent.obtain(
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 70df97a..7e20d11 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -58,9 +58,7 @@
     }
 
     public static boolean showFlagTogglerUi(Context context) {
-        return Utilities.IS_DEBUG_DEVICE &&
-                Settings.Global.getInt(context.getApplicationContext().getContentResolver(),
-                        Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
+        return Utilities.IS_DEBUG_DEVICE && Utilities.isDevelopersOptionsEnabled(context);
     }
 
     public static final boolean IS_DOGFOOD_BUILD = false;
@@ -111,7 +109,7 @@
             "Show chip hints and gleams on the overview screen");
 
     public static final TogglableFlag FAKE_LANDSCAPE_UI = new TogglableFlag(
-            "FAKE_LANDSCAPE_UI", false,
+            "FAKE_LANDSCAPE_UI", true,
             "Rotate launcher UI instead of using transposed layout");
 
     public static void initialize(Context context) {
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 3ab97b0..7af12c5 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -16,7 +16,7 @@
 
 package com.android.launcher3.dragndrop;
 
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_ICON_BADGED;
+import static com.android.launcher3.Utilities.getBadge;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -24,7 +24,6 @@
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.annotation.TargetApi;
-import android.content.pm.ShortcutInfo;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -37,7 +36,6 @@
 import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.InsetDrawable;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
@@ -45,11 +43,11 @@
 
 import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.ItemInfo;
-import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.FirstFrameAnimatorHelper;
@@ -64,7 +62,7 @@
 import androidx.dynamicanimation.animation.SpringAnimation;
 import androidx.dynamicanimation.animation.SpringForce;
 
-public class DragView extends View {
+public class DragView extends View implements LauncherStateManager.StateListener {
     private static final ColorMatrix sTempMatrix1 = new ColorMatrix();
     private static final ColorMatrix sTempMatrix2 = new ColorMatrix();
 
@@ -176,6 +174,27 @@
         setElevation(getResources().getDimension(R.dimen.drag_elevation));
     }
 
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mLauncher.getStateManager().addStateListener(this);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mLauncher.getStateManager().removeStateListener(this);
+    }
+
+    @Override
+    public void onStateTransitionStart(LauncherState toState) { }
+
+    @Override
+    public void onStateTransitionComplete(LauncherState finalState) {
+        setVisibility((finalState == LauncherState.NORMAL
+                || finalState == LauncherState.SPRING_LOADED) ? VISIBLE : INVISIBLE);
+    }
+
     /**
      * Initialize {@code #mIconDrawable} if the item can be represented using
      * an {@link AdaptiveIconDrawable} or {@link FolderAdaptiveIcon}.
@@ -195,7 +214,6 @@
         new Handler(workerLooper).postAtFrontOfQueue(new Runnable() {
             @Override
             public void run() {
-                LauncherAppState appState = LauncherAppState.getInstance(mLauncher);
                 Object[] outObj = new Object[1];
                 int w = mBitmap.getWidth();
                 int h = mBitmap.getHeight();
@@ -211,7 +229,7 @@
                     // Badge is applied after icon normalization so the bounds for badge should not
                     // be scaled down due to icon normalization.
                     Rect badgeBounds = new Rect(bounds);
-                    mBadge = getBadge(info, appState, outObj[0]);
+                    mBadge = getBadge(mLauncher, info, outObj[0]);
                     mBadge.setBounds(badgeBounds);
 
                     // Do not draw the background in case of folder as its translucent
@@ -307,40 +325,6 @@
         invalidate();
     }
 
-    /**
-     * For apps icons and shortcut icons that have badges, this method creates a drawable that can
-     * later on be rendered on top of the layers for the badges. For app icons, work profile badges
-     * can only be applied. For deep shortcuts, when dragged from the pop up container, there's no
-     * badge. When dragged from workspace or folder, it may contain app AND/OR work profile badge
-     **/
-
-    @TargetApi(Build.VERSION_CODES.O)
-    private Drawable getBadge(ItemInfo info, LauncherAppState appState, Object obj) {
-        int iconSize = appState.getInvariantDeviceProfile().iconBitmapSize;
-        if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
-            boolean iconBadged = (info instanceof ItemInfoWithIcon)
-                    && (((ItemInfoWithIcon) info).runtimeStatusFlags & FLAG_ICON_BADGED) > 0;
-            if ((info.id == ItemInfo.NO_ID && !iconBadged)
-                    || !(obj instanceof ShortcutInfo)) {
-                // The item is not yet added on home screen.
-                return new FixedSizeEmptyDrawable(iconSize);
-            }
-            ShortcutInfo si = (ShortcutInfo) obj;
-            LauncherIcons li = LauncherIcons.obtain(appState.getContext());
-            Bitmap badge = li.getShortcutInfoBadge(si, appState.getIconCache()).iconBitmap;
-            li.recycle();
-            float badgeSize = mLauncher.getResources().getDimension(R.dimen.profile_badge_size);
-            float insetFraction = (iconSize - badgeSize) / iconSize;
-            return new InsetDrawable(new FastBitmapDrawable(badge),
-                    insetFraction, insetFraction, 0, 0);
-        } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
-            return ((FolderAdaptiveIcon) obj).getBadge();
-        } else {
-            return mLauncher.getPackageManager()
-                    .getUserBadgedIcon(new FixedSizeEmptyDrawable(iconSize), info.user);
-        }
-    }
-
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         setMeasuredDimension(mBitmap.getWidth(), mBitmap.getHeight());
@@ -626,24 +610,4 @@
             mSpring.animateToFinalPosition(Utilities.boundToRange(value, -mDelta, mDelta));
         }
     }
-
-    private static class FixedSizeEmptyDrawable extends ColorDrawable {
-
-        private final int mSize;
-
-        public FixedSizeEmptyDrawable(int size) {
-            super(Color.TRANSPARENT);
-            mSize = size;
-        }
-
-        @Override
-        public int getIntrinsicHeight() {
-            return mSize;
-        }
-
-        @Override
-        public int getIntrinsicWidth() {
-            return mSize;
-        }
-    }
 }
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 9373976..962f215 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -35,6 +35,8 @@
 import android.view.View;
 import android.view.animation.AnimationUtils;
 
+import androidx.core.graphics.ColorUtils;
+
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.Launcher;
@@ -152,7 +154,8 @@
         final float yDistance = initialY - lp.y;
 
         // Set up the Folder background.
-        final int finalColor = Themes.getAttrColor(mContext, R.attr.folderFillColor);
+        final int finalColor = ColorUtils.setAlphaComponent(
+                Themes.getAttrColor(mContext, R.attr.folderFillColor), 255);
         final int initialColor = setColorAlphaBound(
                 finalColor, mPreviewBackground.getBackgroundAlpha());
         mFolderBackground.mutate();
diff --git a/src/com/android/launcher3/logging/EventLogArray.java b/src/com/android/launcher3/logging/EventLogArray.java
index bfb3792..f20f365 100644
--- a/src/com/android/launcher3/logging/EventLogArray.java
+++ b/src/com/android/launcher3/logging/EventLogArray.java
@@ -18,6 +18,7 @@
 
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.Locale;
 
@@ -76,8 +77,12 @@
         nextIndex = (nextIndex + 1) % logs.length;
     }
 
+    public void clear() {
+        Arrays.setAll(logs, (i) -> null);
+    }
+
     public void dump(String prefix, PrintWriter writer) {
-        writer.println(prefix + name + " event history:");
+        writer.println(prefix + "EventLog (" + name + ") history:");
         SimpleDateFormat sdf = new SimpleDateFormat("  HH:mm:ss.SSSZ  ", Locale.US);
         Date date = new Date();
 
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index 6e7188f..18b6094 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -24,6 +24,10 @@
 import android.app.Activity;
 import android.app.DialogFragment;
 import android.app.Fragment;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -32,6 +36,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.GridOptionsProvider;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.SecureSettingsObserver;
 
@@ -47,7 +52,8 @@
  * Settings activity for Launcher. Currently implements the following setting: Allow rotation
  */
 public class SettingsActivity extends Activity
-        implements OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback {
+        implements OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback,
+        SharedPreferences.OnSharedPreferenceChangeListener{
 
     private static final String DEVELOPER_OPTIONS_KEY = "pref_developer_options";
     private static final String FLAGS_PREFERENCE_KEY = "flag_toggler";
@@ -61,6 +67,8 @@
     private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600;
     public static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
 
+    public static final String GRID_OPTIONS_PREFERENCE_KEY = "pref_grid_options";
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -79,6 +87,28 @@
                     .replace(android.R.id.content, f)
                     .commit();
         }
+        Utilities.getPrefs(getApplicationContext()).registerOnSharedPreferenceChangeListener(this);
+    }
+    @Override
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+        if (GRID_OPTIONS_PREFERENCE_KEY.equals(key)) {
+
+            final ComponentName cn = new ComponentName(getApplicationContext(),
+                    GridOptionsProvider.class);
+            Context c = getApplicationContext();
+            int oldValue = c.getPackageManager().getComponentEnabledSetting(cn);
+            int newValue;
+            if (Utilities.getPrefs(c).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)) {
+                newValue = PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+            } else {
+                newValue = PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+            }
+
+            if (oldValue != newValue) {
+                c.getPackageManager().setComponentEnabledSetting(cn, newValue,
+                        PackageManager.DONT_KILL_APP);
+            }
+        }
     }
 
     private boolean startFragment(String fragment, Bundle args, String key) {
@@ -200,6 +230,10 @@
                     // Show if plugins are enabled or flag UI is enabled.
                     return FeatureFlags.showFlagTogglerUi(getContext()) ||
                             PluginManagerWrapper.hasPlugins(getContext());
+                case GRID_OPTIONS_PREFERENCE_KEY:
+                    return Utilities.isDevelopersOptionsEnabled(getContext()) &&
+                            Utilities.IS_DEBUG_DEVICE &&
+                            Utilities.existsStyleWallpapers(getContext());
             }
 
             return true;
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index fb41ea1..3727fa6 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -20,13 +20,16 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
 
-import android.app.Activity;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.content.res.Resources;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
 
+import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.UiThreadHelper;
 
 /**
@@ -49,7 +52,7 @@
     public static final int REQUEST_ROTATE = 1;
     public static final int REQUEST_LOCK = 2;
 
-    private final Activity mActivity;
+    private final Launcher mLauncher;
     private final SharedPreferences mPrefs;
 
     private boolean mIgnoreAutoRotateSettings;
@@ -70,13 +73,13 @@
 
     private int mLastActivityFlags = -1;
 
-    public RotationHelper(Activity activity) {
-        mActivity = activity;
+    public RotationHelper(Launcher launcher) {
+        mLauncher = launcher;
 
         // On large devices we do not handle auto-rotate differently.
-        mIgnoreAutoRotateSettings = mActivity.getResources().getBoolean(R.bool.allow_rotation);
+        mIgnoreAutoRotateSettings = mLauncher.getResources().getBoolean(R.bool.allow_rotation);
         if (!mIgnoreAutoRotateSettings) {
-            mPrefs = Utilities.getPrefs(mActivity);
+            mPrefs = Utilities.getPrefs(mLauncher);
             mPrefs.registerOnSharedPreferenceChangeListener(this);
             mAutoRotateEnabled = mPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
                     getAllowRotationDefaultValue());
@@ -85,11 +88,32 @@
         }
     }
 
+    public boolean homeScreenCanRotate() {
+        return mIgnoreAutoRotateSettings || mAutoRotateEnabled
+                || mStateHandlerRequest != REQUEST_NONE;
+    }
+
+    private void updateRotationAnimation() {
+        if (FeatureFlags.FAKE_LANDSCAPE_UI.get()) {
+            WindowManager.LayoutParams lp = mLauncher.getWindow().getAttributes();
+            lp.rotationAnimation = homeScreenCanRotate()
+                    ? WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE
+                    : WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
+            mLauncher.getWindow().setAttributes(lp);
+        }
+    }
+
     @Override
     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
+        boolean wasRotationEnabled = mAutoRotateEnabled;
         mAutoRotateEnabled = mPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
                 getAllowRotationDefaultValue());
-        notifyChange();
+        if (mAutoRotateEnabled != wasRotationEnabled) {
+
+            notifyChange();
+            updateRotationAnimation();
+            mLauncher.reapplyUi();
+        }
     }
 
     public void setStateHandlerRequest(int request) {
@@ -109,7 +133,7 @@
     // Used by tests only.
     public void forceAllowRotationForTesting(boolean allowRotation) {
         mIgnoreAutoRotateSettings =
-                allowRotation || mActivity.getResources().getBoolean(R.bool.allow_rotation);
+                allowRotation || mLauncher.getResources().getBoolean(R.bool.allow_rotation);
         notifyChange();
     }
 
@@ -117,6 +141,7 @@
         if (!mInitialized) {
             mInitialized = true;
             notifyChange();
+            updateRotationAnimation();
         }
     }
 
@@ -150,7 +175,7 @@
         }
         if (activityFlags != mLastActivityFlags) {
             mLastActivityFlags = activityFlags;
-            UiThreadHelper.setOrientationAsync(mActivity, activityFlags);
+            UiThreadHelper.setOrientationAsync(mLauncher, activityFlags);
         }
     }
 
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 7439ac1..7d3a941 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -172,6 +172,11 @@
         }
     }
 
+    public static Intent getStyleWallpapersIntent(Context context) {
+        return new Intent(Intent.ACTION_SET_WALLPAPER).setComponent(
+                new ComponentName(context.getString(R.string.wallpaper_picker_package),
+                "com.android.customization.picker.CustomizationPickerActivity"));
+    }
 
     /**
      * Starts the details activity for {@code info}
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index cd0ae3d..e5c75c3 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.views;
 
+import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA;
+import static com.android.launcher3.Utilities.getBadge;
 import static com.android.launcher3.Utilities.mapToRange;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
@@ -62,6 +64,9 @@
 
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
 
 /**
  * A view that is created to look like another view with the purpose of creating fluid animations.
@@ -76,6 +81,39 @@
     private static final RectF sTmpRectF = new RectF();
     private static final Object[] sTmpObjArray = new Object[1];
 
+    // We spring the foreground drawable relative to the icon's movement in the DragLayer.
+    // We then use these two factor values to scale the movement of the fg within this view.
+    private static final int FG_TRANS_X_FACTOR = 80;
+    private static final int FG_TRANS_Y_FACTOR = 100;
+
+    private static final FloatPropertyCompat<FloatingIconView> mFgTransYProperty
+            = new FloatPropertyCompat<FloatingIconView>("FloatingViewFgTransY") {
+        @Override
+        public float getValue(FloatingIconView view) {
+            return view.mFgTransY;
+        }
+
+        @Override
+        public void setValue(FloatingIconView view, float transY) {
+            view.mFgTransY = transY;
+            view.invalidate();
+        }
+    };
+
+    private static final FloatPropertyCompat<FloatingIconView> mFgTransXProperty
+            = new FloatPropertyCompat<FloatingIconView>("FloatingViewFgTransX") {
+        @Override
+        public float getValue(FloatingIconView view) {
+            return view.mFgTransX;
+        }
+
+        @Override
+        public void setValue(FloatingIconView view, float transX) {
+            view.mFgTransX = transX;
+            view.invalidate();
+        }
+    };
+
     private Runnable mEndRunnable;
     private CancellationSignal mLoadIconSignal;
 
@@ -85,6 +123,7 @@
     private boolean mIsVerticalBarLayout = false;
     private boolean mIsAdaptiveIcon = false;
 
+    private @Nullable Drawable mBadge;
     private @Nullable Drawable mForeground;
     private @Nullable Drawable mBackground;
     private float mRotation;
@@ -100,17 +139,30 @@
 
     private final Rect mOutline = new Rect();
     private final Rect mFinalDrawableBounds = new Rect();
-    private final Rect mBgDrawableBounds = new Rect();
 
     private AnimatorSet mFadeAnimatorSet;
     private ListenerView mListenerView;
 
+    private final SpringAnimation mFgSpringY;
+    private float mFgTransY;
+    private final SpringAnimation mFgSpringX;
+    private float mFgTransX;
+
     private FloatingIconView(Launcher launcher) {
         super(launcher);
         mLauncher = launcher;
         mBlurSizeOutline = getResources().getDimensionPixelSize(
                 R.dimen.blur_size_medium_outline);
         mListenerView = new ListenerView(launcher, null);
+
+        mFgSpringX = new SpringAnimation(this, mFgTransXProperty)
+                .setSpring(new SpringForce()
+                .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+                .setStiffness(SpringForce.STIFFNESS_LOW));
+        mFgSpringY = new SpringAnimation(this, mFgTransYProperty)
+                .setSpring(new SpringForce()
+                        .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+                        .setStiffness(SpringForce.STIFFNESS_LOW));
     }
 
     @Override
@@ -158,7 +210,12 @@
                 Math.max(shapeProgressStart, progress), shapeProgressStart, 1f, 0, toMax,
                 LINEAR), 0, 1);
 
-        mOutline.bottom = (int) (rect.height() / scale);
+        if (mIsVerticalBarLayout) {
+            mOutline.right = (int) (rect.width() / scale);
+        } else {
+            mOutline.bottom = (int) (rect.height() / scale);
+        }
+
         mTaskCornerRadius = cornerRadius / scale;
         if (mIsAdaptiveIcon) {
             if (!isOpening && shapeRevealProgress >= 0) {
@@ -178,7 +235,32 @@
                 mRevealAnimator.setCurrentFraction(shapeRevealProgress);
             }
 
-            setBackgroundDrawableBounds(mOutline.height() / minSize);
+            float drawableScale = (mIsVerticalBarLayout ? mOutline.width() : mOutline.height())
+                    / minSize;
+            setBackgroundDrawableBounds(drawableScale);
+            if (isOpening) {
+                // Center align foreground
+                int height = mFinalDrawableBounds.height();
+                int width = mFinalDrawableBounds.width();
+                int diffY = mIsVerticalBarLayout ? 0
+                        : (int) (((height * drawableScale) - height) / 2);
+                int diffX = mIsVerticalBarLayout ? (int) (((width * drawableScale) - width) / 2)
+                        : 0;
+                sTmpRect.set(mFinalDrawableBounds);
+                sTmpRect.offset(diffX, diffY);
+                mForeground.setBounds(sTmpRect);
+            } else {
+                // Spring the foreground relative to the icon's movement within the DragLayer.
+                int diffX = (int) (dX / mLauncher.getDeviceProfile().availableWidthPx
+                        * FG_TRANS_X_FACTOR);
+                int diffY = (int) (dY / mLauncher.getDeviceProfile().availableHeightPx
+                        * FG_TRANS_Y_FACTOR);
+
+                mFgSpringX.animateToFinalPosition(diffX);
+                mFgSpringY.animateToFinalPosition(diffY);
+            }
+
+
         }
         invalidate();
         invalidateOutline();
@@ -289,7 +371,9 @@
             if (supportsAdaptiveIcons) {
                 drawable = Utilities.getFullDrawable(mLauncher, info, lp.width, lp.height,
                         false, sTmpObjArray);
-                if (!(drawable instanceof AdaptiveIconDrawable)) {
+                if ((drawable instanceof AdaptiveIconDrawable)) {
+                    mBadge = getBadge(mLauncher, info, sTmpObjArray[0]);
+                } else {
                     // The drawable we get back is not an adaptive icon, so we need to use the
                     // BubbleTextView icon that is already legacy treated.
                     drawable = btvIcon;
@@ -348,8 +432,17 @@
 
                 mStartRevealRect.set(0, 0, originalWidth, originalHeight);
 
+                if (mBadge != null) {
+                    mBadge.setBounds(mStartRevealRect);
+                    if (!isOpening) {
+                        DRAWABLE_ALPHA.set(mBadge, 0);
+                    }
+
+                }
+
                 if (!isFolderIcon) {
-                    mStartRevealRect.inset(mBlurSizeOutline, mBlurSizeOutline);
+                    Utilities.scaleRectAboutCenter(mStartRevealRect,
+                            IconShape.getNormalizationScale());
                 }
 
                 float aspectRatio = mLauncher.getDeviceProfile().aspectRatio;
@@ -393,17 +486,15 @@
     }
 
     private void setBackgroundDrawableBounds(float scale) {
-        mBgDrawableBounds.set(mFinalDrawableBounds);
-        Utilities.scaleRectAboutCenter(mBgDrawableBounds, scale);
+        sTmpRect.set(mFinalDrawableBounds);
+        Utilities.scaleRectAboutCenter(sTmpRect, scale);
         // Since the drawable is at the top of the view, we need to offset to keep it centered.
         if (mIsVerticalBarLayout) {
-            mBgDrawableBounds.offsetTo((int) (mFinalDrawableBounds.left  * scale),
-                    mBgDrawableBounds.top);
+            sTmpRect.offsetTo((int) (mFinalDrawableBounds.left * scale), sTmpRect.top);
         } else {
-            mBgDrawableBounds.offsetTo(mBgDrawableBounds.left,
-                    (int) (mFinalDrawableBounds.top * scale));
+            sTmpRect.offsetTo(sTmpRect.left, (int) (mFinalDrawableBounds.top * scale));
         }
-        mBackground.setBounds(mBgDrawableBounds);
+        mBackground.setBounds(sTmpRect);
     }
 
     @WorkerThread
@@ -448,7 +539,13 @@
             mBackground.draw(canvas);
         }
         if (mForeground != null) {
+            int count2 = canvas.save();
+            canvas.translate(mFgTransX, mFgTransY);
             mForeground.draw(canvas);
+            canvas.restoreToCount(count2);
+        }
+        if (mBadge != null) {
+            mBadge.draw(canvas);
         }
         canvas.restoreToCount(count);
     }
@@ -568,6 +665,12 @@
             }
         });
 
+        if (mBadge != null) {
+            ObjectAnimator badgeFade = ObjectAnimator.ofInt(mBadge, DRAWABLE_ALPHA, 255);
+            badgeFade.addUpdateListener(valueAnimator -> invalidate());
+            fade.play(badgeFade);
+        }
+
         if (originalView instanceof BubbleTextView) {
             BubbleTextView btv = (BubbleTextView) originalView;
             btv.forceHideDot(true);
@@ -624,7 +727,6 @@
         mBackground = null;
         mClipPath = null;
         mFinalDrawableBounds.setEmpty();
-        mBgDrawableBounds.setEmpty();
         if (mRevealAnimator != null) {
             mRevealAnimator.cancel();
         }
@@ -639,5 +741,10 @@
         mOnTargetChangeRunnable = null;
         mTaskCornerRadius = 0;
         mOutline.setEmpty();
+        mFgTransY = 0;
+        mFgSpringX.cancel();
+        mFgTransX = 0;
+        mFgSpringY.cancel();
+        mBadge = null;
     }
 }
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 7062369..63f7427 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -152,9 +152,9 @@
         RectF target = new RectF(x - halfSize, y - halfSize, x + halfSize, y + halfSize);
 
         ArrayList<OptionItem> options = new ArrayList<>();
-        int resString = existsStyleWallpapers(launcher) ?
+        int resString = Utilities.existsStyleWallpapers(launcher) ?
                 R.string.styles_wallpaper_button_text : R.string.wallpaper_button_text;
-        int resDrawable = existsStyleWallpapers(launcher) ?
+        int resDrawable = Utilities.existsStyleWallpapers(launcher) ?
                 R.drawable.ic_palette : R.drawable.ic_wallpaper;
         options.add(new OptionItem(resString, resDrawable,
                 ControlType.WALLPAPER_BUTTON, OptionsPopupView::startWallpaperPicker));
@@ -168,14 +168,6 @@
         show(launcher, target, options);
     }
 
-    private static boolean existsStyleWallpapers(Launcher launcher) {
-        Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER);
-        intent.setComponent(new ComponentName(launcher.getString(R.string.wallpaper_picker_package),
-                "com.android.customization.picker.CustomizationPickerActivity"));
-        ResolveInfo ri = launcher.getPackageManager().resolveActivity(intent, 0);
-        return ri != null;
-    }
-
     public static boolean onWidgetsClicked(View view) {
         return openWidgets(Launcher.getLauncher(view.getContext()));
     }
@@ -212,7 +204,7 @@
                 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
                 .putExtra(EXTRA_WALLPAPER_OFFSET,
                         launcher.getWorkspace().getWallpaperOffsetForCenterPage());
-        if (!existsStyleWallpapers(launcher)) {
+        if (!Utilities.existsStyleWallpapers(launcher)) {
             intent.putExtra(EXTRA_WALLPAPER_FLAVOR, "wallpaper_only");
         } else {
             intent.putExtra(EXTRA_WALLPAPER_FLAVOR, "focus_wallpaper");
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
index 6008d14..550327d 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -18,6 +18,9 @@
 
 import android.app.Activity;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
 import android.os.CancellationSignal;
 
 import com.android.launcher3.DeviceProfile;
@@ -79,4 +82,18 @@
     public static RotationMode getRotationMode(DeviceProfile dp) {
         return RotationMode.NORMAL;
     }
+
+    public static boolean startIntentSenderForResult(Activity activity, IntentSender intent,
+            int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+            Bundle options) {
+        return false;
+    }
+
+    public static boolean startActivityForResult(Activity activity, Intent intent, int requestCode,
+            Bundle options) {
+        return false;
+    }
+
+    public static void resetPendingActivityResults(Launcher launcher, int requestCode) { }
+
 }
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index b6878bb..ca6d968 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -64,8 +64,12 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.rules.TestRule;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
+import java.io.ByteArrayOutputStream;
+import java.io.File;
 import java.io.IOException;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -89,23 +93,22 @@
     public static final long DEFAULT_UI_TIMEOUT = 10000;
     protected static final int LONG_WAIT_TIME_MS = 60000;
     private static final String TAG = "AbstractLauncherUiTest";
+    private static int sScreenshotCount = 0;
 
     protected MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
-    protected final UiDevice mDevice;
+    protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation());
     protected final LauncherInstrumentation mLauncher;
     protected Context mTargetContext;
     protected String mTargetPackage;
 
     protected AbstractLauncherUiTest() {
-        final Instrumentation instrumentation = getInstrumentation();
-        mDevice = UiDevice.getInstance(instrumentation);
         try {
             mDevice.setOrientationNatural();
         } catch (RemoteException e) {
             throw new RuntimeException(e);
         }
         if (TestHelpers.isInLauncherProcess()) Utilities.enableRunningInTestHarnessForTests();
-        mLauncher = new LauncherInstrumentation(instrumentation);
+        mLauncher = new LauncherInstrumentation(getInstrumentation());
     }
 
     @Rule
@@ -163,6 +166,36 @@
                 }
             } : base;
 
+    @Rule
+    public TestWatcher mFailureWatcher = new TestWatcher() {
+        private void dumpViewHierarchy() {
+            final ByteArrayOutputStream stream = new ByteArrayOutputStream();
+            try {
+                mDevice.dumpWindowHierarchy(stream);
+                stream.flush();
+                stream.close();
+                for (String line : stream.toString().split("\\r?\\n")) {
+                    Log.e(TAG, line.trim());
+                }
+            } catch (IOException e) {
+                Log.e(TAG, "error dumping XML to logcat", e);
+            }
+        }
+
+        @Override
+        protected void failed(Throwable e, Description description) {
+            if (mDevice == null) return;
+            final String pathname = getInstrumentation().getTargetContext().
+                    getFilesDir().getPath() + "/TaplTestScreenshot" + sScreenshotCount++ + ".png";
+            Log.e(TAG, "Failed test " + description.getMethodName() +
+                    ", screenshot will be saved to " + pathname +
+                    ", track trace is below, UI object dump is further below:\n" +
+                    Log.getStackTraceString(e));
+            dumpViewHierarchy();
+            mDevice.takeScreenshot(new File(pathname));
+        }
+    };
+
     @Before
     public void setUp() throws Exception {
         mTargetContext = InstrumentationRegistry.getTargetContext();
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 7578dff..d4cfe3a 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -24,11 +24,8 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
-import android.util.Log;
-
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
-import androidx.test.uiautomator.UiDevice;
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
@@ -45,62 +42,14 @@
 import com.android.launcher3.widget.WidgetsRecyclerView;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.TestWatcher;
-import org.junit.runner.Description;
 import org.junit.runner.RunWith;
 
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class TaplTestsLauncher3 extends AbstractLauncherUiTest {
-    private static final String TAG = "TaplTestsAosp";
     private static final String APP_NAME = "LauncherTestApp";
 
-    private static int sScreenshotCount = 0;
-
-    public static class FailureWatcher extends TestWatcher {
-        private UiDevice mDevice;
-
-        public FailureWatcher(UiDevice device) {
-            this.mDevice = device;
-        }
-
-        private void dumpViewHierarchy() {
-            final ByteArrayOutputStream stream = new ByteArrayOutputStream();
-            try {
-                mDevice.dumpWindowHierarchy(stream);
-                stream.flush();
-                stream.close();
-                for (String line : stream.toString().split("\\r?\\n")) {
-                    Log.e(TAG, line.trim());
-                }
-            } catch (IOException e) {
-                Log.e(TAG, "error dumping XML to logcat", e);
-            }
-        }
-
-        @Override
-        protected void failed(Throwable e, Description description) {
-            if (mDevice == null) return;
-            final String pathname = getInstrumentation().getTargetContext().
-                    getFilesDir().getPath() + "/TaplTestScreenshot" + sScreenshotCount++ + ".png";
-            Log.e(TAG, "Failed test " + description.getMethodName() +
-                    ", screenshot will be saved to " + pathname +
-                    ", track trace is below, UI object dump is further below:\n" +
-                    Log.getStackTraceString(e));
-            dumpViewHierarchy();
-            mDevice.takeScreenshot(new File(pathname));
-        }
-    }
-
-    @Rule
-    public TestWatcher mFailureWatcher = new FailureWatcher(mDevice);
-
     @Before
     public void setUp() throws Exception {
         super.setUp();
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index b66fa8a..a57d7ba 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -17,26 +17,25 @@
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertTrue;
 
 import android.app.PendingIntent;
 import android.appwidget.AppWidgetManager;
 import android.content.Intent;
 import android.graphics.Color;
+import android.view.View;
+
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
-import android.view.View;
 
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.R;
-import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace.ItemOperator;
+import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.testcomponent.AppWidgetNoConfig;
 import com.android.launcher3.testcomponent.AppWidgetWithConfig;
@@ -49,7 +48,6 @@
 import com.android.launcher3.widget.WidgetCell;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -181,7 +179,9 @@
 
         // Accept confirmation:
         BlockingBroadcastReceiver resultReceiver = new BlockingBroadcastReceiver(mCallbackAction);
-        mDevice.wait(Until.findObject(By.text("Add automatically")), DEFAULT_UI_TIMEOUT).click();
+        mDevice.wait(Until.findObject(
+                By.text(mLauncher.isAvd() ? "ADD AUTOMATICALLY" : "Add automatically")),
+                DEFAULT_UI_TIMEOUT).click();
         Intent result = resultReceiver.blockingGetIntent();
         assertNotNull(result);
         mAppWidgetId = result.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 68bdfe3..a296975 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -18,6 +18,8 @@
 
 import static com.android.launcher3.tapl.LauncherInstrumentation.NavigationModel.ZERO_BUTTON;
 
+import android.graphics.Rect;
+
 import androidx.annotation.NonNull;
 import androidx.test.uiautomator.BySelector;
 import androidx.test.uiautomator.Direction;
@@ -32,7 +34,6 @@
 public class AllApps extends LauncherInstrumentation.VisibleContainer {
     private static final int MAX_SCROLL_ATTEMPTS = 40;
     private static final int MIN_INTERACT_SIZE = 100;
-    private static final int FLING_SPEED = LauncherInstrumentation.needSlowGestures() ? 1000 : 3000;
 
     private final int mHeight;
 
@@ -102,7 +103,7 @@
                             "search_container_all_apps");
 
             int attempts = 0;
-            allAppsContainer.setGestureMargins(0, searchBox.getVisibleBounds().bottom + 1, 0, 5);
+            final Rect margins = new Rect(0, searchBox.getVisibleBounds().bottom + 1, 0, 5);
 
             for (int scroll = getScroll(allAppsContainer);
                     scroll != 0;
@@ -113,7 +114,7 @@
                         "Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
                         ++attempts <= MAX_SCROLL_ATTEMPTS);
 
-                allAppsContainer.scroll(Direction.UP, 1);
+                mLauncher.scroll(allAppsContainer, Direction.UP, 1, margins, 50);
             }
 
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("scrolled up")) {
@@ -133,7 +134,7 @@
             // Try to figure out how much percentage of the container needs to be scrolled in order
             // to reveal the app icon to have the MIN_INTERACT_SIZE
             final float pct = Math.max(((float) (MIN_INTERACT_SIZE - appHeight)) / mHeight, 0.2f);
-            allAppsContainer.scroll(Direction.DOWN, pct);
+            mLauncher.scroll(allAppsContainer, Direction.DOWN, pct, null, 10);
             try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                     "scrolled an icon in all apps to make it visible - and then")) {
                 mLauncher.waitForIdle();
@@ -150,9 +151,8 @@
                      mLauncher.addContextLayer("want to fling forward in all apps")) {
             final UiObject2 allAppsContainer = verifyActiveContainer();
             // Start the gesture in the center to avoid starting at elements near the top.
-            allAppsContainer.setGestureMargins(0, 0, 0, mHeight / 2);
-            allAppsContainer.fling(Direction.DOWN,
-                    (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
+            mLauncher.scroll(
+                    allAppsContainer, Direction.DOWN, 1, new Rect(0, 0, 0, mHeight / 2), 10);
             verifyActiveContainer();
         }
     }
@@ -165,9 +165,8 @@
                      mLauncher.addContextLayer("want to fling backward in all apps")) {
             final UiObject2 allAppsContainer = verifyActiveContainer();
             // Start the gesture in the center, for symmetry with forward.
-            allAppsContainer.setGestureMargins(0, mHeight / 2, 0, 0);
-            allAppsContainer.fling(Direction.UP,
-                    (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
+            mLauncher.scroll(
+                    allAppsContainer, Direction.UP, 1, new Rect(0, mHeight / 2, 0, 0), 10);
             verifyActiveContainer();
         }
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index b3a369a..ace49e9 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -28,7 +28,7 @@
  * Common overview pane for both Launcher and fallback recents
  */
 public class BaseOverview extends LauncherInstrumentation.VisibleContainer {
-    private static final int FLING_SPEED = LauncherInstrumentation.needSlowGestures() ? 500 : 1500;
+    private static final int FLING_SPEED = LauncherInstrumentation.isAvd() ? 500 : 1500;
     private static final int FLINGS_FOR_DISMISS_LIMIT = 40;
 
     BaseOverview(LauncherInstrumentation launcher) {
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index a63d468..57fd4b9 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -28,6 +28,7 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
@@ -47,6 +48,7 @@
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.BySelector;
 import androidx.test.uiautomator.Configurator;
+import androidx.test.uiautomator.Direction;
 import androidx.test.uiautomator.UiDevice;
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
@@ -56,6 +58,7 @@
 
 import org.junit.Assert;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.lang.ref.WeakReference;
 import java.util.Deque;
@@ -195,7 +198,7 @@
         return NavigationModel.THREE_BUTTON;
     }
 
-    public static boolean needSlowGestures() {
+    public static boolean isAvd() {
         return Build.MODEL.contains("Cuttlefish");
     }
 
@@ -212,7 +215,22 @@
         };
     }
 
+    private void dumpViewHierarchy() {
+        final ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        try {
+            mDevice.dumpWindowHierarchy(stream);
+            stream.flush();
+            stream.close();
+            for (String line : stream.toString().split("\\r?\\n")) {
+                Log.e(TAG, line.trim());
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "error dumping XML to logcat", e);
+        }
+    }
+
     private void fail(String message) {
+        dumpViewHierarchy();
         Assert.fail("http://go/tapl : " + getContextDescription() + message);
     }
 
@@ -363,10 +381,10 @@
                         ? NORMAL_STATE_ORDINAL : BACKGROUND_APP_STATE_ORDINAL;
                 final Point displaySize = getRealDisplaySize();
 
-                swipeViaMovePointer(
+                swipeToState(
                         displaySize.x / 2, displaySize.y - 1,
                         displaySize.x / 2, 0,
-                        finalState, ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME);
+                        ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, finalState);
             }
         } else {
             log(action = "clicking home button");
@@ -530,6 +548,14 @@
     }
 
     @NonNull
+    UiObject2 waitForLauncherObjectByClass(String clazz) {
+        final BySelector selector = getLauncherObjectSelectorByClass(clazz);
+        final UiObject2 object = mDevice.wait(Until.findObject(selector), WAIT_TIME_MS);
+        assertNotNull("Can't find a launcher object; selector: " + selector, object);
+        return object;
+    }
+
+    @NonNull
     UiObject2 waitForFallbackLauncherObject(String resName) {
         final BySelector selector = getFallbackLauncherObjectSelector(resName);
         final UiObject2 object = mDevice.wait(Until.findObject(selector), WAIT_TIME_MS);
@@ -541,6 +567,10 @@
         return By.res(getLauncherPackageName(), resName);
     }
 
+    BySelector getLauncherObjectSelectorByClass(String clazz) {
+        return By.pkg(getLauncherPackageName()).clazz(clazz);
+    }
+
     BySelector getFallbackLauncherObjectSelector(String resName) {
         return By.res(getOverviewPackageName(), resName);
     }
@@ -563,18 +593,63 @@
                 () -> mDevice.swipe(startX, startY, endX, endY, steps));
     }
 
-    void swipeViaMovePointer(
-            int startX, int startY, int endX, int endY, int expectedState, int steps) {
-        changeStateViaGesture(startX, startY, endX, endY, expectedState, () -> {
-            final long downTime = SystemClock.uptimeMillis();
-            final Point start = new Point(startX, startY);
-            final Point end = new Point(endX, endY);
-            sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start);
-            final long endTime = movePointer(downTime, downTime, steps * GESTURE_STEP_MS, start,
-                    end);
-            sendPointer(
-                    downTime, endTime, MotionEvent.ACTION_UP, end);
-        });
+    void swipeToState(int startX, int startY, int endX, int endY, int steps, int expectedState) {
+        changeStateViaGesture(startX, startY, endX, endY, expectedState,
+                () -> linearGesture(startX, startY, endX, endY, steps));
+    }
+
+    void scroll(UiObject2 container, Direction direction, float percent, Rect margins, int steps) {
+        final Rect rect = container.getVisibleBounds();
+        if (margins != null) {
+            rect.left += margins.left;
+            rect.top += margins.top;
+            rect.right -= margins.right;
+            rect.bottom -= margins.bottom;
+        }
+
+        final int startX;
+        final int startY;
+        final int endX;
+        final int endY;
+
+        switch (direction) {
+            case UP: {
+                startX = endX = rect.centerX();
+                final int vertCenter = rect.centerY();
+                final float halfGestureHeight = rect.height() * percent / 2.0f;
+                startY = (int) (vertCenter - halfGestureHeight);
+                endY = (int) (vertCenter + halfGestureHeight);
+            }
+            break;
+            case DOWN: {
+                startX = endX = rect.centerX();
+                final int vertCenter = rect.centerY();
+                final float halfGestureHeight = rect.height() * percent / 2.0f;
+                startY = (int) (vertCenter + halfGestureHeight);
+                endY = (int) (vertCenter - halfGestureHeight);
+            }
+            break;
+            default:
+                fail("Unsupported direction");
+                return;
+        }
+
+        executeAndWaitForEvent(
+                () -> linearGesture(startX, startY, endX, endY, steps),
+                event -> TestProtocol.SCROLL_FINISHED_MESSAGE.equals(event.getClassName()),
+                "Didn't receive a scroll end message: " + startX + ", " + startY
+                        + ", " + endX + ", " + endY);
+    }
+
+    // Inject a swipe gesture. Inject exactly 'steps' motion points, incrementing event time by a
+    // fixed interval each time.
+    private void linearGesture(int startX, int startY, int endX, int endY, int steps) {
+        final long downTime = SystemClock.uptimeMillis();
+        final Point start = new Point(startX, startY);
+        final Point end = new Point(endX, endY);
+        sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start);
+        final long endTime = movePointer(downTime, downTime, steps * GESTURE_STEP_MS, start, end);
+        sendPointer(downTime, endTime, MotionEvent.ACTION_UP, end);
     }
 
     private void changeStateViaGesture(int startX, int startY, int endX, int endY,
@@ -688,10 +763,7 @@
     }
 
     static void sleep(int duration) {
-        try {
-            Thread.sleep(duration);
-        } catch (InterruptedException e) {
-        }
+        SystemClock.sleep(duration);
     }
 
     int getEdgeSensitivityWidth() {
diff --git a/tests/tapl/com/android/launcher3/tapl/Overview.java b/tests/tapl/com/android/launcher3/tapl/Overview.java
index e625510..ec99d26 100644
--- a/tests/tapl/com/android/launcher3/tapl/Overview.java
+++ b/tests/tapl/com/android/launcher3/tapl/Overview.java
@@ -49,13 +49,14 @@
                 "want to switch from overview to all apps")) {
             verifyActiveContainer();
 
-            // Swipe from the prediction row to the top.
+            // Swipe from an app icon to the top.
             LauncherInstrumentation.log("Overview.switchToAllApps before swipe");
-            final UiObject2 predictionRow = mLauncher.waitForLauncherObject("prediction_row");
-            mLauncher.swipe(mLauncher.getDevice().getDisplayWidth() / 2,
-                    predictionRow.getVisibleBounds().centerY(),
+            final UiObject2 appIcon = mLauncher.waitForLauncherObjectByClass(
+                    "android.widget.TextView");
+            mLauncher.swipeToState(mLauncher.getDevice().getDisplayWidth() / 2,
+                    appIcon.getVisibleBounds().centerY(),
                     mLauncher.getDevice().getDisplayWidth() / 2,
-                    0, ALL_APPS_STATE_ORDINAL);
+                    0, 50, ALL_APPS_STATE_ORDINAL);
 
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
                     "swiped all way up from overview")) {
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index c0bafa2..11c0794 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -37,7 +37,7 @@
  */
 public final class Workspace extends Home {
     private static final float FLING_SPEED =
-            LauncherInstrumentation.needSlowGestures() ? 1500.0F : 3500.0F;
+            LauncherInstrumentation.isAvd() ? 1500.0F : 3500.0F;
     private static final int DRAG_DURACTION = 2000;
     private final UiObject2 mHotseat;
 
@@ -65,13 +65,14 @@
             LauncherInstrumentation.log(
                     "switchToAllApps: swipeHeight = " + swipeHeight + ", slop = "
                             + mLauncher.getTouchSlop());
-            mLauncher.swipe(
+
+            mLauncher.swipeToState(
                     start.x,
                     start.y,
                     start.x,
                     start.y - swipeHeight - mLauncher.getTouchSlop(),
-                    ALL_APPS_STATE_ORDINAL
-            );
+                    60,
+                    ALL_APPS_STATE_ORDINAL);
 
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
                     "swiped to all apps")) {