Snap for 6559824 from d5fed54573f6cd790e83a482836da9f94f877a3b to mainline-release

Change-Id: I374471441793df737cdd7062709fe972e96d50b2
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index 5f5fab0..d552daf 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -51,6 +51,7 @@
     WidgetsContainer widgets_container = 5;
     PredictionContainer prediction_container = 6;
     SearchResultContainer search_result_container = 7;
+    ShortcutsContainer shortcuts_container = 8;
   }
 }
 
@@ -69,6 +70,11 @@
 message SearchResultContainer {
 }
 
+// Container for package specific shortcuts to deep links and notifications.
+// Typically shown as popup window by longpressing on an icon.
+message ShortcutsContainer {
+}
+
 enum Origin {
   UNKNOWN = 0;
   DEFAULT_LAYOUT = 1;       // icon automatically placed in workspace, folder, hotseat
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
index e4d0adf..f1ce72e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
@@ -15,17 +15,9 @@
  */
 package com.android.launcher3.hybridhotseat;
 
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
 import android.content.Intent;
-import android.content.res.Configuration;
-import android.os.Build;
 import android.view.View;
 
-import androidx.core.app.NotificationCompat;
-
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.InvariantDeviceProfile;
@@ -37,11 +29,8 @@
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.launcher3.util.ActivityTracker;
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ArrowTipView;
 import com.android.launcher3.views.Snackbar;
 
@@ -54,18 +43,16 @@
  * Controller class for managing user onboaridng flow for hybrid hotseat
  */
 public class HotseatEduController {
+
     public static final String KEY_HOTSEAT_EDU_SEEN = "hotseat_edu_seen";
-
-    private static final String NOTIFICATION_CHANNEL_ID = "launcher_onboarding";
-    private static final int ONBOARDING_NOTIFICATION_ID = 7641;
-
+    public static final String HOTSEAT_EDU_ACTION =
+            "com.android.launcher3.action.SHOW_HYBRID_HOTSEAT_EDU";
     private static final String SETTINGS_ACTION =
             "android.settings.ACTION_CONTENT_SUGGESTIONS_SETTINGS";
 
     private final Launcher mLauncher;
     private final Hotseat mHotseat;
-    private final NotificationManager mNotificationManager;
-    private final Notification mNotification;
+    private final HotseatRestoreHelper mRestoreHelper;
     private List<WorkspaceItemInfo> mPredictedApps;
     private HotseatEduDialog mActiveDialog;
 
@@ -73,24 +60,25 @@
     private IntArray mNewScreens = null;
     private Runnable mOnOnboardingComplete;
 
-    HotseatEduController(Launcher launcher, Runnable runnable) {
+    HotseatEduController(Launcher launcher, HotseatRestoreHelper restoreHelper, Runnable runnable) {
         mLauncher = launcher;
         mHotseat = launcher.getHotseat();
+        mRestoreHelper = restoreHelper;
         mOnOnboardingComplete = runnable;
-        mNotificationManager = mLauncher.getSystemService(NotificationManager.class);
-        createNotificationChannel();
-        mNotification = createNotification();
     }
 
     /**
      * Checks what type of migration should be used and migrates hotseat
      */
     void migrate() {
+        mRestoreHelper.createBackup();
         if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) {
             migrateToFolder();
         } else {
             migrateHotseatWhole();
         }
+        Snackbar.show(mLauncher, R.string.hotsaet_tip_prediction_enabled, R.string.hotseat_turn_off,
+                null, () -> mLauncher.startActivity(new Intent(SETTINGS_ACTION)));
     }
 
     /**
@@ -101,7 +89,6 @@
      */
     private int migrateToFolder() {
         ArrayDeque<FolderInfo> folders = new ArrayDeque<>();
-
         ArrayList<WorkspaceItemInfo> putIntoFolder = new ArrayList<>();
 
         //separate folders and items that can get in folders
@@ -216,11 +203,6 @@
         return pageId;
     }
 
-
-    void removeNotification() {
-        mNotificationManager.cancel(ONBOARDING_NOTIFICATION_ID);
-    }
-
     void moveHotseatItems() {
         mHotseat.removeAllViewsInLayout();
         if (!mNewItems.isEmpty()) {
@@ -258,45 +240,9 @@
 
     void setPredictedApps(List<WorkspaceItemInfo> predictedApps) {
         mPredictedApps = predictedApps;
-        if (!mPredictedApps.isEmpty()
-                && mLauncher.getOrientation() == Configuration.ORIENTATION_PORTRAIT) {
-            mNotificationManager.notify(ONBOARDING_NOTIFICATION_ID, mNotification);
-        }
-        else {
-            removeNotification();
-        }
-    }
-
-    private void createNotificationChannel() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
-        CharSequence name = mLauncher.getString(R.string.hotseat_edu_prompt_title);
-        int importance = NotificationManager.IMPORTANCE_LOW;
-        NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, name,
-                importance);
-        mNotificationManager.createNotificationChannel(channel);
-    }
-
-    private Notification createNotification() {
-        Intent intent = new Intent(mLauncher.getApplicationContext(), mLauncher.getClass());
-        intent = new NotificationHandler().addToIntent(intent);
-
-        CharSequence name = mLauncher.getString(R.string.hotseat_edu_prompt_title);
-        String description = mLauncher.getString(R.string.hotseat_edu_prompt_content);
-        NotificationCompat.Builder builder = new NotificationCompat.Builder(mLauncher,
-                NOTIFICATION_CHANNEL_ID)
-                .setContentTitle(name)
-                .setOngoing(true)
-                .setColor(Themes.getColorAccent(mLauncher))
-                .setContentIntent(PendingIntent.getActivity(mLauncher, 0, intent,
-                        PendingIntent.FLAG_CANCEL_CURRENT))
-                .setSmallIcon(R.drawable.hotseat_edu_notification_icon)
-                .setContentText(description);
-        return builder.build();
-
     }
 
     void destroy() {
-        removeNotification();
         if (mActiveDialog != null) {
             mActiveDialog.setHotseatEduController(null);
         }
@@ -334,14 +280,5 @@
         mActiveDialog.setHotseatEduController(this);
         mActiveDialog.show(mPredictedApps);
     }
-
-    static class NotificationHandler implements
-            ActivityTracker.SchedulerCallback<QuickstepLauncher> {
-        @Override
-        public boolean init(QuickstepLauncher activity, boolean alreadyOnHome) {
-            activity.getHotseatPredictionController().showEdu();
-            return true;
-        }
-    }
 }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
index 66c60bc..fdd3562 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -16,7 +16,8 @@
 package com.android.launcher3.hybridhotseat;
 
 import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.HYBRID_HOTSEAT_CANCELED;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType
+        .HYBRID_HOTSEAT_CANCELED;
 
 import android.animation.PropertyValuesHolder;
 import android.content.Context;
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index e2acf93..725f516 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -41,7 +41,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.AllAppsStore;
@@ -93,6 +92,8 @@
     private Launcher mLauncher;
     private final Hotseat mHotseat;
 
+    private final HotseatRestoreHelper mRestoreHelper;
+
     private List<ComponentKeyMapper> mComponentKeyMappers = new ArrayList<>();
 
     private DynamicItemCache mDynamicItemCache;
@@ -130,6 +131,7 @@
         mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons;
         launcher.getDeviceProfile().inv.addOnChangeListener(this);
         mHotseat.addOnAttachStateChangeListener(this);
+        mRestoreHelper = new HotseatRestoreHelper(mLauncher);
         if (mHotseat.isAttachedToWindow()) {
             onViewAttachedToWindow(mHotseat);
         }
@@ -148,8 +150,7 @@
      */
     public void showEdu() {
         if (mHotseatEduController == null) return;
-        mLauncher.getStateManager().goToState(LauncherState.NORMAL, true,
-                () -> mHotseatEduController.showEdu());
+        mHotseatEduController.showEdu();
     }
 
     @Override
@@ -299,7 +300,8 @@
         });
         setPauseUIUpdate(false);
         if (!isEduSeen()) {
-            mHotseatEduController = new HotseatEduController(mLauncher, this::createPredictor);
+            mHotseatEduController = new HotseatEduController(mLauncher, mRestoreHelper,
+                    this::createPredictor);
         }
     }
 
@@ -322,9 +324,11 @@
         updateDependencies();
         bindItems(items, false, null);
     }
-
     private void setPredictedApps(List<AppTarget> appTargets) {
         mComponentKeyMappers.clear();
+        if (appTargets.isEmpty() && mRestoreHelper.shouldRestoreToBackup()) {
+            mRestoreHelper.restoreBackup();
+        }
         StringBuilder predictionLog = new StringBuilder("predictedApps: [\n");
         ArrayList<ComponentKey> componentKeys = new ArrayList<>();
         for (AppTarget appTarget : appTargets) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java
new file mode 100644
index 0000000..c95ff7a
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2020 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.hybridhotseat;
+
+import static com.android.launcher3.LauncherSettings.Favorites.HYBRID_HOTSEAT_BACKUP_TABLE;
+import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.model.GridBackupTable;
+import com.android.launcher3.provider.LauncherDbUtils;
+
+/**
+ * A helper class to manage migration revert restoration for hybrid hotseat
+ */
+public class HotseatRestoreHelper {
+    private final Launcher mLauncher;
+    private boolean mBackupExists;
+
+    HotseatRestoreHelper(Launcher context) {
+        mLauncher = context;
+        setupBackupTable();
+    }
+
+    /**
+     * Creates a snapshot backup of Favorite table for future restoration use.
+     */
+    public synchronized void createBackup() {
+        try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction)
+                LauncherSettings.Settings.call(
+                        mLauncher.getContentResolver(),
+                        LauncherSettings.Settings.METHOD_NEW_TRANSACTION)
+                        .getBinder(LauncherSettings.Settings.EXTRA_VALUE)) {
+            InvariantDeviceProfile idp = mLauncher.getDeviceProfile().inv;
+            GridBackupTable backupTable = new GridBackupTable(mLauncher,
+                    transaction.getDb(), idp.numHotseatIcons, idp.numColumns,
+                    idp.numRows);
+            backupTable.createCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE);
+            transaction.commit();
+            LauncherSettings.Settings.call(mLauncher.getContentResolver(),
+                    LauncherSettings.Settings.METHOD_REFRESH_HOTSEAT_RESTORE_TABLE);
+            mBackupExists = true;
+        }
+    }
+
+    /**
+     * Finds and restores a previously saved snapshow of Favorites table
+     */
+    public void restoreBackup() {
+        try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction)
+                LauncherSettings.Settings.call(
+                        mLauncher.getContentResolver(),
+                        LauncherSettings.Settings.METHOD_NEW_TRANSACTION)
+                        .getBinder(LauncherSettings.Settings.EXTRA_VALUE)) {
+            if (!tableExists(transaction.getDb(), HYBRID_HOTSEAT_BACKUP_TABLE)) {
+                mBackupExists = false;
+                return;
+            }
+            InvariantDeviceProfile idp = mLauncher.getDeviceProfile().inv;
+            GridBackupTable backupTable = new GridBackupTable(mLauncher,
+                    transaction.getDb(), idp.numHotseatIcons, idp.numColumns,
+                    idp.numRows);
+            backupTable.restoreFromCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE, true);
+            transaction.commit();
+            mBackupExists = false;
+            mLauncher.getModel().forceReload();
+        }
+    }
+
+    /**
+     * Returns if prediction controller should attempt restoring a backup
+     */
+    public synchronized boolean shouldRestoreToBackup() {
+        return mBackupExists;
+    }
+
+    private synchronized void setupBackupTable() {
+        try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction)
+                LauncherSettings.Settings.call(
+                        mLauncher.getContentResolver(),
+                        LauncherSettings.Settings.METHOD_NEW_TRANSACTION)
+                        .getBinder(LauncherSettings.Settings.EXTRA_VALUE)) {
+            mBackupExists = tableExists(transaction.getDb(), HYBRID_HOTSEAT_BACKUP_TABLE);
+        }
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 1dc5be6..39e0f88 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -44,6 +44,7 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.Folder;
+import com.android.launcher3.hybridhotseat.HotseatEduController;
 import com.android.launcher3.hybridhotseat.HotseatPredictionController;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
@@ -99,6 +100,20 @@
     }
 
     @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        if (HotseatEduController.HOTSEAT_EDU_ACTION.equals(intent.getAction())
+                && mHotseatPredictionController != null) {
+            boolean alreadyOnHome = hasWindowFocus() && ((intent.getFlags()
+                    & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
+                    != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
+            getStateManager().goToState(NORMAL, alreadyOnHome, () -> {
+                mHotseatPredictionController.showEdu();
+            });
+        }
+    }
+
+    @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         onStateOrResumeChanging(false /* inTransition */);
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 94c7771..79dc3e2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -29,6 +29,7 @@
 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
 import static com.android.launcher3.anim.Interpolators.INSTANT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
@@ -36,6 +37,7 @@
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_TRANSLATE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
@@ -188,7 +190,7 @@
             }
         } else if (toState == NORMAL && fromState == OVERVIEW_PEEK) {
             // Keep fully visible until the very end (when overview is offscreen) to make invisible.
-            config.setInterpolator(ANIM_OVERVIEW_FADE, t -> t < 1 ? 0 : 1);
+            config.setInterpolator(ANIM_OVERVIEW_FADE, FINAL_FRAME);
         } else if (toState == OVERVIEW_PEEK && fromState == NORMAL) {
             config.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
             config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
@@ -209,6 +211,7 @@
             }
             config.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
             config.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
+            config.setInterpolator(ANIM_DEPTH, OVERSHOOT_1_2);
             Interpolator translationInterpolator = ENABLE_OVERVIEW_ACTIONS.get()
                     && removeShelfFromOverview(mActivity)
                     ? OVERSHOOT_1_2
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index bf0690c..39bbfb9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -116,8 +116,7 @@
             if (TestProtocol.sDebugTracing) {
                 Log.d(TestProtocol.PAUSE_NOT_DETECTED,
                         "NavBarToHomeTouchController.canInterceptTouch true 2 "
-                                + AbstractFloatingView.getTopOpenView(mLauncher).getClass()
-                                .getSimpleName());
+                                + AbstractFloatingView.getTopOpenView(mLauncher), new Exception());
             }
             return true;
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index c18a0fd..e5782e7 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -33,11 +33,11 @@
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.quickstep.util.RemoteAnimationProvider;
+import com.android.quickstep.util.SurfaceTransactionApplier;
 import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.util.TransformParams;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
 
 /**
  * Provider for the atomic (for 3-button mode) remote window animation from the app to the overview.
@@ -132,8 +132,7 @@
 
         TransformParams params = new TransformParams()
                 .setTargetSet(targets)
-                .setSyncTransactionApplier(
-                        new SyncRtSurfaceTransactionApplierCompat(mActivity.getRootView()));
+                .setSyncTransactionApplier(new SurfaceTransactionApplier(mActivity.getRootView()));
 
         AnimatedFloat recentsAlpha = new AnimatedFloat(() -> { });
         params.setBaseBuilderProxy((builder, app, p)
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
index 5b396dd..6cbe794 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -15,57 +15,39 @@
  */
 package com.android.quickstep;
 
-import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
-import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
 
-import android.animation.Animator;
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.Matrix;
-import android.graphics.Matrix.ScaleToFit;
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.os.Build;
 import android.util.Log;
 import android.view.MotionEvent;
-import android.view.animation.Interpolator;
 
 import androidx.annotation.CallSuper;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.VibratorWrapper;
-import com.android.launcher3.views.FloatingIconView;
+import com.android.launcher3.util.WindowBounds;
 import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.RectFSpringAnim;
-import com.android.quickstep.util.TaskViewSimulator;
+import com.android.quickstep.util.SurfaceTransactionApplier;
 import com.android.quickstep.util.TransformParams;
-import com.android.quickstep.util.TransformParams.BuilderProxy;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder;
 
 import java.util.ArrayList;
 import java.util.function.Consumer;
@@ -75,40 +57,13 @@
  */
 @TargetApi(Build.VERSION_CODES.Q)
 public abstract class BaseSwipeUpHandler<T extends StatefulActivity<?>, Q extends RecentsView>
-        implements RecentsAnimationListener {
+        extends SwipeUpAnimationLogic implements RecentsAnimationListener {
 
     private static final String TAG = "BaseSwipeUpHandler";
-    protected static final Rect TEMP_RECT = new Rect();
 
-    public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.7f;
-    private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL;
-
-    // The distance needed to drag to reach the task size in recents.
-    protected int mTransitionDragLength;
-    // How much further we can drag past recents, as a factor of mTransitionDragLength.
-    protected float mDragLengthFactor = 1;
-    // Start resisting when swiping past this factor of mTransitionDragLength.
-    private float mDragLengthFactorStartPullback = 1f;
-    // This is how far down we can scale down, where 0f is full screen and 1f is recents.
-    private float mDragLengthFactorMaxPullback = 1f;
-
-    protected final Context mContext;
-    protected final RecentsAnimationDeviceState mDeviceState;
-    protected final GestureState mGestureState;
     protected final BaseActivityInterface<?, T> mActivityInterface;
     protected final InputConsumerController mInputConsumer;
 
-    protected final TaskViewSimulator mTaskViewSimulator;
-    private AnimatorPlaybackController mWindowTransitionController;
-
-    protected final TransformParams mTransformParams = new TransformParams();
-
-    // Shift in the range of [0, 1].
-    // 0 => preview snapShot is completely visible, and hotseat is completely translated down
-    // 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
-    // visible.
-    protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
-
     protected final ActivityInitListener mActivityInitListener;
 
     protected RecentsAnimationController mRecentsAnimationController;
@@ -119,7 +74,6 @@
 
     protected T mActivity;
     protected Q mRecentsView;
-    protected DeviceProfile mDp;
 
     protected Runnable mGestureEndCallback;
 
@@ -131,13 +85,10 @@
 
     protected BaseSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
             GestureState gestureState, InputConsumerController inputConsumer) {
-        mContext = context;
-        mDeviceState = deviceState;
-        mGestureState = gestureState;
+        super(context, deviceState, gestureState, new TransformParams());
         mActivityInterface = gestureState.getActivityInterface();
         mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
         mInputConsumer = inputConsumer;
-        mTaskViewSimulator = new TaskViewSimulator(context, gestureState.getActivityInterface());
     }
 
     /**
@@ -157,28 +108,6 @@
         return mRecentsView != null ? mRecentsView.getEventDispatcher(navbarRotation) : null;
     }
 
-    @UiThread
-    public void updateDisplacement(float displacement) {
-        // We are moving in the negative x/y direction
-        displacement = -displacement;
-        float shift;
-        if (displacement > mTransitionDragLength * mDragLengthFactor && mTransitionDragLength > 0) {
-            shift = mDragLengthFactor;
-        } else {
-            float translation = Math.max(displacement, 0);
-            shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength;
-            if (shift > mDragLengthFactorStartPullback) {
-                float pullbackProgress = Utilities.getProgress(shift,
-                        mDragLengthFactorStartPullback, mDragLengthFactor);
-                pullbackProgress = PULLBACK_INTERPOLATOR.getInterpolation(pullbackProgress);
-                shift = mDragLengthFactorStartPullback + pullbackProgress
-                        * (mDragLengthFactorMaxPullback - mDragLengthFactorStartPullback);
-            }
-        }
-
-        mCurrentShift.updateValue(shift);
-    }
-
     public void setGestureEndCallback(Runnable gestureEndCallback) {
         mGestureEndCallback = gestureEndCallback;
     }
@@ -186,10 +115,10 @@
     public abstract Intent getLaunchIntent();
 
     protected void linkRecentsViewScroll() {
-        SyncRtSurfaceTransactionApplierCompat.create(mRecentsView, applier -> {
+        SurfaceTransactionApplier.create(mRecentsView, applier -> {
             mTransformParams.setSyncTransactionApplier(applier);
             runOnRecentsAnimationStart(() ->
-                    mRecentsAnimationTargets.addDependentTransactionApplier(applier));
+                    mRecentsAnimationTargets.addReleaseCheck(applier));
         });
 
         mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
@@ -275,6 +204,7 @@
             RecentsAnimationTargets targets) {
         mRecentsAnimationController = recentsAnimationController;
         mRecentsAnimationTargets = targets;
+        mTransformParams.setTargetSet(mRecentsAnimationTargets);
         DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext);
         RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(
                 mGestureState.getRunningTaskId());
@@ -282,7 +212,8 @@
         if (targets.minimizedHomeBounds != null && runningTaskTarget != null) {
             Rect overviewStackBounds = mActivityInterface
                     .getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget);
-            dp = dp.getMultiWindowProfile(mContext, overviewStackBounds);
+            dp = dp.getMultiWindowProfile(mContext,
+                    new WindowBounds(overviewStackBounds, targets.homeContentInsets));
         } else {
             // If we are not in multi-window mode, home insets should be same as system insets.
             dp = dp.copy(mContext);
@@ -355,35 +286,6 @@
         return mGestureState.getLastStartedTaskId() != -1;
     }
 
-    protected void initTransitionEndpoints(DeviceProfile dp) {
-        mDp = dp;
-
-        mTaskViewSimulator.setDp(dp);
-        mTaskViewSimulator.setLayoutRotation(
-                mDeviceState.getCurrentActiveRotation(),
-                mDeviceState.getDisplayRotation());
-        mTransitionDragLength = mActivityInterface.getSwipeUpDestinationAndLength(
-                dp, mContext, TEMP_RECT,
-                mTaskViewSimulator.getOrientationState().getOrientationHandler());
-
-        if (mDeviceState.isFullyGesturalNavMode()) {
-            // We can drag all the way to the top of the screen.
-            mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
-
-            float startScale = mTaskViewSimulator.getFullScreenScale();
-            // Start pulling back when RecentsView scale is 0.75f, and let it go down to 0.5f.
-            mDragLengthFactorStartPullback = (0.75f - startScale) / (1 - startScale);
-            mDragLengthFactorMaxPullback = (0.5f - startScale) / (1 - startScale);
-        } else {
-            mDragLengthFactor = 1;
-            mDragLengthFactorStartPullback = mDragLengthFactorMaxPullback = 1;
-        }
-
-        PendingAnimation pa = new PendingAnimation(mTransitionDragLength * 2);
-        mTaskViewSimulator.addAppToOverviewAnim(pa, t -> t * mDragLengthFactor);
-        mWindowTransitionController = pa.createPlaybackController();
-    }
-
     /**
      * Return true if the window should be translated horizontally if the recents view scrolls
      */
@@ -455,8 +357,8 @@
         if (mWindowTransitionController != null) {
             float progress = mCurrentShift.value / mDragLengthFactor;
             mWindowTransitionController.setPlayFraction(progress);
-            mTransformParams.setTargetSet(mRecentsAnimationTargets);
-
+        }
+        if (mRecentsAnimationTargets != null) {
             if (mRecentsViewScrollLinked) {
                 mTaskViewSimulator.setScroll(mRecentsView.getScrollOffset());
             }
@@ -464,49 +366,14 @@
         }
     }
 
-    protected PagedOrientationHandler getOrientationHandler() {
-        return mTaskViewSimulator.getOrientationState().getOrientationHandler();
-    }
-
-    /**
-     * Creates an animation that transforms the current app window into the home app.
-     * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
-     * @param homeAnimationFactory The home animation factory.
-     */
+    @Override
     protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
             HomeAnimationFactory homeAnimationFactory) {
-        final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
-        final FloatingIconView fiv = homeAnimationFactory.mIconView;
-        final boolean isFloatingIconView = fiv != null;
-
-        mWindowTransitionController.setPlayFraction(startProgress / mDragLengthFactor);
-        mTaskViewSimulator.apply(mTransformParams
-                .setProgress(startProgress)
-                .setTargetSet(mRecentsAnimationTargets));
-        RectF cropRectF = new RectF(mTaskViewSimulator.getCurrentCropRect());
-
-        // Matrix to map a rect in Launcher space to window space
-        Matrix homeToWindowPositionMap = new Matrix();
-        mTaskViewSimulator.applyWindowToHomeRotation(homeToWindowPositionMap);
-
-        final RectF startRect = new RectF(cropRectF);
-        mTaskViewSimulator.getCurrentMatrix().mapRect(startRect);
-        // Move the startRect to Launcher space as floatingIconView runs in Launcher
-        Matrix windowToHomePositionMap = new Matrix();
-        homeToWindowPositionMap.invert(windowToHomePositionMap);
-        windowToHomePositionMap.mapRect(startRect);
-
-        RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mContext);
-        if (isFloatingIconView) {
-            anim.addAnimatorListener(fiv);
-            fiv.setOnTargetChangeListener(anim::onTargetPositionChanged);
-            fiv.setFastFinishRunnable(anim::end);
+        RectFSpringAnim anim =
+                super.createWindowAnimationToHome(startProgress, homeAnimationFactory);
+        if (mRecentsAnimationTargets != null) {
+            mRecentsAnimationTargets.addReleaseCheck(anim);
         }
-
-        SpringAnimationRunner runner = new SpringAnimationRunner(
-                homeAnimationFactory, cropRectF, homeToWindowPositionMap);
-        anim.addOnUpdateListener(runner);
-        anim.addAnimatorListener(runner);
         return anim;
     }
 
@@ -515,166 +382,4 @@
         BaseSwipeUpHandler newHandler(
                 GestureState gestureState, long touchTimeMs, boolean continuingLastGesture);
     }
-
-    protected interface RunningWindowAnim {
-        void end();
-
-        void cancel();
-
-        static RunningWindowAnim wrap(Animator animator) {
-            return new RunningWindowAnim() {
-                @Override
-                public void end() {
-                    animator.end();
-                }
-
-                @Override
-                public void cancel() {
-                    animator.cancel();
-                }
-            };
-        }
-
-        static RunningWindowAnim wrap(RectFSpringAnim rectFSpringAnim) {
-            return new RunningWindowAnim() {
-                @Override
-                public void end() {
-                    rectFSpringAnim.end();
-                }
-
-                @Override
-                public void cancel() {
-                    rectFSpringAnim.cancel();
-                }
-            };
-        }
-    }
-
-    /**
-     * @param progress The progress of the animation to the home screen.
-     * @return The current alpha to set on the animating app window.
-     */
-    protected float getWindowAlpha(float progress) {
-        // Alpha interpolates between [1, 0] between progress values [start, end]
-        final float start = 0f;
-        final float end = 0.85f;
-
-        if (progress <= start) {
-            return 1f;
-        }
-        if (progress >= end) {
-            return 0f;
-        }
-        return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5);
-    }
-
-    protected abstract class HomeAnimationFactory {
-
-        private FloatingIconView mIconView;
-
-        public HomeAnimationFactory(@Nullable FloatingIconView iconView) {
-            mIconView = iconView;
-        }
-
-        public @NonNull RectF getWindowTargetRect() {
-            PagedOrientationHandler orientationHandler = getOrientationHandler();
-            DeviceProfile dp = mDp;
-            final int halfIconSize = dp.iconSizePx / 2;
-            float primaryDimension = orientationHandler
-                    .getPrimaryValue(dp.availableWidthPx, dp.availableHeightPx);
-            float secondaryDimension = orientationHandler
-                    .getSecondaryValue(dp.availableWidthPx, dp.availableHeightPx);
-            final float targetX =  primaryDimension / 2f;
-            final float targetY = secondaryDimension - dp.hotseatBarSizePx;
-            // Fallback to animate to center of screen.
-            return new RectF(targetX - halfIconSize, targetY - halfIconSize,
-                    targetX + halfIconSize, targetY + halfIconSize);
-        }
-
-        public abstract @NonNull AnimatorPlaybackController createActivityAnimationToHome();
-
-        public void playAtomicAnimation(float velocity) {
-            // No-op
-        }
-    }
-
-    private class SpringAnimationRunner extends AnimationSuccessListener
-            implements RectFSpringAnim.OnUpdateListener, BuilderProxy {
-
-        final Rect mCropRect = new Rect();
-        final Matrix mMatrix = new Matrix();
-
-        final RectF mWindowCurrentRect = new RectF();
-        final Matrix mHomeToWindowPositionMap;
-
-        final FloatingIconView mFIV;
-        final AnimatorPlaybackController mHomeAnim;
-        final RectF mCropRectF;
-
-        final float mStartRadius;
-        final float mEndRadius;
-        final float mWindowAlphaThreshold;
-
-        SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF,
-                Matrix homeToWindowPositionMap) {
-            mHomeAnim = factory.createActivityAnimationToHome();
-            mCropRectF = cropRectF;
-            mHomeToWindowPositionMap = homeToWindowPositionMap;
-
-            cropRectF.roundOut(mCropRect);
-            mFIV = factory.mIconView;
-
-            // End on a "round-enough" radius so that the shape reveal doesn't have to do too much
-            // rounding at the end of the animation.
-            mStartRadius = mTaskViewSimulator.getCurrentCornerRadius();
-            mEndRadius = cropRectF.width() / 2f;
-
-            // We want the window alpha to be 0 once this threshold is met, so that the
-            // FolderIconView can be seen morphing into the icon shape.
-            mWindowAlphaThreshold = mFIV != null ? 1f - SHAPE_PROGRESS_DURATION : 1f;
-        }
-
-        @Override
-        public void onUpdate(RectF currentRect, float progress) {
-            mHomeAnim.setPlayFraction(progress);
-            mHomeToWindowPositionMap.mapRect(mWindowCurrentRect, currentRect);
-
-            mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL);
-            float cornerRadius = Utilities.mapRange(progress, mStartRadius, mEndRadius);
-            mTransformParams
-                    .setTargetAlpha(getWindowAlpha(progress))
-                    .setCornerRadius(cornerRadius);
-
-            mTransformParams.applySurfaceParams(mTransformParams.createSurfaceParams(this));
-            if (mFIV != null) {
-                mFIV.update(currentRect, 1f, progress,
-                        mWindowAlphaThreshold, mMatrix.mapRadius(cornerRadius), false);
-            }
-        }
-
-        @Override
-        public void onBuildTargetParams(
-                Builder builder, RemoteAnimationTargetCompat app, TransformParams params) {
-            builder.withMatrix(mMatrix)
-                    .withWindowCrop(mCropRect)
-                    .withCornerRadius(params.getCornerRadius());
-        }
-
-        @Override
-        public void onCancel() {
-            if (mFIV != null) {
-                mFIV.fastFinish();
-            }
-        }
-
-        @Override
-        public void onAnimationStart(Animator animation) {
-            mHomeAnim.dispatchOnStart();
-        }
-
-        @Override
-        public void onAnimationSuccess(Animator animator) {
-            mHomeAnim.getAnimationPlayer().end();
-        }
-    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
index 46799ff..92e10b1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
@@ -29,6 +29,7 @@
 import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
 import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
 import static com.android.quickstep.GestureState.STATE_END_TARGET_ANIMATION_FINISHED;
+import static com.android.quickstep.GestureState.STATE_END_TARGET_SET;
 import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
 import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
@@ -253,6 +254,10 @@
         mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
                 this::notifyTransitionCancelled);
 
+        mGestureState.runOnceAtState(STATE_END_TARGET_SET,
+                () -> mDeviceState.onEndTargetCalculated(mGestureState.getEndTarget(),
+                        mActivityInterface));
+
         if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             mStateCallback.addChangeListener(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
                             | STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT,
@@ -1035,7 +1040,7 @@
                 }
                 // Make sure recents is in its final state
                 maybeUpdateRecentsAttachedState(false);
-                mActivityInterface.onSwipeUpToHomeComplete();
+                mActivityInterface.onSwipeUpToHomeComplete(mDeviceState);
             }
         });
         return anim;
@@ -1232,7 +1237,6 @@
         }
         ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
         doLogGesture(HOME);
-        mDeviceState.enableMultipleRegions(false);
     }
 
     protected abstract void finishRecentsControllerToHome(Runnable callback);
@@ -1248,7 +1252,6 @@
 
         SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(false, TAG);
         doLogGesture(RECENTS);
-        mDeviceState.onSwipeUpToOverview(mActivityInterface);
         reset();
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
index c9ff884..70be3ab 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
@@ -69,7 +69,7 @@
 
     /** 4 */
     @Override
-    public void onSwipeUpToHomeComplete() {
+    public void onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState) {
         onSwipeUpToRecentsComplete();
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java
index fd17551..e49c466 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java
@@ -33,6 +33,7 @@
 
 import com.android.launcher3.BuildConfig;
 import com.android.quickstep.util.ImageActionUtils;
+import com.android.systemui.shared.recents.model.Task;
 
 import java.util.function.Supplier;
 
@@ -85,11 +86,11 @@
      * @param screenshotBounds the location of where the bitmap was laid out on the screen in
      *                         screen coordinates.
      * @param visibleInsets    that are used to draw the screenshot within the bounds.
-     * @param taskId           of the task that the screenshot was taken of.
+     * @param task             of the task that the screenshot was taken of.
      */
     public void saveScreenshot(Bitmap screenshot, Rect screenshotBounds,
-            Insets visibleInsets, int taskId) {
+            Insets visibleInsets, Task.TaskKey task) {
         ImageActionUtils.saveScreenshot(mSystemUiProxy, screenshot, screenshotBounds, visibleInsets,
-                taskId);
+                task);
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
index 13b84e0..4801d71 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
@@ -96,7 +96,7 @@
     }
 
     @Override
-    public void onSwipeUpToHomeComplete() {
+    public void onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState) {
         Launcher launcher = getCreatedActivity();
         if (launcher == null) {
             return;
@@ -105,6 +105,7 @@
         // recents, we assume the first task is invisible, making translation off by one task.
         launcher.getStateManager().reapplyState();
         launcher.getRootView().setForceHideBackArrow(false);
+        notifyRecentsOfOrientation(deviceState);
     }
 
     @Override
@@ -235,17 +236,20 @@
                         // Are we going from Recents to Workspace?
                         if (toState == LauncherState.NORMAL) {
                             exitRunnable.run();
-
-                            // reset layout on swipe to home
-                            RecentsView recentsView = getCreatedActivity().getOverviewPanel();
-                            recentsView.setLayoutRotation(deviceState.getCurrentActiveRotation(),
-                                    deviceState.getDisplayRotation());
+                            notifyRecentsOfOrientation(deviceState);
                             stateManager.removeStateListener(this);
                         }
                     }
                 });
     }
 
+    private void notifyRecentsOfOrientation(RecentsAnimationDeviceState deviceState) {
+        // reset layout on swipe to home
+        RecentsView recentsView = getCreatedActivity().getOverviewPanel();
+        recentsView.setLayoutRotation(deviceState.getCurrentActiveRotation(),
+                deviceState.getDisplayRotation());
+    }
+
     @Override
     public Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target) {
         return homeBounds;
@@ -306,8 +310,8 @@
     @Override
     protected float getExtraSpace(Context context, DeviceProfile dp,
             PagedOrientationHandler orientationHandler) {
-        if (dp.isVerticalBarLayout() ||
-                hideShelfInTwoButtonLandscape(context, orientationHandler)) {
+        if ((dp.isVerticalBarLayout() && !showOverviewActions(context))
+                || hideShelfInTwoButtonLandscape(context, orientationHandler)) {
             return 0;
         } else {
             Resources res = context.getResources();
@@ -315,12 +319,14 @@
                 //TODO: this needs to account for the swipe gesture height and accessibility
                 // UI when shown.
                 float actionsBottomMargin = 0;
-                if (getMode(context) == Mode.THREE_BUTTONS) {
-                    actionsBottomMargin = res.getDimensionPixelSize(
+                if (!dp.isVerticalBarLayout()) {
+                    if (getMode(context) == Mode.THREE_BUTTONS) {
+                        actionsBottomMargin = res.getDimensionPixelSize(
                             R.dimen.overview_actions_bottom_margin_three_button);
-                } else {
-                    actionsBottomMargin = res.getDimensionPixelSize(
+                    } else {
+                        actionsBottomMargin = res.getDimensionPixelSize(
                             R.dimen.overview_actions_bottom_margin_gesture);
+                    }
                 }
                 float actionsHeight = actionsBottomMargin
                         + res.getDimensionPixelSize(R.dimen.overview_actions_height);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 0d49b2b..a28dabc 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -79,12 +79,13 @@
 
     @Override
     protected Activity getCurrentActivity() {
-        OverviewComponentObserver observer = new OverviewComponentObserver(mContext,
-                new RecentsAnimationDeviceState(mContext));
+        RecentsAnimationDeviceState rads = new RecentsAnimationDeviceState(mContext);
+        OverviewComponentObserver observer = new OverviewComponentObserver(mContext, rads);
         try {
             return observer.getActivityInterface().getCreatedActivity();
         } finally {
             observer.onDestroy();
+            rads.destroy();
         }
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java
new file mode 100644
index 0000000..b17730b
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2020 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;
+
+import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
+
+import android.animation.Animator;
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.Matrix.ScaleToFit;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.animation.Interpolator;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.views.FloatingIconView;
+import com.android.quickstep.util.RectFSpringAnim;
+import com.android.quickstep.util.TaskViewSimulator;
+import com.android.quickstep.util.TransformParams;
+import com.android.quickstep.util.TransformParams.BuilderProxy;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder;
+
+public abstract class SwipeUpAnimationLogic {
+
+    protected static final Rect TEMP_RECT = new Rect();
+    private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL;
+
+    protected DeviceProfile mDp;
+
+    protected final Context mContext;
+    protected final RecentsAnimationDeviceState mDeviceState;
+    protected final GestureState mGestureState;
+    protected final TaskViewSimulator mTaskViewSimulator;
+
+    protected final TransformParams mTransformParams;
+
+    // Shift in the range of [0, 1].
+    // 0 => preview snapShot is completely visible, and hotseat is completely translated down
+    // 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
+    // visible.
+    protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
+
+    // The distance needed to drag to reach the task size in recents.
+    protected int mTransitionDragLength;
+    // How much further we can drag past recents, as a factor of mTransitionDragLength.
+    protected float mDragLengthFactor = 1;
+    // Start resisting when swiping past this factor of mTransitionDragLength.
+    private float mDragLengthFactorStartPullback = 1f;
+    // This is how far down we can scale down, where 0f is full screen and 1f is recents.
+    private float mDragLengthFactorMaxPullback = 1f;
+
+    protected AnimatorPlaybackController mWindowTransitionController;
+
+    public SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState,
+            GestureState gestureState, TransformParams transformParams) {
+        mContext = context;
+        mDeviceState = deviceState;
+        mGestureState = gestureState;
+        mTaskViewSimulator = new TaskViewSimulator(context, gestureState.getActivityInterface());
+        mTransformParams = transformParams;
+    }
+
+    protected void initTransitionEndpoints(DeviceProfile dp) {
+        mDp = dp;
+
+        mTaskViewSimulator.setDp(dp);
+        mTaskViewSimulator.setLayoutRotation(
+                mDeviceState.getCurrentActiveRotation(),
+                mDeviceState.getDisplayRotation());
+        mTransitionDragLength = mGestureState.getActivityInterface().getSwipeUpDestinationAndLength(
+                dp, mContext, TEMP_RECT,
+                mTaskViewSimulator.getOrientationState().getOrientationHandler());
+
+        if (mDeviceState.isFullyGesturalNavMode()) {
+            // We can drag all the way to the top of the screen.
+            mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
+
+            float startScale = mTaskViewSimulator.getFullScreenScale();
+            // Start pulling back when RecentsView scale is 0.75f, and let it go down to 0.5f.
+            mDragLengthFactorStartPullback = (0.75f - startScale) / (1 - startScale);
+            mDragLengthFactorMaxPullback = (0.5f - startScale) / (1 - startScale);
+        } else {
+            mDragLengthFactor = 1;
+            mDragLengthFactorStartPullback = mDragLengthFactorMaxPullback = 1;
+        }
+
+        PendingAnimation pa = new PendingAnimation(mTransitionDragLength * 2);
+        mTaskViewSimulator.addAppToOverviewAnim(pa, t -> t * mDragLengthFactor);
+        mWindowTransitionController = pa.createPlaybackController();
+    }
+
+    @UiThread
+    public void updateDisplacement(float displacement) {
+        // We are moving in the negative x/y direction
+        displacement = -displacement;
+        float shift;
+        if (displacement > mTransitionDragLength * mDragLengthFactor && mTransitionDragLength > 0) {
+            shift = mDragLengthFactor;
+        } else {
+            float translation = Math.max(displacement, 0);
+            shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength;
+            if (shift > mDragLengthFactorStartPullback) {
+                float pullbackProgress = Utilities.getProgress(shift,
+                        mDragLengthFactorStartPullback, mDragLengthFactor);
+                pullbackProgress = PULLBACK_INTERPOLATOR.getInterpolation(pullbackProgress);
+                shift = mDragLengthFactorStartPullback + pullbackProgress
+                        * (mDragLengthFactorMaxPullback - mDragLengthFactorStartPullback);
+            }
+        }
+
+        mCurrentShift.updateValue(shift);
+    }
+
+    /**
+     * Called when the value of {@link #mCurrentShift} changes
+     */
+    @UiThread
+    public abstract void updateFinalShift();
+
+    protected PagedOrientationHandler getOrientationHandler() {
+        return mTaskViewSimulator.getOrientationState().getOrientationHandler();
+    }
+
+    protected abstract class HomeAnimationFactory {
+
+        public FloatingIconView mIconView;
+
+        public HomeAnimationFactory(@Nullable FloatingIconView iconView) {
+            mIconView = iconView;
+        }
+
+        public @NonNull RectF getWindowTargetRect() {
+            PagedOrientationHandler orientationHandler = getOrientationHandler();
+            DeviceProfile dp = mDp;
+            final int halfIconSize = dp.iconSizePx / 2;
+            float primaryDimension = orientationHandler
+                    .getPrimaryValue(dp.availableWidthPx, dp.availableHeightPx);
+            float secondaryDimension = orientationHandler
+                    .getSecondaryValue(dp.availableWidthPx, dp.availableHeightPx);
+            final float targetX =  primaryDimension / 2f;
+            final float targetY = secondaryDimension - dp.hotseatBarSizePx;
+            // Fallback to animate to center of screen.
+            return new RectF(targetX - halfIconSize, targetY - halfIconSize,
+                    targetX + halfIconSize, targetY + halfIconSize);
+        }
+
+        public abstract @NonNull AnimatorPlaybackController createActivityAnimationToHome();
+
+        public void playAtomicAnimation(float velocity) {
+            // No-op
+        }
+    }
+
+    /**
+     * Creates an animation that transforms the current app window into the home app.
+     * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
+     * @param homeAnimationFactory The home animation factory.
+     */
+    protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
+            HomeAnimationFactory homeAnimationFactory) {
+        final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
+        final FloatingIconView fiv = homeAnimationFactory.mIconView;
+        final boolean isFloatingIconView = fiv != null;
+
+        mWindowTransitionController.setPlayFraction(startProgress / mDragLengthFactor);
+        mTaskViewSimulator.apply(mTransformParams.setProgress(startProgress));
+        RectF cropRectF = new RectF(mTaskViewSimulator.getCurrentCropRect());
+
+        // Matrix to map a rect in Launcher space to window space
+        Matrix homeToWindowPositionMap = new Matrix();
+        mTaskViewSimulator.applyWindowToHomeRotation(homeToWindowPositionMap);
+
+        final RectF startRect = new RectF(cropRectF);
+        mTaskViewSimulator.getCurrentMatrix().mapRect(startRect);
+        // Move the startRect to Launcher space as floatingIconView runs in Launcher
+        Matrix windowToHomePositionMap = new Matrix();
+        homeToWindowPositionMap.invert(windowToHomePositionMap);
+        windowToHomePositionMap.mapRect(startRect);
+
+        RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mContext);
+        if (isFloatingIconView) {
+            anim.addAnimatorListener(fiv);
+            fiv.setOnTargetChangeListener(anim::onTargetPositionChanged);
+            fiv.setFastFinishRunnable(anim::end);
+        }
+
+        SpringAnimationRunner runner = new SpringAnimationRunner(
+                homeAnimationFactory, cropRectF, homeToWindowPositionMap);
+        anim.addOnUpdateListener(runner);
+        anim.addAnimatorListener(runner);
+        return anim;
+    }
+
+    /**
+     * @param progress The progress of the animation to the home screen.
+     * @return The current alpha to set on the animating app window.
+     */
+    protected float getWindowAlpha(float progress) {
+        // Alpha interpolates between [1, 0] between progress values [start, end]
+        final float start = 0f;
+        final float end = 0.85f;
+
+        if (progress <= start) {
+            return 1f;
+        }
+        if (progress >= end) {
+            return 0f;
+        }
+        return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5);
+    }
+
+    protected class SpringAnimationRunner extends AnimationSuccessListener
+            implements RectFSpringAnim.OnUpdateListener, BuilderProxy {
+
+        final Rect mCropRect = new Rect();
+        final Matrix mMatrix = new Matrix();
+
+        final RectF mWindowCurrentRect = new RectF();
+        final Matrix mHomeToWindowPositionMap;
+
+        final FloatingIconView mFIV;
+        final AnimatorPlaybackController mHomeAnim;
+        final RectF mCropRectF;
+
+        final float mStartRadius;
+        final float mEndRadius;
+        final float mWindowAlphaThreshold;
+
+        SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF,
+                Matrix homeToWindowPositionMap) {
+            mHomeAnim = factory.createActivityAnimationToHome();
+            mCropRectF = cropRectF;
+            mHomeToWindowPositionMap = homeToWindowPositionMap;
+
+            cropRectF.roundOut(mCropRect);
+            mFIV = factory.mIconView;
+
+            // End on a "round-enough" radius so that the shape reveal doesn't have to do too much
+            // rounding at the end of the animation.
+            mStartRadius = mTaskViewSimulator.getCurrentCornerRadius();
+            mEndRadius = cropRectF.width() / 2f;
+
+            // We want the window alpha to be 0 once this threshold is met, so that the
+            // FolderIconView can be seen morphing into the icon shape.
+            mWindowAlphaThreshold = mFIV != null ? 1f - SHAPE_PROGRESS_DURATION : 1f;
+        }
+
+        @Override
+        public void onUpdate(RectF currentRect, float progress) {
+            mHomeAnim.setPlayFraction(progress);
+            mHomeToWindowPositionMap.mapRect(mWindowCurrentRect, currentRect);
+
+            mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL);
+            float cornerRadius = Utilities.mapRange(progress, mStartRadius, mEndRadius);
+            mTransformParams
+                    .setTargetAlpha(getWindowAlpha(progress))
+                    .setCornerRadius(cornerRadius);
+
+            mTransformParams.applySurfaceParams(mTransformParams.createSurfaceParams(this));
+            if (mFIV != null) {
+                mFIV.update(currentRect, 1f, progress,
+                        mWindowAlphaThreshold, mMatrix.mapRadius(cornerRadius), false);
+            }
+        }
+
+        @Override
+        public void onBuildTargetParams(
+                Builder builder, RemoteAnimationTargetCompat app, TransformParams params) {
+            builder.withMatrix(mMatrix)
+                    .withWindowCrop(mCropRect)
+                    .withCornerRadius(params.getCornerRadius());
+        }
+
+        @Override
+        public void onCancel() {
+            if (mFIV != null) {
+                mFIV.fastFinish();
+            }
+        }
+
+        @Override
+        public void onAnimationStart(Animator animation) {
+            mHomeAnim.dispatchOnStart();
+        }
+
+        @Override
+        public void onAnimationSuccess(Animator animator) {
+            mHomeAnim.getAnimationPlayer().end();
+        }
+    }
+
+    public interface RunningWindowAnim {
+        void end();
+
+        void cancel();
+
+        static RunningWindowAnim wrap(Animator animator) {
+            return new RunningWindowAnim() {
+                @Override
+                public void end() {
+                    animator.end();
+                }
+
+                @Override
+                public void cancel() {
+                    animator.cancel();
+                }
+            };
+        }
+
+        static RunningWindowAnim wrap(RectFSpringAnim rectFSpringAnim) {
+            return new RunningWindowAnim() {
+                @Override
+                public void end() {
+                    rectFSpringAnim.end();
+                }
+
+                @Override
+                public void cancel() {
+                    rectFSpringAnim.cancel();
+                }
+            };
+        }
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
index 042c542..3cfff7e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
@@ -126,7 +126,7 @@
                 public void onScreenshot() {
                     if (isAllowedByPolicy) {
                         imageApi.saveScreenshot(mThumbnailView.getThumbnail(),
-                                getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key.id);
+                                getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key);
                     } else {
                         showBlockedByPolicyMessage();
                     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java
index 021d39d..cdaa655 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java
@@ -18,6 +18,8 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
 
 import android.app.Activity;
@@ -35,6 +37,7 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
 import com.android.launcher3.model.WellbeingModel;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.SystemShortcut;
@@ -83,10 +86,12 @@
 
         private final int mIconRes;
         private final int mTextRes;
+        private final LauncherEvent mLauncherEvent;
 
-        MultiWindowFactory(int iconRes, int textRes) {
+        MultiWindowFactory(int iconRes, int textRes, LauncherEvent launcherEvent) {
             mIconRes = iconRes;
             mTextRes = textRes;
+            mLauncherEvent = launcherEvent;
         }
 
         protected abstract boolean isAvailable(BaseDraggingActivity activity, int displayId);
@@ -102,7 +107,8 @@
             if (!isAvailable(activity, task.key.displayId)) {
                 return null;
             }
-            return new MultiWindowSystemShortcut(mIconRes, mTextRes, activity, taskView, this);
+            return new MultiWindowSystemShortcut(mIconRes, mTextRes, activity, taskView, this,
+                    mLauncherEvent);
         }
     }
 
@@ -114,11 +120,12 @@
         private final TaskThumbnailView mThumbnailView;
         private final TaskView mTaskView;
         private final MultiWindowFactory mFactory;
+        private final LauncherEvent mLauncherEvent;
 
-        public MultiWindowSystemShortcut(int iconRes, int textRes,
-                BaseDraggingActivity activity, TaskView taskView, MultiWindowFactory factory) {
+        public MultiWindowSystemShortcut(int iconRes, int textRes, BaseDraggingActivity activity,
+                TaskView taskView, MultiWindowFactory factory, LauncherEvent launcherEvent) {
             super(iconRes, textRes, activity, dummyInfo(taskView));
-
+            mLauncherEvent = launcherEvent;
             mHandler = new Handler(Looper.getMainLooper());
             mTaskView = taskView;
             mRecentsView = activity.getOverviewPanel();
@@ -203,12 +210,13 @@
                 WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
                         future, animStartedListener, mHandler, true /* scaleUp */,
                         taskKey.displayId);
+                mTarget.getStatsLogManager().log(mLauncherEvent, mTaskView.buildProto());
             }
         }
     }
 
-    TaskShortcutFactory SPLIT_SCREEN = new MultiWindowFactory(
-            R.drawable.ic_split_screen, R.string.recent_task_option_split_screen) {
+    TaskShortcutFactory SPLIT_SCREEN = new MultiWindowFactory(R.drawable.ic_split_screen,
+            R.string.recent_task_option_split_screen, LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP) {
 
         @Override
         protected boolean isAvailable(BaseDraggingActivity activity, int displayId) {
@@ -241,8 +249,8 @@
         }
     };
 
-    TaskShortcutFactory FREE_FORM = new MultiWindowFactory(
-            R.drawable.ic_split_screen, R.string.recent_task_option_freeform) {
+    TaskShortcutFactory FREE_FORM = new MultiWindowFactory(R.drawable.ic_split_screen,
+            R.string.recent_task_option_freeform, LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP) {
 
         @Override
         protected boolean isAvailable(BaseDraggingActivity activity, int displayId) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
index c68d6e2..e2e25f3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
@@ -42,6 +42,7 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.util.DefaultDisplay;
+import com.android.quickstep.util.SurfaceTransactionApplier;
 import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.util.TransformParams;
 import com.android.quickstep.views.RecentsView;
@@ -49,7 +50,6 @@
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
 
 /**
  * Utility class for helpful methods related to {@link TaskView} objects and their tasks.
@@ -128,11 +128,10 @@
             RemoteAnimationTargetCompat[] wallpaperTargets, DepthController depthController,
             PendingAnimation out) {
 
-        SyncRtSurfaceTransactionApplierCompat applier =
-                new SyncRtSurfaceTransactionApplierCompat(v);
+        SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
         final RemoteAnimationTargets targets =
                 new RemoteAnimationTargets(appTargets, wallpaperTargets, MODE_OPENING);
-        targets.addDependentTransactionApplier(applier);
+        targets.addReleaseCheck(applier);
 
         TransformParams params = new TransformParams()
                     .setSyncTransactionApplier(applier)
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 4954588..6f596e9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -30,6 +30,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
 
 import android.annotation.TargetApi;
+import android.app.ActivityManager;
 import android.app.PendingIntent;
 import android.app.RemoteAction;
 import android.app.Service;
@@ -81,6 +82,7 @@
 import com.android.quickstep.inputconsumers.OverviewWithoutFocusInputConsumer;
 import com.android.quickstep.inputconsumers.ResetGestureInputConsumer;
 import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer;
+import com.android.quickstep.inputconsumers.SysUiOverlayInputConsumer;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.AssistantUtilities;
 import com.android.quickstep.util.ProtoTracer;
@@ -133,10 +135,10 @@
     private static final int MAX_BACK_NOTIFICATION_COUNT = 3;
 
     /**
-     * System Action ID to show all apps.  This ID should follow the ones in
-     * com.android.systemui.accessibility.SystemActions.
+     * System Action ID to show all apps.
+     * TODO: Use AccessibilityService's corresponding global action constant in S
      */
-    private static final int SYSTEM_ACTION_ID_ALL_APPS = 100;
+    private static final int SYSTEM_ACTION_ID_ALL_APPS = 14;
 
     private int mBackGestureNotificationCounter = -1;
     @Nullable
@@ -466,10 +468,17 @@
 
         final int action = event.getAction();
         if (action == ACTION_DOWN) {
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.NO_SWIPE_TO_HOME, "TouchInteractionService.onInputEvent:DOWN");
+            }
             mDeviceState.setOrientationTransformIfNeeded(event);
             GestureState newGestureState;
 
             if (mDeviceState.isInSwipeUpTouchRegion(event)) {
+                if (TestProtocol.sDebugTracing) {
+                    Log.d(TestProtocol.NO_SWIPE_TO_HOME,
+                            "TouchInteractionService.onInputEvent:isInSwipeUpTouchRegion");
+                }
                 // Clone the previous gesture state since onConsumerAboutToBeSwitched might trigger
                 // onConsumerInactive and wipe the previous gesture state
                 GestureState prevGestureState = new GestureState(mGestureState);
@@ -479,18 +488,22 @@
 
                 ActiveGestureLog.INSTANCE.addLog("setInputConsumer: " + mConsumer.getName());
                 mUncheckedConsumer = mConsumer;
-            } else if (mDeviceState.isUserUnlocked()
-                    && mDeviceState.isFullyGesturalNavMode()
-                    && mDeviceState.canTriggerAssistantAction(event)) {
+            } else if (mDeviceState.isUserUnlocked() && mDeviceState.isFullyGesturalNavMode()) {
                 newGestureState = createGestureState();
-                // Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we should
-                // not interrupt it. QuickSwitch assumes that interruption can only happen if the
-                // next gesture is also quick switch.
-                mUncheckedConsumer = new AssistantInputConsumer(
-                    this,
-                    newGestureState,
-                    InputConsumer.NO_OP, mInputMonitorCompat,
-                    mOverviewComponentObserver.assistantGestureIsConstrained());
+                ActivityManager.RunningTaskInfo runningTask = newGestureState.getRunningTask();
+                if (mDeviceState.canTriggerAssistantAction(event, runningTask)) {
+                    // Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we
+                    // should not interrupt it. QuickSwitch assumes that interruption can only
+                    // happen if the next gesture is also quick switch.
+                    mUncheckedConsumer = new AssistantInputConsumer(
+                            this,
+                            newGestureState,
+                            InputConsumer.NO_OP, mInputMonitorCompat,
+                            mOverviewComponentObserver.assistantGestureIsConstrained());
+                } else {
+                    newGestureState = DEFAULT_STATE;
+                    mUncheckedConsumer = InputConsumer.NO_OP;
+                }
             } else {
                 newGestureState = DEFAULT_STATE;
                 mUncheckedConsumer = InputConsumer.NO_OP;
@@ -536,6 +549,9 @@
 
     private InputConsumer newConsumer(GestureState previousGestureState,
             GestureState newGestureState, MotionEvent event) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_SWIPE_TO_HOME, "newConsumer");
+        }
         boolean canStartSystemGesture = mDeviceState.canStartSystemGesture();
 
         if (!mDeviceState.isUserUnlocked()) {
@@ -547,6 +563,9 @@
                 return mResetGestureInputConsumer;
             }
         }
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_SWIPE_TO_HOME, "newConsumer:user is unlocked");
+        }
 
         // When there is an existing recents animation running, bypass systemState check as this is
         // a followup gesture and the first gesture started in a valid system state.
@@ -558,7 +577,7 @@
             handleOrientationSetup(base);
         }
         if (mDeviceState.isFullyGesturalNavMode()) {
-            if (mDeviceState.canTriggerAssistantAction(event)) {
+            if (mDeviceState.canTriggerAssistantAction(event, newGestureState.getRunningTask())) {
                 base = new AssistantInputConsumer(
                     this,
                     newGestureState,
@@ -588,6 +607,13 @@
                 }
             }
 
+            // If Bubbles is expanded, use the overlay input consumer, which will close Bubbles
+            // instead of going all the way home when a swipe up is detected.
+            if (mDeviceState.isBubblesExpanded()) {
+                base = new SysUiOverlayInputConsumer(
+                        getBaseContext(), mDeviceState, mInputMonitorCompat);
+            }
+
             if (mDeviceState.isScreenPinningActive()) {
                 // Note: we only allow accessibility to wrap this, and it replaces the previous
                 // base input consumer (which should be NO_OP anyway since topTaskLocked == true).
@@ -613,7 +639,7 @@
         if (!isFixedRotationTransformEnabled()) {
             return;
         }
-        mDeviceState.enableMultipleRegions(baseInputConsumer instanceof OtherActivityInputConsumer);
+        baseInputConsumer.notifyOrientationSetup();
     }
 
     private InputConsumer newBaseConsumer(GestureState previousGestureState,
@@ -725,12 +751,13 @@
         if (!mDeviceState.isUserUnlocked()) {
             return;
         }
+
         if (mDeviceState.isButtonNavMode() && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
             // Prevent the overview from being started before the real home on first boot.
             return;
         }
 
-        if (RestoreDbTask.isPending(this)) {
+        if (RestoreDbTask.isPending(this) || !mDeviceState.isUserSetupComplete()) {
             // Preloading while a restore is pending may cause launcher to start the restore
             // too early.
             return;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
index 6f919c1..be3fdde 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
@@ -15,6 +15,7 @@
  */
 package com.android.quickstep.fallback;
 
+import android.graphics.PointF;
 import android.view.MotionEvent;
 
 import androidx.annotation.Nullable;
@@ -30,7 +31,8 @@
 /**
  * In 0-button mode, intercepts swipe up from the nav bar on FallbackRecentsView to go home.
  */
-public class FallbackNavBarTouchController implements TouchController {
+public class FallbackNavBarTouchController implements TouchController,
+        TriggerSwipeUpTouchTracker.OnSwipeUpListener {
 
     private final RecentsActivity mActivity;
     @Nullable
@@ -44,7 +46,7 @@
                     DefaultDisplay.INSTANCE.get(mActivity).getInfo());
             mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(mActivity,
                     true /* disableHorizontalSwipe */, navBarPosition,
-                    null /* onInterceptTouch */, this::onSwipeUp);
+                    null /* onInterceptTouch */, this);
         } else {
             mTriggerSwipeUpTracker = null;
         }
@@ -72,7 +74,11 @@
         return false;
     }
 
-    private void onSwipeUp(boolean wasFling) {
+    @Override
+    public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
         mActivity.<FallbackRecentsView>getOverviewPanel().startHome();
     }
+
+    @Override
+    public void onSwipeUpCancelled() {}
 }
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 8b08ea7..14215a1 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
@@ -404,6 +404,11 @@
     }
 
     @Override
+    public void notifyOrientationSetup() {
+        mDeviceState.onStartGesture();
+    }
+
+    @Override
     public void onConsumerAboutToBeSwitched() {
         Preconditions.assertUIThread();
         mMainThreadHandler.removeCallbacks(mCancelRecentsAnimationRunnable);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index ac1c3a8..4440a04 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -17,6 +17,7 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.PointF;
 import android.view.MotionEvent;
 
 import com.android.launcher3.BaseActivity;
@@ -33,7 +34,8 @@
 import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
-public class OverviewWithoutFocusInputConsumer implements InputConsumer {
+public class OverviewWithoutFocusInputConsumer implements InputConsumer,
+        TriggerSwipeUpTouchTracker.OnSwipeUpListener {
 
     private final Context mContext;
     private final InputMonitorCompat mInputMonitor;
@@ -45,7 +47,7 @@
         mContext = context;
         mInputMonitor = inputMonitor;
         mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(context, disableHorizontalSwipe,
-                deviceState.getNavBarPosition(), this::onInterceptTouch, this::onSwipeUp);
+                deviceState.getNavBarPosition(), this::onInterceptTouch, this);
     }
 
     @Override
@@ -70,7 +72,8 @@
         }
     }
 
-    private void onSwipeUp(boolean wasFling) {
+    @Override
+    public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
         mContext.startActivity(new Intent(Intent.ACTION_MAIN)
                 .addCategory(Intent.CATEGORY_HOME)
                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
@@ -83,4 +86,7 @@
                 wasFling ? Touch.FLING : Touch.SWIPE, Direction.UP, containerType, pageIndex);
         activity.getUserEventDispatcher().setPreviousHomeGesture(true);
     }
+
+    @Override
+    public void onSwipeUpCancelled() {}
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
new file mode 100644
index 0000000..3f833c0
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 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.inputconsumers;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
+import com.android.quickstep.InputConsumer;
+import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
+import com.android.systemui.shared.system.InputMonitorCompat;
+
+/**
+ * Input consumer used when a fullscreen System UI overlay is showing (such as the expanded Bubbles
+ * UI).
+ *
+ * This responds to swipes up by sending a closeSystemDialogs broadcast (causing overlays to close)
+ * rather than closing the app behind the overlay and sending the user all the way home.
+ */
+public class SysUiOverlayInputConsumer implements InputConsumer,
+        TriggerSwipeUpTouchTracker.OnSwipeUpListener {
+
+    private final Context mContext;
+    private final InputMonitorCompat mInputMonitor;
+    private final TriggerSwipeUpTouchTracker mTriggerSwipeUpTracker;
+
+    public SysUiOverlayInputConsumer(
+            Context context,
+            RecentsAnimationDeviceState deviceState,
+            InputMonitorCompat inputMonitor) {
+        mContext = context;
+        mInputMonitor = inputMonitor;
+        mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(context, true,
+                deviceState.getNavBarPosition(), this::onInterceptTouch, this);
+    }
+
+    @Override
+    public int getType() {
+        return TYPE_SYSUI_OVERLAY;
+    }
+
+    @Override
+    public boolean allowInterceptByParent() {
+        return !mTriggerSwipeUpTracker.interceptedTouch();
+    }
+
+    @Override
+    public void onMotionEvent(MotionEvent ev) {
+        mTriggerSwipeUpTracker.onMotionEvent(ev);
+    }
+
+    private void onInterceptTouch() {
+        if (mInputMonitor != null) {
+            TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
+            mInputMonitor.pilferPointers();
+        }
+    }
+
+    @Override
+    public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
+        // Close system dialogs when a swipe up is detected.
+        mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+    }
+
+    @Override
+    public void onSwipeUpCancelled() {
+
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
index 8a6c4a1..e5d2c53 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
@@ -29,6 +29,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.FlingSpringAnim;
 import com.android.launcher3.util.DynamicResource;
+import com.android.quickstep.RemoteAnimationTargets.ReleaseCheck;
 import com.android.systemui.plugins.ResourceProvider;
 
 import java.util.ArrayList;
@@ -39,7 +40,7 @@
  * Applies spring forces to animate from a starting rect to a target rect,
  * while providing update callbacks to the caller.
  */
-public class RectFSpringAnim {
+public class RectFSpringAnim extends ReleaseCheck {
 
     private static final FloatPropertyCompat<RectFSpringAnim> RECT_CENTER_X =
             new FloatPropertyCompat<RectFSpringAnim>("rectCenterXSpring") {
@@ -116,6 +117,7 @@
         ResourceProvider rp = DynamicResource.provider(context);
         mMinVisChange = rp.getDimension(R.dimen.swipe_up_fling_min_visible_change);
         mYOvershoot = rp.getDimension(R.dimen.swipe_up_y_overshoot);
+        setCanRelease(true);
     }
 
     public void onTargetPositionChanged() {
@@ -190,10 +192,12 @@
                     maybeOnEnd();
                 });
 
+        setCanRelease(false);
+        mAnimsStarted = true;
+
         mRectXAnim.start();
         mRectYAnim.start();
         mRectScaleAnim.start();
-        mAnimsStarted = true;
         for (Animator.AnimatorListener animatorListener : mAnimatorListeners) {
             animatorListener.onAnimationStart(null);
         }
@@ -245,6 +249,7 @@
     private void maybeOnEnd() {
         if (mAnimsStarted && isEnded()) {
             mAnimsStarted = false;
+            setCanRelease(true);
             for (Animator.AnimatorListener animatorListener : mAnimatorListeners) {
                 animatorListener.onAnimationEnd(null);
             }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index ebc9f96..7090393 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -121,8 +121,11 @@
                 addStaggeredAnimationForView(child, grid.inv.numRows + 1, totalRows);
             }
 
-            View qsb = launcher.findViewById(R.id.search_container_all_apps);
-            addStaggeredAnimationForView(qsb, grid.inv.numRows + 2, totalRows);
+            if (launcher.getAppsView().getSearchUiManager()
+                    .isQsbVisible(NORMAL.getVisibleElements(launcher))) {
+                addStaggeredAnimationForView(launcher.getAppsView().getSearchView(),
+                        grid.inv.numRows + 2, totalRows);
+            }
         }
 
         if (animateOverviewScrim) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SurfaceTransactionApplier.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SurfaceTransactionApplier.java
new file mode 100644
index 0000000..0a3e3ec
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SurfaceTransactionApplier.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2020 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 static com.android.systemui.shared.system.TransactionCompat.deferTransactionUntil;
+import static com.android.systemui.shared.system.TransactionCompat.setEarlyWakeup;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Message;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
+import android.view.View;
+
+import com.android.quickstep.RemoteAnimationTargets.ReleaseCheck;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
+import com.android.systemui.shared.system.ViewRootImplCompat;
+
+import java.util.function.Consumer;
+
+
+/**
+ * Helper class to apply surface transactions in sync with RenderThread similar to
+ *   android.view.SyncRtSurfaceTransactionApplier
+ * with some Launcher specific utility methods
+ */
+@TargetApi(Build.VERSION_CODES.R)
+public class SurfaceTransactionApplier extends ReleaseCheck {
+
+    private static final int MSG_UPDATE_SEQUENCE_NUMBER = 0;
+
+    private final SurfaceControl mBarrierSurfaceControl;
+    private final ViewRootImplCompat mTargetViewRootImpl;
+    private final Handler mApplyHandler;
+
+    private int mLastSequenceNumber = 0;
+
+    /**
+     * @param targetView The view in the surface that acts as synchronization anchor.
+     */
+    public SurfaceTransactionApplier(View targetView) {
+        mTargetViewRootImpl = new ViewRootImplCompat(targetView);
+        mBarrierSurfaceControl = mTargetViewRootImpl.getRenderSurfaceControl();
+        mApplyHandler = new Handler(this::onApplyMessage);
+    }
+
+    protected boolean onApplyMessage(Message msg) {
+        if (msg.what == MSG_UPDATE_SEQUENCE_NUMBER) {
+            setCanRelease(msg.arg1 == mLastSequenceNumber);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Schedules applying surface parameters on the next frame.
+     *
+     * @param params The surface parameters to apply. DO NOT MODIFY the list after passing into
+     *               this method to avoid synchronization issues.
+     */
+    public void scheduleApply(final SurfaceParams... params) {
+        View view = mTargetViewRootImpl.getView();
+        if (view == null) {
+            return;
+        }
+
+        mLastSequenceNumber++;
+        final int toApplySeqNo = mLastSequenceNumber;
+        setCanRelease(false);
+        mTargetViewRootImpl.registerRtFrameCallback(frame -> {
+            if (mBarrierSurfaceControl == null || !mBarrierSurfaceControl.isValid()) {
+                Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0)
+                        .sendToTarget();
+                return;
+            }
+            Transaction t = new Transaction();
+            for (int i = params.length - 1; i >= 0; i--) {
+                SurfaceParams surfaceParams = params[i];
+                if (surfaceParams.surface.isValid()) {
+                    deferTransactionUntil(t, surfaceParams.surface, mBarrierSurfaceControl, frame);
+                    surfaceParams.applyTo(t);
+                }
+            }
+            setEarlyWakeup(t);
+            t.apply();
+            Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0)
+                    .sendToTarget();
+        });
+
+        // Make sure a frame gets scheduled.
+        view.invalidate();
+    }
+
+    /**
+     * Creates an instance of SyncRtSurfaceTransactionApplier, deferring until the target view is
+     * attached if necessary.
+     */
+    public static void create(
+            final View targetView, final Consumer<SurfaceTransactionApplier> callback) {
+        if (targetView == null) {
+            // No target view, no applier
+            callback.accept(null);
+        } else if (new ViewRootImplCompat(targetView).isValid()) {
+            // Already attached, we're good to go
+            callback.accept(new SurfaceTransactionApplier(targetView));
+        } else {
+            // Haven't been attached before we can get the view root
+            targetView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+                @Override
+                public void onViewAttachedToWindow(View v) {
+                    targetView.removeOnAttachStateChangeListener(this);
+                    callback.accept(new SurfaceTransactionApplier(targetView));
+                }
+
+                @Override
+                public void onViewDetachedFromWindow(View v) {
+                    // Do nothing
+                }
+            });
+        }
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
index 3b08675..3c9762b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -23,6 +23,7 @@
 import android.animation.TimeInterpolator;
 import android.content.Context;
 import android.graphics.Matrix;
+import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -74,7 +75,7 @@
     private DeviceProfile mDp;
 
     private final Matrix mMatrix = new Matrix();
-    private RemoteAnimationTargetCompat mRunningTarget;
+    private final Point mRunningTargetWindowPosition = new Point();
 
     // Thumbnail view properties
     private final Rect mThumbnailPosition = new Rect();
@@ -139,13 +140,19 @@
      * Sets the targets which the simulator will control
      */
     public void setPreview(RemoteAnimationTargetCompat runningTarget) {
-        mRunningTarget = runningTarget;
+        setPreviewBounds(runningTarget.screenSpaceBounds, runningTarget.contentInsets);
+        mRunningTargetWindowPosition.set(runningTarget.position.x, runningTarget.position.y);
+    }
 
-        mThumbnailData.insets.set(mRunningTarget.contentInsets);
+    /**
+     * Sets the targets which the simulator will control
+     */
+    public void setPreviewBounds(Rect bounds, Rect insets) {
+        mThumbnailData.insets.set(insets);
         // TODO: What is this?
         mThumbnailData.windowingMode = WINDOWING_MODE_FULLSCREEN;
 
-        mThumbnailPosition.set(runningTarget.screenSpaceBounds);
+        mThumbnailPosition.set(bounds);
         mLayoutValid = false;
     }
 
@@ -199,16 +206,14 @@
         postDisplayRotation(deltaRotation(
                 mOrientationState.getLauncherRotation(), mOrientationState.getDisplayRotation()),
                 mDp.widthPx, mDp.heightPx, matrix);
-        if (mRunningTarget != null) {
-            matrix.postTranslate(-mRunningTarget.position.x, -mRunningTarget.position.y);
-        }
+        matrix.postTranslate(-mRunningTargetWindowPosition.x, -mRunningTargetWindowPosition.y);
     }
 
     /**
      * Applies the target to the previously set parameters
      */
     public void apply(TransformParams params) {
-        if (mDp == null || mRunningTarget == null) {
+        if (mDp == null || mThumbnailPosition.isEmpty()) {
             return;
         }
         if (!mLayoutValid) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java
index 9bb508e..7a62e83 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java
@@ -57,7 +57,7 @@
     private float mTargetAlpha;
     private float mCornerRadius;
     private RemoteAnimationTargets mTargetSet;
-    private SyncRtSurfaceTransactionApplierCompat mSyncTransactionApplier;
+    private SurfaceTransactionApplier mSyncTransactionApplier;
 
     private BuilderProxy mHomeBuilderProxy = BuilderProxy.ALWAYS_VISIBLE;
     private BuilderProxy mBaseBuilderProxy = BuilderProxy.ALWAYS_VISIBLE;
@@ -112,7 +112,7 @@
      * are computed based on these TransformParams.
      */
     public TransformParams setSyncTransactionApplier(
-            SyncRtSurfaceTransactionApplierCompat applier) {
+            SurfaceTransactionApplier applier) {
         mSyncTransactionApplier = applier;
         return this;
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java
index c71258b..29b9558 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java
@@ -149,8 +149,12 @@
             isSwipeUp = squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop;
         }
 
-        if (isSwipeUp && mOnSwipeUp != null) {
-            mOnSwipeUp.onSwipeUp(wasFling);
+        if (mOnSwipeUp != null) {
+            if (isSwipeUp) {
+                mOnSwipeUp.onSwipeUp(wasFling, new PointF(velocityX, velocityY));
+            } else {
+                mOnSwipeUp.onSwipeUpCancelled();
+            }
         }
     }
 
@@ -161,7 +165,11 @@
         /**
          * Called on touch up if a swipe up was detected.
          * @param wasFling Whether the swipe was a fling, or just passed touch slop at low velocity.
+         * @param finalVelocity The final velocity of the swipe.
          */
-        void onSwipeUp(boolean wasFling);
+        void onSwipeUp(boolean wasFling, PointF finalVelocity);
+
+        /** Called on touch up if a swipe up was not detected. */
+        void onSwipeUpCancelled();
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
index ea33d00..08c3dc9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
@@ -21,6 +21,7 @@
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 
 import android.content.Context;
+import android.content.res.Configuration;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
@@ -33,6 +34,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
+import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.TaskOverlayFactory.OverlayUICallbacks;
 
@@ -45,8 +47,6 @@
 public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayout
         implements OnClickListener {
 
-    public static final long VISIBILITY_TRANSITION_DURATION_MS = 80;
-
     @IntDef(flag = true, value = {
             HIDDEN_UNSUPPORTED_NAVIGATION,
             HIDDEN_DISABLED_FEATURE,
@@ -131,6 +131,12 @@
         updateHiddenFlags(HIDDEN_UNSUPPORTED_NAVIGATION, !removeShelfFromOverview(getContext()));
     }
 
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        updateVerticalMargin(SysUINavigationMode.getMode(getContext()));
+    }
+
     public void updateHiddenFlags(@ActionsHiddenFlags int visibilityFlags, boolean enable) {
         if (enable) {
             mHiddenFlags |= visibilityFlags;
@@ -154,10 +160,13 @@
         return mMultiValueAlpha.getProperty(INDEX_FULLSCREEN_ALPHA);
     }
 
-    /** Updates vertical margins for different navigation mode. */
-    public void updateVerticalMarginForNavModeChange(Mode mode) {
-        int bottomMargin = 0;
-        if (mode == Mode.THREE_BUTTONS) {
+    /** Updates vertical margins for different navigation mode or configuration changes. */
+    public void updateVerticalMargin(Mode mode) {
+        int bottomMargin;
+        int orientation = getResources().getConfiguration().orientation;
+        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+            bottomMargin = 0;
+        } else if (mode == Mode.THREE_BUTTONS) {
             bottomMargin = getResources()
                     .getDimensionPixelSize(R.dimen.overview_actions_bottom_margin_three_button);
         } else {
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 a506b7e..64d90cf 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
@@ -127,6 +127,7 @@
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.util.SplitScreenBounds;
+import com.android.quickstep.util.SurfaceTransactionApplier;
 import com.android.quickstep.util.TransformParams;
 import com.android.systemui.plugins.ResourceProvider;
 import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
@@ -135,7 +136,6 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.LauncherEventUtil;
 import com.android.systemui.shared.system.PackageManagerWrapper;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 
 import java.util.ArrayList;
@@ -211,7 +211,7 @@
     protected final BaseActivityInterface mSizeStrategy;
     protected RecentsAnimationController mRecentsAnimationController;
     protected RecentsAnimationTargets mRecentsAnimationTargets;
-    protected SyncRtSurfaceTransactionApplierCompat mSyncTransactionApplier;
+    protected SurfaceTransactionApplier mSyncTransactionApplier;
     protected int mTaskWidth;
     protected int mTaskHeight;
     protected boolean mEnableDrawingLiveTile = false;
@@ -492,6 +492,7 @@
 
     public void init(OverviewActionsView actionsView) {
         mActionsView = actionsView;
+        mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
     }
 
     @Override
@@ -501,7 +502,7 @@
         mModel.getThumbnailCache().getHighResLoadingState().addCallback(this);
         mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
-        mSyncTransactionApplier = new SyncRtSurfaceTransactionApplierCompat(this);
+        mSyncTransactionApplier = new SurfaceTransactionApplier(this);
         RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this);
         mIdp.addOnChangeListener(this);
         mIPinnedStackAnimationListener.setActivity(mActivity);
@@ -1039,6 +1040,11 @@
     }
 
     private void animateRecentsRotationInPlace(int newRotation) {
+        if (mOrientationState.canLauncherRotate()) {
+            // Update the rotation but let system take care of the rotation animation
+            setLayoutRotation(newRotation, mOrientationState.getDisplayRotation());
+            return;
+        }
         AnimatorSet pa = setRecentsChangedOrientation(true);
         pa.addListener(AnimationSuccessListener.forRunnable(() -> {
             setLayoutRotation(newRotation, mOrientationState.getDisplayRotation());
@@ -1621,8 +1627,10 @@
                     : View.LAYOUT_DIRECTION_RTL);
             mClearAllButton.setRotation(mOrientationHandler.getDegreesRotated());
             mActivity.getDragLayer().recreateControllers();
+            boolean isInLandscape = touchRotation != 0
+                    || mOrientationState.getLauncherRotation() != ROTATION_0;
             mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION,
-                    touchRotation != 0 || mOrientationState.getLauncherRotation() != ROTATION_0);
+                    !mOrientationState.canLauncherRotate() && isInLandscape);
             resetPaddingFromTaskSize();
             requestLayout();
         }
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 6b759ba..af9d7f7 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
@@ -30,6 +30,7 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
 
 import android.animation.Animator;
@@ -222,8 +223,8 @@
         setOutlineProvider(mOutlineProvider);
     }
 
-    /* Builds proto for logging */
-    protected LauncherAtom.ItemInfo buildProto() {
+    /** Builds proto for logging */
+    public LauncherAtom.ItemInfo buildProto() {
         ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(getTask().key);
         LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder();
         itemBuilder.setIsWork(componentKey.user != Process.myUserHandle());
@@ -424,6 +425,7 @@
     private boolean showTaskMenu(int action) {
         getRecentsView().snapToPage(getRecentsView().indexOfChild(this));
         mMenuView = TaskMenuView.showForTask(this);
+        mActivity.getStatsLogManager().log(LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS, buildProto());
         UserEventDispatcher.newInstance(getContext()).logActionOnItem(action, Direction.NONE,
                 LauncherLogProto.ItemType.TASK_ICON);
         if (mMenuView != null) {
diff --git a/quickstep/res/layout/gesture_tutorial_fragment.xml b/quickstep/res/layout/gesture_tutorial_fragment.xml
index 190290e..459d65f 100644
--- a/quickstep/res/layout/gesture_tutorial_fragment.xml
+++ b/quickstep/res/layout/gesture_tutorial_fragment.xml
@@ -16,7 +16,7 @@
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="@color/gesture_tutorial_background_color">
+    android:background="?android:attr/colorBackground">
 
     <View
         android:id="@+id/gesture_tutorial_ripple_view"
@@ -24,6 +24,13 @@
         android:layout_height="match_parent"
         android:background="@drawable/gesture_tutorial_ripple"/>
 
+    <View
+        android:id="@+id/gesture_tutorial_fake_task_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@color/gesture_tutorial_fake_task_view_color"
+        android:visibility="invisible" />
+
     <ImageView
         android:id="@+id/gesture_tutorial_fragment_hand_coaching"
         android:layout_width="match_parent"
@@ -42,6 +49,7 @@
         android:background="@android:color/transparent"
         android:accessibilityTraversalAfter="@id/gesture_tutorial_fragment_titles_container"
         android:contentDescription="@string/gesture_tutorial_close_button_content_description"
+        android:tint="?android:attr/textColorPrimary"
         android:src="@drawable/gesture_tutorial_close_button"/>
 
     <LinearLayout
diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
index 3583676..40da136 100644
--- a/quickstep/res/values/colors.xml
+++ b/quickstep/res/values/colors.xml
@@ -14,5 +14,6 @@
      limitations under the License.
 -->
 <resources>
+    <color name="back_arrow_color_light">#FFFFFFFF</color>
     <color name="back_arrow_color_dark">#99000000</color>
 </resources>
\ No newline at end of file
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 3676965..1ed3b24 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -63,12 +63,6 @@
     <!-- Content description for a close button. [CHAR LIMIT=NONE] -->
     <string  name="gesture_tutorial_close_button_content_description" translatable="false">Close</string>
 
-
-    <!-- Hotseat migration notification title -->
-    <string name="hotseat_edu_prompt_title">Easily access your most-used apps</string>
-    <!-- Hotseat migration notification content -->
-    <string name="hotseat_edu_prompt_content">Pixel predicts apps you\’ll need next, right on your Home screen. Tap to set up.</string>
-
     <!-- Hotseat educational strings for users who don't qualify for migration -->
     <string name="hotseat_edu_title_migrate">Get app suggestions on the bottom row of your Home screen</string>
 
@@ -89,6 +83,8 @@
     <string name="hotseat_tip_no_empty_slots">Drag apps off the bottom row to get app suggestions</string>
     <!-- tip shown if user declines migration and has some open spots for prediction -->
     <string name="hotseat_tip_gaps_filled">App suggestions added to empty space</string>
+    <!-- tip shown when user migrates and predictions are enabled in hotseat -->
+    <string name="hotsaet_tip_prediction_enabled">App suggestions Enabled</string>
 
     <!-- content description for hotseat items -->
     <string name="hotseat_prediction_content_description">Predicted app: <xliff:g id="title" example="Chrome">%1$s</xliff:g></string>
@@ -116,7 +112,6 @@
     <!-- Subtitle shown on the confirmation screen after successful gesture. [CHAR LIMIT=60] -->
     <string name="back_gesture_tutorial_confirm_subtitle" translatable="false">To change the sensitivity of the back gesture, go to Settings</string>
 
-
     <!-- Title shown during interactive part of Home gesture tutorial. [CHAR LIMIT=30] -->
     <string name="home_gesture_tutorial_playground_title" translatable="false">Tutorial: Go Home</string>
     <!-- Subtitle shown during interactive parts of Home gesture tutorial. [CHAR LIMIT=60] -->
@@ -128,6 +123,17 @@
     <!-- Feedback shown during interactive parts of Home gesture tutorial when the gesture is horizontal instead of vertical. [CHAR LIMIT=100] -->
     <string name="home_gesture_feedback_wrong_swipe_direction" translatable="false">Make sure you swipe straight up</string>
 
+    <!-- Title shown during interactive part of Overview gesture tutorial. [CHAR LIMIT=30] -->
+    <string name="overview_gesture_tutorial_playground_title" translatable="false">Tutorial: Switch Apps</string>
+    <!-- Subtitle shown during interactive parts of Overview gesture tutorial. [CHAR LIMIT=60] -->
+    <string name="overview_gesture_tutorial_playground_subtitle" translatable="false">Swipe up from the bottom of the screen and hold</string>
+    <!-- Feedback shown during interactive parts of Overview gesture tutorial when the gesture is started too far from the edge. [CHAR LIMIT=100] -->
+    <string name="overview_gesture_feedback_swipe_too_far_from_edge" translatable="false">Make sure you swipe from the bottom edge of the screen</string>
+    <!-- Feedback shown during interactive parts of Overview gesture tutorial when the Home gesture is detected. [CHAR LIMIT=100] -->
+    <string name="overview_gesture_feedback_home_detected" translatable="false">Try holding the window for longer before releasing</string>
+    <!-- Feedback shown during interactive parts of Overview gesture tutorial when the gesture is horizontal instead of vertical. [CHAR LIMIT=100] -->
+    <string name="overview_gesture_feedback_wrong_swipe_direction" translatable="false">Make sure you swipe straight up and pause</string>
+
     <!-- Title shown on the confirmation screen after successful gesture. [CHAR LIMIT=30] -->
     <string name="gesture_tutorial_confirm_title" translatable="false">All set</string>
     <!-- Button text shown on a button on the confirm screen to leave the tutorial. [CHAR LIMIT=14] -->
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index 90957e4..8d054b4 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -35,14 +35,14 @@
     <style name="TextAppearance.GestureTutorial.Title"
         parent="TextAppearance.GestureTutorial">
         <item name="android:gravity">center</item>
-        <item name="android:textColor">@color/gesture_tutorial_title_color</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
         <item name="android:textSize">28sp</item>
     </style>
 
     <style name="TextAppearance.GestureTutorial.Subtitle"
         parent="TextAppearance.GestureTutorial">
         <item name="android:gravity">center</item>
-        <item name="android:textColor">@color/gesture_tutorial_subtitle_color</item>
+        <item name="android:textColor">?android:attr/textColorTertiary</item>
         <item name="android:letterSpacing">0.03</item>
         <item name="android:textSize">21sp</item>
     </style>
@@ -50,7 +50,7 @@
     <style name="TextAppearance.GestureTutorial.Feedback"
         parent="TextAppearance.GestureTutorial">
         <item name="android:gravity">center</item>
-        <item name="android:textColor">@color/gesture_tutorial_feedback_color</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
         <item name="android:letterSpacing">0.03</item>
         <item name="android:textSize">21sp</item>
     </style>
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 0968d8e..4874307 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -93,7 +93,7 @@
     public void onNavigationModeChanged(Mode newMode) {
         getDragLayer().recreateControllers();
         if (mActionsView != null && isOverviewActionsEnabled()) {
-            mActionsView.updateVerticalMarginForNavModeChange(newMode);
+            mActionsView.updateVerticalMargin(newMode);
         }
     }
 
@@ -175,7 +175,7 @@
             // Overview is above all other launcher elements, including qsb, so move it to the top.
             getOverviewPanel().bringToFront();
             mActionsView.bringToFront();
-            mActionsView.updateVerticalMarginForNavModeChange(SysUINavigationMode.getMode(this));
+            mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this));
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index c0d16c9..10f789d 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -82,6 +82,7 @@
 import com.android.quickstep.util.MultiValueUpdateListener;
 import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.util.StaggeredWorkspaceAnim;
+import com.android.quickstep.util.SurfaceTransactionApplier;
 import com.android.systemui.shared.system.ActivityCompat;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -89,7 +90,6 @@
 import com.android.systemui.shared.system.RemoteAnimationDefinitionCompat;
 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
@@ -455,9 +455,9 @@
 
         RemoteAnimationTargets openingTargets = new RemoteAnimationTargets(appTargets,
                 wallpaperTargets, MODE_OPENING);
-        SyncRtSurfaceTransactionApplierCompat surfaceApplier =
-                new SyncRtSurfaceTransactionApplierCompat(floatingView);
-        openingTargets.addDependentTransactionApplier(surfaceApplier);
+        SurfaceTransactionApplier surfaceApplier =
+                new SurfaceTransactionApplier(floatingView);
+        openingTargets.addReleaseCheck(surfaceApplier);
 
         // Scale the app icon to take up the entire screen. This simplifies the math when
         // animating the app window position / scale.
@@ -714,8 +714,7 @@
      */
     private Animator getUnlockWindowAnimator(RemoteAnimationTargetCompat[] appTargets,
             RemoteAnimationTargetCompat[] wallpaperTargets) {
-        SyncRtSurfaceTransactionApplierCompat surfaceApplier =
-                new SyncRtSurfaceTransactionApplierCompat(mDragLayer);
+        SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(mDragLayer);
         ValueAnimator unlockAnimator = ValueAnimator.ofFloat(0, 1);
         unlockAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS);
         float cornerRadius = mDeviceProfile.isMultiWindowMode ? 0 :
@@ -743,8 +742,7 @@
      */
     private Animator getClosingWindowAnimators(RemoteAnimationTargetCompat[] appTargets,
             RemoteAnimationTargetCompat[] wallpaperTargets) {
-        SyncRtSurfaceTransactionApplierCompat surfaceApplier =
-                new SyncRtSurfaceTransactionApplierCompat(mDragLayer);
+        SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(mDragLayer);
         Matrix matrix = new Matrix();
         Point tmpPos = new Point();
         ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
index 2181aa8..f42b124 100644
--- a/quickstep/src/com/android/launcher3/model/WellbeingModel.java
+++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
@@ -77,6 +77,7 @@
     private static final String EXTRA_ACTION = "action";
     private static final String EXTRA_MAX_NUM_ACTIONS_SHOWN = "max_num_actions_shown";
     private static final String EXTRA_PACKAGES = "packages";
+    private static final String EXTRA_SUCCESS = "success";
 
     public static final MainThreadInitializedObject<WellbeingModel> INSTANCE =
             new MainThreadInitializedObject<>(WellbeingModel::new);
@@ -221,6 +222,7 @@
             params.putInt(EXTRA_MAX_NUM_ACTIONS_SHOWN, 1);
             // Perform wellbeing call .
             remoteActionBundle = client.call(METHOD_GET_ACTIONS, null, params);
+            if (!remoteActionBundle.getBoolean(EXTRA_SUCCESS, true)) return false;
 
             synchronized (mModelLock) {
                 // Remove the entries for requested packages, and then update the fist with what we
@@ -281,9 +283,9 @@
                 // Remove all existing messages
                 mWorkerHandler.removeCallbacksAndMessages(null);
                 final String[] packageNames = mContext.getSystemService(LauncherApps.class)
-                            .getActivityList(null, Process.myUserHandle()).stream()
-                            .map(li -> li.getApplicationInfo().packageName).distinct()
-                            .toArray(String[]::new);
+                        .getActivityList(null, Process.myUserHandle()).stream()
+                        .map(li -> li.getApplicationInfo().packageName).distinct()
+                        .toArray(String[]::new);
                 if (!updateActions(packageNames)) {
                     scheduleRefreshRetry(msg);
                 }
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index 8292a92..ebe9e26 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.statehandlers;
 
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTROLLER;
 
 import android.os.IBinder;
@@ -102,11 +103,30 @@
      */
     private float mDepth;
 
+    private View.OnAttachStateChangeListener mOnAttachListener;
+
     public DepthController(Launcher l) {
         mLauncher = l;
     }
 
     private void ensureDependencies() {
+        if (mLauncher.getRootView() != null && mOnAttachListener == null) {
+            mOnAttachListener = new View.OnAttachStateChangeListener() {
+                @Override
+                public void onViewAttachedToWindow(View view) {
+                    // To handle the case where window token is invalid during last setDepth call.
+                    IBinder windowToken = mLauncher.getRootView().getWindowToken();
+                    if (windowToken != null) {
+                        mWallpaperManager.setWallpaperZoomOut(windowToken, mDepth);
+                    }
+                }
+
+                @Override
+                public void onViewDetachedFromWindow(View view) {
+                }
+            };
+            mLauncher.getRootView().addOnAttachStateChangeListener(mOnAttachListener);
+        }
         if (mWallpaperManager != null) {
             return;
         }
@@ -172,11 +192,12 @@
 
         float toDepth = toState.getDepth(mLauncher);
         if (Float.compare(mDepth, toDepth) != 0) {
-            animation.setFloat(this, DEPTH, toDepth, LINEAR);
+            animation.setFloat(this, DEPTH, toDepth, config.getInterpolator(ANIM_DEPTH, LINEAR));
         }
     }
 
     private void setDepth(float depth) {
+        depth = Utilities.boundToRange(depth, 0, 1);
         // Round out the depth to dedupe frequent, non-perceptable updates
         int depthI = (int) (depth * 256);
         float depthF = depthI / 256f;
@@ -184,10 +205,10 @@
             return;
         }
 
-        mDepth = depthF;
         if (mSurface == null || !mSurface.isValid()) {
             return;
         }
+        mDepth = depthF;
         ensureDependencies();
         IBinder windowToken = mLauncher.getRootView().getWindowToken();
         if (windowToken != null) {
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 9124925..7122647 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -101,7 +101,7 @@
         activity.getStateManager().reapplyState();
     }
 
-    public abstract void onSwipeUpToHomeComplete();
+    public abstract void onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState);
 
     public abstract void onAssistantVisibilityChanged(float visibility);
 
diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
index a4861dc..ec720d5 100644
--- a/quickstep/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -34,6 +34,7 @@
     int TYPE_OVERVIEW_WITHOUT_FOCUS = 1 << 7;
     int TYPE_RESET_GESTURE = 1 << 8;
     int TYPE_OVERSCROLL = 1 << 9;
+    int TYPE_SYSUI_OVERLAY = 1 << 10;
 
     String[] NAMES = new String[] {
            "TYPE_NO_OP",                    // 0
@@ -46,6 +47,7 @@
             "TYPE_OVERVIEW_WITHOUT_FOCUS",  // 7
             "TYPE_RESET_GESTURE",           // 8
             "TYPE_OVERSCROLL",              // 9
+            "TYPE_SYSUI_OVERLAY"         // 10
     };
 
     InputConsumer NO_OP = () -> TYPE_NO_OP;
@@ -70,6 +72,11 @@
     }
 
     /**
+     * Handle and specific setup necessary based on the orientation of the device
+     */
+    default void notifyOrientationSetup() {}
+
+    /**
      * Returns the active input consumer is in the hierarchy of this input consumer.
      */
     default InputConsumer getActiveConsumerInHierarchy() {
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index 4cf7aab..3375c53 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -36,8 +36,8 @@
 
 import com.android.launcher3.R;
 import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.DefaultDisplay;
-import com.android.quickstep.util.RecentsOrientedState.SurfaceRotation;
 
 import java.io.PrintWriter;
 
@@ -67,6 +67,14 @@
     private boolean mEnableMultipleRegions;
     private Resources mResources;
     private OrientationRectF mLastRectTouched;
+    /**
+     * The rotation of the last touched nav bar. Derived from {@link #mLastRectTouched}, but has a
+     * longer lifetime than the rect. Note this is different than {@link #mQuickStepStartingRotation}
+     * as it always updates its value on every touch whereas mQuickstepStartingRotation only
+     * updates when device rotation matches touch rotation. Maybe this will be only one necessary
+     * after TODO(b/154580671) is in. TBD.
+     */
+    private int mLastRectRotation;
     private SysUINavigationMode.Mode mMode;
     private QuickStepContractInfo mContractInfo;
 
@@ -143,15 +151,18 @@
      * ALSO, you BETTER call this with {@param enableMultipleRegions} set to false once you're done.
      *
      * @param enableMultipleRegions Set to true to start tracking multiple nav bar regions
-     * @param info The current displayInfo
+     * @param info The current displayInfo which will be the start of the quickswitch gesture
      */
     void enableMultipleRegions(boolean enableMultipleRegions, DefaultDisplay.Info info) {
         mEnableMultipleRegions = enableMultipleRegions &&
                 mMode != SysUINavigationMode.Mode.TWO_BUTTONS;
-        if (!enableMultipleRegions) {
+        if (mEnableMultipleRegions) {
+            mQuickStepStartingRotation = info.rotation;
+        } else {
+            mLastRectRotation = 0;
             mQuickStepStartingRotation = QUICKSTEP_ROTATION_UNINITIALIZED;
-            resetSwipeRegions(info);
         }
+        resetSwipeRegions(info);
     }
 
     /**
@@ -236,6 +247,10 @@
     }
 
     boolean touchInValidSwipeRegions(float x, float y) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_SWIPE_TO_HOME, "touchInValidSwipeRegions " + x + "," + y + " in "
+                    + mLastRectTouched);
+        }
         if (mLastRectTouched != null) {
             return mLastRectTouched.contains(x, y);
         }
@@ -243,11 +258,7 @@
     }
 
     int getCurrentActiveRotation() {
-        if (mLastRectTouched == null) {
-            return 0;
-        } else {
-            return mLastRectTouched.mRotation;
-        }
+        return mLastRectRotation;
     }
 
     int getQuickStepStartingRotation() {
@@ -281,12 +292,20 @@
 
                 for (int i = 0; i < MAX_ORIENTATIONS; i++) {
                     OrientationRectF rect = mSwipeTouchRegions.get(i);
+                    if (TestProtocol.sDebugTracing) {
+                        Log.d(TestProtocol.NO_SWIPE_TO_HOME, "transform:DOWN, rect=" + rect);
+                    }
                     if (rect == null) {
                         continue;
                     }
                     if (rect.applyTransform(event, false)) {
+                        if (TestProtocol.sDebugTracing) {
+                            Log.d(TestProtocol.NO_SWIPE_TO_HOME, "setting mLastRectTouched");
+                        }
                         mLastRectTouched = rect;
-                        if (mCurrentDisplayRotation == mLastRectTouched.mRotation) {
+                        mLastRectRotation = rect.mRotation;
+                        if (mEnableMultipleRegions && mCurrentDisplayRotation == mLastRectRotation) {
+                            // TODO(b/154580671) might make this block unnecessary
                             // Start a touch session for the default nav region for the display
                             mQuickStepStartingRotation = mLastRectTouched.mRotation;
                             resetSwipeRegions();
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 2d9c56f..70b4f20 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -40,21 +40,20 @@
 /**
  * Manages the recent task list from the system, caching it as necessary.
  */
-@TargetApi(Build.VERSION_CODES.P)
+@TargetApi(Build.VERSION_CODES.R)
 public class RecentTasksList extends TaskStackChangeListener {
 
+    private static final TaskLoadResult INVALID_RESULT = new TaskLoadResult(-1, false, 0);
+
     private final KeyguardManagerCompat mKeyguardManager;
     private final LooperExecutor mMainThreadExecutor;
     private final ActivityManagerWrapper mActivityManagerWrapper;
 
     // The list change id, increments as the task list changes in the system
     private int mChangeId;
-    // The last change id when the list was last loaded completely, must be <= the list change id
-    private int mLastLoadedId;
-    // The last change id was loaded with keysOnly  = true
-    private boolean mLastLoadHadKeysOnly;
 
-    ArrayList<Task> mTasks = new ArrayList<>();
+    private TaskLoadResult mResultsBg = INVALID_RESULT;
+    private TaskLoadResult mResultsUi = INVALID_RESULT;
 
     public RecentTasksList(LooperExecutor mainThreadExecutor,
             KeyguardManagerCompat keyguardManager, ActivityManagerWrapper activityManagerWrapper) {
@@ -71,7 +70,7 @@
     public void getTaskKeys(int numTasks, Consumer<ArrayList<Task>> callback) {
         // Kick off task loading in the background
         UI_HELPER_EXECUTOR.execute(() -> {
-            ArrayList<Task> tasks = loadTasksInBackground(numTasks, true /* loadKeysOnly */);
+            ArrayList<Task> tasks = loadTasksInBackground(numTasks, -1, true /* loadKeysOnly */);
             mMainThreadExecutor.execute(() -> callback.accept(tasks));
         });
     }
@@ -85,26 +84,30 @@
      */
     public synchronized int getTasks(boolean loadKeysOnly, Consumer<ArrayList<Task>> callback) {
         final int requestLoadId = mChangeId;
-        Runnable resultCallback = callback == null
-                ? () -> { }
-                : () -> callback.accept(copyOf(mTasks));
-
-        if (mLastLoadedId == mChangeId && (!mLastLoadHadKeysOnly || loadKeysOnly)) {
+        if (mResultsUi.isValidForRequest(requestLoadId, loadKeysOnly)) {
             // The list is up to date, send the callback on the next frame,
             // so that requestID can be returned first.
-            mMainThreadExecutor.post(resultCallback);
+            if (callback != null) {
+                // Copy synchronously as the changeId might change by next frame
+                ArrayList<Task> result = copyOf(mResultsUi);
+                mMainThreadExecutor.post(() -> callback.accept(result));
+            }
+
             return requestLoadId;
         }
 
         // Kick off task loading in the background
         UI_HELPER_EXECUTOR.execute(() -> {
-            ArrayList<Task> tasks = loadTasksInBackground(Integer.MAX_VALUE, loadKeysOnly);
-
+            if (!mResultsBg.isValidForRequest(requestLoadId, loadKeysOnly)) {
+                mResultsBg = loadTasksInBackground(Integer.MAX_VALUE, requestLoadId, loadKeysOnly);
+            }
+            TaskLoadResult loadResult = mResultsBg;
             mMainThreadExecutor.execute(() -> {
-                mTasks = tasks;
-                mLastLoadedId = requestLoadId;
-                mLastLoadHadKeysOnly = loadKeysOnly;
-                resultCallback.run();
+                mResultsUi = loadResult;
+                if (callback != null) {
+                    ArrayList<Task> result = copyOf(mResultsUi);
+                    callback.accept(result);
+                }
             });
         });
 
@@ -119,8 +122,8 @@
     }
 
     @Override
-    public synchronized void onTaskStackChanged() {
-        mChangeId++;
+    public void onTaskStackChanged() {
+        invalidateLoadedTasks();
     }
 
     @Override
@@ -131,22 +134,28 @@
         // callback (those are for changes to the active tasks), but the task list is still updated,
         // so we should also invalidate the change id to ensure we load a new list instead of 
         // reusing a stale list.
-        mChangeId++;
+        invalidateLoadedTasks();
     }
 
     @Override
     public void onTaskRemoved(int taskId) {
-        mTasks = loadTasksInBackground(Integer.MAX_VALUE, false);
+        invalidateLoadedTasks();
     }
 
+
     @Override
-    public synchronized void onActivityPinned(String packageName, int userId, int taskId,
-            int stackId) {
-        mChangeId++;
+    public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
+        invalidateLoadedTasks();
     }
 
     @Override
     public synchronized void onActivityUnpinned() {
+        invalidateLoadedTasks();
+    }
+
+    private synchronized void invalidateLoadedTasks() {
+        UI_HELPER_EXECUTOR.execute(() -> mResultsBg = INVALID_RESULT);
+        mResultsUi = INVALID_RESULT;
         mChangeId++;
     }
 
@@ -154,9 +163,8 @@
      * Loads and creates a list of all the recent tasks.
      */
     @VisibleForTesting
-    ArrayList<Task> loadTasksInBackground(int numTasks, boolean loadKeysOnly) {
+    TaskLoadResult loadTasksInBackground(int numTasks, int requestId, boolean loadKeysOnly) {
         int currentUserId = Process.myUserHandle().getIdentifier();
-        ArrayList<Task> allTasks = new ArrayList<>();
         List<ActivityManager.RecentTaskInfo> rawTasks =
                 mActivityManagerWrapper.getRecentTasks(numTasks, currentUserId);
         // The raw tasks are given in most-recent to least-recent order, we need to reverse it
@@ -173,6 +181,7 @@
             }
         };
 
+        TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size());
         for (ActivityManager.RecentTaskInfo rawTask : rawTasks) {
             Task.TaskKey taskKey = new Task.TaskKey(rawTask);
             Task task;
@@ -197,4 +206,22 @@
         }
         return newTasks;
     }
+
+    private static class TaskLoadResult extends ArrayList<Task> {
+
+        final int mId;
+
+        // If the result was loaded with keysOnly  = true
+        final boolean mKeysOnly;
+
+        TaskLoadResult(int id, boolean keysOnly, int size) {
+            super(size);
+            mId = id;
+            mKeysOnly = keysOnly;
+        }
+
+        boolean isValidForRequest(int requestId, boolean loadKeysOnly) {
+            return mId == requestId && (!mKeysOnly || loadKeysOnly);
+        }
+    }
 }
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index b0a3cd2..4e9aa61 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -29,7 +29,6 @@
 import android.view.MotionEvent;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.util.Preconditions;
@@ -58,8 +57,7 @@
     private boolean mUseLauncherSysBarFlags = false;
     private boolean mSplitScreenMinimized = false;
     private boolean mTouchInProgress;
-    private boolean mFinishPending;
-    private @Nullable Runnable mFinishPendingCallback;
+    private boolean mDisableInputProxyPending;
 
     public RecentsAnimationController(RecentsAnimationControllerCompat controller,
             boolean allowMinimizeSplitScreen,
@@ -138,12 +136,12 @@
 
     @UiThread
     public void finishAnimationToHome() {
-        finishAndClear(true /* toRecents */, null, false /* sendUserLeaveHint */);
+        finishAndDisableInputProxy(true /* toRecents */, null, false /* sendUserLeaveHint */);
     }
 
     @UiThread
     public void finishAnimationToApp() {
-        finishAndClear(false /* toRecents */, null, false /* sendUserLeaveHint */);
+        finishAndDisableInputProxy(false /* toRecents */, null, false /* sendUserLeaveHint */);
     }
 
     /** See {@link #finish(boolean, Runnable, boolean)} */
@@ -162,19 +160,16 @@
     @UiThread
     public void finish(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint) {
         Preconditions.assertUIThread();
-        if (!toRecents) {
-            finishAndClear(false, onFinishComplete, sendUserLeaveHint);
+        if (toRecents && mTouchInProgress) {
+            // Finish the controller as requested, but don't disable input proxy yet.
+            mDisableInputProxyPending = true;
+            finishController(toRecents, onFinishComplete, sendUserLeaveHint);
         } else {
-            if (mTouchInProgress) {
-                mFinishPending = true;
-                mFinishPendingCallback = onFinishComplete;
-            } else {
-                finishAndClear(true, onFinishComplete, sendUserLeaveHint);
-            }
+            finishAndDisableInputProxy(toRecents, onFinishComplete, sendUserLeaveHint);
         }
     }
 
-    private void finishAndClear(boolean toRecents, Runnable onFinishComplete,
+    private void finishAndDisableInputProxy(boolean toRecents, Runnable onFinishComplete,
             boolean sendUserLeaveHint) {
         disableInputProxy();
         finishController(toRecents, onFinishComplete, sendUserLeaveHint);
@@ -262,11 +257,9 @@
         } else if (action == ACTION_CANCEL || action == ACTION_UP) {
             // Finish any pending actions
             mTouchInProgress = false;
-            if (mFinishPending) {
-                mFinishPending = false;
-                finishAndClear(true /* toRecents */, mFinishPendingCallback,
-                        false /* sendUserLeaveHint */);
-                mFinishPendingCallback = null;
+            if (mDisableInputProxyPending) {
+                mDisableInputProxyPending = false;
+                disableInputProxy();
             }
         }
         if (mInputConsumer != null) {
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 6e7c423..66e011d 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -18,6 +18,7 @@
 import static android.content.Intent.ACTION_USER_UNLOCKED;
 
 import static com.android.launcher3.util.DefaultDisplay.CHANGE_ALL;
+import static com.android.launcher3.util.DefaultDisplay.CHANGE_FRAME_DELAY;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
@@ -44,6 +45,7 @@
 import android.graphics.Region;
 import android.os.Process;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.view.MotionEvent;
 
@@ -52,6 +54,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.SecureSettingsObserver;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
 import com.android.quickstep.util.NavBarPosition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -103,7 +106,8 @@
     private TaskStackChangeListener mFrozenTaskListener = new TaskStackChangeListener() {
         @Override
         public void onRecentTaskListFrozenChanged(boolean frozen) {
-            if (frozen) {
+            mTaskListFrozen = frozen;
+            if (frozen || mInOverview) {
                 return;
             }
             enableMultipleRegions(false);
@@ -124,6 +128,9 @@
      * TODO: (b/156984037) For when user rotates after entering overview
      */
     private boolean mInOverview;
+    private boolean mTaskListFrozen;
+
+    private boolean mIsUserSetupComplete;
 
     public RecentsAnimationDeviceState(Context context) {
         mContext = context;
@@ -175,6 +182,17 @@
                         ComponentName.unflattenFromString(blockingActivity));
             }
         }
+
+        SecureSettingsObserver userSetupObserver = new SecureSettingsObserver(
+                context.getContentResolver(),
+                e -> mIsUserSetupComplete = e,
+                Settings.Secure.USER_SETUP_COMPLETE,
+                0);
+        mIsUserSetupComplete = userSetupObserver.getValue();
+        if (!mIsUserSetupComplete) {
+            userSetupObserver.register();
+            runOnDestroy(userSetupObserver::unregister);
+        }
     }
 
     private void setupOrientationSwipeHandler() {
@@ -243,7 +261,8 @@
 
     @Override
     public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) {
-        if (info.id != getDisplayId()) {
+        if (info.id != getDisplayId() || (flags & CHANGE_FRAME_DELAY) == CHANGE_FRAME_DELAY) {
+            // ignore displays that aren't running launcher and frame refresh rate changes
             return;
         }
 
@@ -314,6 +333,13 @@
         return mIsUserUnlocked;
     }
 
+    /**
+     * @return whether the user has completed setup wizard
+     */
+    public boolean isUserSetupComplete() {
+        return mIsUserSetupComplete;
+    }
+
     private void notifyUserUnlocked() {
         for (Runnable action : mUserUnlockedActions) {
             action.run();
@@ -360,7 +386,6 @@
         return (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
                 && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
                 && (mSystemUiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0
-                && (mSystemUiStateFlags & SYSUI_STATE_BUBBLES_EXPANDED) == 0
                 && ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
                         || (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0);
     }
@@ -381,6 +406,13 @@
     }
 
     /**
+     * @return whether the bubble stack is expanded
+     */
+    public boolean isBubblesExpanded() {
+        return (mSystemUiStateFlags & SYSUI_STATE_BUBBLES_EXPANDED) != 0;
+    }
+
+    /**
      * @return whether lock-task mode is active
      */
     public boolean isLockToAppActive() {
@@ -493,20 +525,22 @@
 
     /**
      * @param ev An ACTION_DOWN motion event
-     * @return whether the given motion event can trigger the assistant.
+     * @param task Info for the currently running task
+     * @return whether the given motion event can trigger the assistant over the current task.
      */
-    public boolean canTriggerAssistantAction(MotionEvent ev) {
+    public boolean canTriggerAssistantAction(MotionEvent ev, ActivityManager.RunningTaskInfo task) {
         return mAssistantAvailable
                 && !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags)
                 && mOrientationTouchTransformer.touchInAssistantRegion(ev)
-                && !isLockToAppActive();
+                && !isLockToAppActive()
+                && !isGestureBlockedActivity(task);
     }
 
     /**
      * *May* apply a transform on the motion event if it lies in the nav bar region for another
      * orientation that is currently being tracked as a part of quickstep
      */
-    public void setOrientationTransformIfNeeded(MotionEvent event) {
+    void setOrientationTransformIfNeeded(MotionEvent event) {
         // negative coordinates bug b/143901881
         if (event.getX() < 0 || event.getY() < 0) {
             event.setLocation(Math.max(0, event.getX()), Math.max(0, event.getY()));
@@ -514,25 +548,54 @@
         mOrientationTouchTransformer.transform(event);
     }
 
-    void onSwipeUpToOverview(BaseActivityInterface activityInterface) {
-        mInOverview = true;
-        activityInterface.onExitOverview(this, () -> {
-            mInOverview = false;
-            enableMultipleRegions(false);
-        });
+    void enableMultipleRegions(boolean enable) {
+        mOrientationTouchTransformer.enableMultipleRegions(enable, mDefaultDisplay.getInfo());
+        notifySysuiForRotation(mOrientationTouchTransformer.getQuickStepStartingRotation());
     }
 
-    void enableMultipleRegions(boolean enable) {
-        if (mInOverview) {
-            return;
+    private void notifySysuiForRotation(int rotation) {
+        UI_HELPER_EXECUTOR.execute(() ->
+                SystemUiProxy.INSTANCE.get(mContext).onQuickSwitchToNewTask(rotation));
+    }
+
+    public void onStartGesture() {
+        if (mTaskListFrozen) {
+            // Prioritize whatever nav bar user touches once in quickstep
+            // This case is specifically when user changes what nav bar they are using mid
+            // quickswitch session before tasks list is unfrozen
+            notifySysuiForRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
         }
-        mOrientationTouchTransformer.enableMultipleRegions(enable, mDefaultDisplay.getInfo());
-        UI_HELPER_EXECUTOR.execute(() -> {
-            int quickStepStartingRotation =
-                    mOrientationTouchTransformer.getQuickStepStartingRotation();
-            SystemUiProxy.INSTANCE.get(mContext)
-                    .onQuickSwitchToNewTask(quickStepStartingRotation);
-        });
+    }
+
+
+    void onEndTargetCalculated(GestureState.GestureEndTarget endTarget,
+            BaseActivityInterface activityInterface) {
+        if (endTarget == GestureState.GestureEndTarget.RECENTS) {
+            mInOverview = true;
+            if (!mTaskListFrozen) {
+                // If we're in landscape w/o ever quickswitching, show the navbar in landscape
+                enableMultipleRegions(true);
+            }
+            activityInterface.onExitOverview(this, () -> {
+                mInOverview = false;
+                enableMultipleRegions(false);
+            });
+        } else if (endTarget == GestureState.GestureEndTarget.HOME) {
+            enableMultipleRegions(false);
+        } else if (endTarget == GestureState.GestureEndTarget.NEW_TASK) {
+            if (mOrientationTouchTransformer.getQuickStepStartingRotation() == -1) {
+                // First gesture to start quickswitch
+                enableMultipleRegions(true);
+            } else {
+                notifySysuiForRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
+            }
+        } else if (endTarget == GestureState.GestureEndTarget.LAST_TASK) {
+            if (!mTaskListFrozen) {
+                // touched nav bar but didn't go anywhere and not quickswitching, do nothing
+                return;
+            }
+            notifySysuiForRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
+        }
     }
 
     public int getCurrentActiveRotation() {
diff --git a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
index f90df45..ab5e3ba 100644
--- a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
@@ -16,19 +16,16 @@
 package com.android.quickstep;
 
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
 
-import java.util.ArrayDeque;
 import java.util.ArrayList;
-import java.util.Queue;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * Holds a collection of RemoteAnimationTargets, filtered by different properties.
  */
 public class RemoteAnimationTargets {
 
-    private final Queue<SyncRtSurfaceTransactionApplierCompat> mDependentTransactionAppliers =
-            new ArrayDeque<>(1);
+    private final CopyOnWriteArrayList<ReleaseCheck> mReleaseChecks = new CopyOnWriteArrayList<>();
 
     public final RemoteAnimationTargetCompat[] unfilteredApps;
     public final RemoteAnimationTargetCompat[] apps;
@@ -36,6 +33,8 @@
     public final int targetMode;
     public final boolean hasRecents;
 
+    private boolean mReleased = false;
+
     public RemoteAnimationTargets(RemoteAnimationTargetCompat[] apps,
             RemoteAnimationTargetCompat[] wallpapers, int targetMode) {
         ArrayList<RemoteAnimationTargetCompat> filteredApps = new ArrayList<>();
@@ -76,21 +75,65 @@
         return false;
     }
 
-    public void addDependentTransactionApplier(SyncRtSurfaceTransactionApplierCompat delay) {
-        mDependentTransactionAppliers.add(delay);
+    public void addReleaseCheck(ReleaseCheck check) {
+        mReleaseChecks.add(check);
     }
 
     public void release() {
-        SyncRtSurfaceTransactionApplierCompat applier = mDependentTransactionAppliers.poll();
-        if (applier == null) {
-            for (RemoteAnimationTargetCompat target : unfilteredApps) {
-                target.release();
+        if (mReleased) {
+            return;
+        }
+        for (ReleaseCheck check : mReleaseChecks) {
+            if (!check.mCanRelease) {
+                check.addOnSafeToReleaseCallback(this::release);
+                return;
             }
-            for (RemoteAnimationTargetCompat target : wallpapers) {
-                target.release();
+        }
+        mReleaseChecks.clear();
+        mReleased = true;
+
+        for (RemoteAnimationTargetCompat target : unfilteredApps) {
+            target.release();
+        }
+        for (RemoteAnimationTargetCompat target : wallpapers) {
+            target.release();
+        }
+    }
+
+    /**
+     * Interface for intercepting surface release method
+     */
+    public static class ReleaseCheck {
+
+        boolean mCanRelease = false;
+        private Runnable mAfterApplyCallback;
+
+        protected void setCanRelease(boolean canRelease) {
+            mCanRelease = canRelease;
+            if (mCanRelease && mAfterApplyCallback != null) {
+                Runnable r = mAfterApplyCallback;
+                mAfterApplyCallback = null;
+                r.run();
             }
-        } else {
-            applier.addAfterApplyCallback(this::release);
+        }
+
+        /**
+         * Adds a callback to notify when the surface can safely be released
+         */
+        void addOnSafeToReleaseCallback(Runnable callback) {
+            if (mCanRelease) {
+                callback.run();
+            } else {
+                if (mAfterApplyCallback == null) {
+                    mAfterApplyCallback = callback;
+                } else {
+                    final Runnable oldCallback = mAfterApplyCallback;
+                    mAfterApplyCallback = () -> {
+                        callback.run();
+                        oldCallback.run();
+                    };
+                }
+            }
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 20d133c..299e9e5 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -31,6 +31,7 @@
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
 import com.android.systemui.shared.recents.ISystemUiProxy;
+import com.android.systemui.shared.recents.model.Task;
 
 /**
  * Holds the reference to SystemUI.
@@ -344,4 +345,17 @@
             }
         }
     }
+
+    @Override
+    public void handleImageBundleAsScreenshot(Bundle screenImageBundle, Rect locationInScreen,
+            Insets visibleInsets, Task.TaskKey task) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.handleImageBundleAsScreenshot(screenImageBundle, locationInScreen,
+                        visibleInsets, task);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call handleImageBundleAsScreenshot");
+            }
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
index 58870ed..1f398fc 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
@@ -18,8 +18,11 @@
 import static com.android.quickstep.interaction.TutorialController.TutorialType.BACK_NAVIGATION_COMPLETE;
 import static com.android.quickstep.interaction.TutorialController.TutorialType.LEFT_EDGE_BACK_NAVIGATION;
 
+import android.graphics.PointF;
 import android.view.View;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.R;
 import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
 import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult;
@@ -32,14 +35,6 @@
     }
 
     @Override
-    void transitToController() {
-        super.transitToController();
-        if (mTutorialType != BACK_NAVIGATION_COMPLETE) {
-            showHandCoachingAnimation();
-        }
-    }
-
-    @Override
     Integer getTitleStringId() {
         switch (mTutorialType) {
             case RIGHT_EDGE_BACK_NAVIGATION:
@@ -154,11 +149,14 @@
     }
 
     @Override
-    public void onNavBarGestureAttempted(NavBarGestureResult result) {
+    public void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity) {
         if (mTutorialType == BACK_NAVIGATION_COMPLETE) {
             if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) {
                 mTutorialFragment.closeTutorial();
             }
         }
     }
+
+    @Override
+    public void setNavBarGestureProgress(@Nullable Float displacement) {}
 }
diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
index 5bf5026..0521db4 100644
--- a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
+++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
@@ -280,7 +280,11 @@
                 new SpringForce()
                         .setStiffness(SpringForce.STIFFNESS_MEDIUM)
                         .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
-        mPaint.setColor(context.getColor(R.color.back_arrow_color_dark));
+        int currentNightMode =
+                context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+        mPaint.setColor(context.getColor(currentNightMode == Configuration.UI_MODE_NIGHT_YES
+                ? R.color.back_arrow_color_light
+                : R.color.back_arrow_color_dark));
         loadDimens();
         updateArrowDirection();
 
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
index 524cbaf..0edabd4 100644
--- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
@@ -17,6 +17,9 @@
 
 import static com.android.quickstep.interaction.TutorialController.TutorialType.HOME_NAVIGATION_COMPLETE;
 
+import android.annotation.TargetApi;
+import android.graphics.PointF;
+import android.os.Build;
 import android.view.View;
 
 import com.android.launcher3.R;
@@ -24,21 +27,14 @@
 import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult;
 
 /** A {@link TutorialController} for the Home tutorial. */
-final class HomeGestureTutorialController extends TutorialController {
+@TargetApi(Build.VERSION_CODES.R)
+final class HomeGestureTutorialController extends SwipeUpGestureTutorialController {
 
     HomeGestureTutorialController(HomeGestureTutorialFragment fragment, TutorialType tutorialType) {
         super(fragment, tutorialType);
     }
 
     @Override
-    void transitToController() {
-        super.transitToController();
-        if (mTutorialType != HOME_NAVIGATION_COMPLETE) {
-            showHandCoachingAnimation();
-        }
-    }
-
-    @Override
     Integer getTitleStringId() {
         switch (mTutorialType) {
             case HOME_NAVIGATION:
@@ -74,6 +70,14 @@
     public void onBackGestureAttempted(BackGestureResult result) {
         switch (mTutorialType) {
             case HOME_NAVIGATION:
+                switch (result) {
+                    case BACK_COMPLETED_FROM_LEFT:
+                    case BACK_COMPLETED_FROM_RIGHT:
+                    case BACK_CANCELLED_FROM_LEFT:
+                    case BACK_CANCELLED_FROM_RIGHT:
+                        showFeedback(R.string.home_gesture_feedback_swipe_too_far_from_edge);
+                        break;
+                }
                 break;
             case HOME_NAVIGATION_COMPLETE:
                 if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT
@@ -85,22 +89,26 @@
     }
 
     @Override
-    public void onNavBarGestureAttempted(NavBarGestureResult result) {
+    public void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity) {
         switch (mTutorialType) {
             case HOME_NAVIGATION:
                 switch (result) {
-                    case HOME_GESTURE_COMPLETED:
-                        hideHandCoachingAnimation();
-                        mTutorialFragment.changeController(HOME_NAVIGATION_COMPLETE);
+                    case HOME_GESTURE_COMPLETED: {
+                        animateFakeTaskViewHome(finalVelocity, () ->
+                                mTutorialFragment.changeController(HOME_NAVIGATION_COMPLETE));
                         break;
+                    }
                     case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE:
                     case OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE:
                         showFeedback(R.string.home_gesture_feedback_swipe_too_far_from_edge);
                         break;
                     case OVERVIEW_GESTURE_COMPLETED:
-                        showFeedback(R.string.home_gesture_feedback_overview_detected);
+                        fadeOutFakeTaskView(true, () ->
+                                showFeedback(R.string.home_gesture_feedback_overview_detected));
                         break;
                     case HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION:
+                    case HOME_OR_OVERVIEW_CANCELLED:
+                        fadeOutFakeTaskView(false, null);
                         showFeedback(R.string.home_gesture_feedback_wrong_swipe_direction);
                         break;
                 }
@@ -112,4 +120,5 @@
                 break;
         }
     }
+
 }
diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
index 6d8caa2..4069c09 100644
--- a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
@@ -17,6 +17,7 @@
 
 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_GESTURE_COMPLETED;
 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_NOT_STARTED_TOO_FAR_FROM_EDGE;
+import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_OR_OVERVIEW_CANCELLED;
 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION;
 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.OVERVIEW_GESTURE_COMPLETED;
 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE;
@@ -24,19 +25,23 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.view.Display;
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.View;
 import android.view.View.OnTouchListener;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.ResourceUtils;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.util.NavBarPosition;
 import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
 
 /** Utility class to handle home gestures. */
-public class NavBarGestureHandler implements OnTouchListener {
+public class NavBarGestureHandler implements OnTouchListener,
+        TriggerSwipeUpTouchTracker.OnSwipeUpListener {
 
     private static final String LOG_TAG = "NavBarGestureHandler";
 
@@ -44,6 +49,7 @@
     private final TriggerSwipeUpTouchTracker mSwipeUpTouchTracker;
     private int mBottomGestureHeight;
     private boolean mTouchCameFromNavBar;
+    private float mDownY;
     private NavBarGestureAttemptCallback mGestureCallback;
 
     NavBarGestureHandler(Context context) {
@@ -55,10 +61,11 @@
             displayRotation = display.getRotation();
             display.getRealSize(mDisplaySize);
         }
+        mDownY = mDisplaySize.y;
         mSwipeUpTouchTracker =
                 new TriggerSwipeUpTouchTracker(context, true /*disableHorizontalSwipe*/,
                         new NavBarPosition(Mode.NO_BUTTON, displayRotation),
-                        null /*onInterceptTouch*/, this::onSwipeUp);
+                        null /*onInterceptTouch*/, this);
 
         final Resources resources = context.getResources();
         mBottomGestureHeight =
@@ -73,16 +80,26 @@
         mGestureCallback = null;
     }
 
-    private void onSwipeUp(boolean wasFling) {
+    @Override
+    public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
         if (mGestureCallback == null) {
             return;
         }
+        finalVelocity.set(finalVelocity.x / 1000, finalVelocity.y / 1000);
         if (mTouchCameFromNavBar) {
             mGestureCallback.onNavBarGestureAttempted(wasFling
-                    ? HOME_GESTURE_COMPLETED : OVERVIEW_GESTURE_COMPLETED);
+                    ? HOME_GESTURE_COMPLETED : OVERVIEW_GESTURE_COMPLETED, finalVelocity);
         } else {
             mGestureCallback.onNavBarGestureAttempted(wasFling
-                    ? HOME_NOT_STARTED_TOO_FAR_FROM_EDGE : OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE);
+                    ? HOME_NOT_STARTED_TOO_FAR_FROM_EDGE : OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE,
+                    finalVelocity);
+        }
+    }
+
+    @Override
+    public void onSwipeUpCancelled() {
+        if (mGestureCallback != null) {
+            mGestureCallback.onNavBarGestureAttempted(HOME_OR_OVERVIEW_CANCELLED, new PointF());
         }
     }
 
@@ -91,15 +108,22 @@
         int action = motionEvent.getAction();
         boolean intercepted = mSwipeUpTouchTracker.interceptedTouch();
         if (action == MotionEvent.ACTION_DOWN) {
-            mTouchCameFromNavBar = motionEvent.getRawY() >= mDisplaySize.y - mBottomGestureHeight;
+            mDownY = motionEvent.getY();
+            mTouchCameFromNavBar = mDownY >= mDisplaySize.y - mBottomGestureHeight;
+            if (!mTouchCameFromNavBar) {
+                mGestureCallback.setNavBarGestureProgress(null);
+            }
             mSwipeUpTouchTracker.init();
         } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
             if (mGestureCallback != null && !intercepted && mTouchCameFromNavBar) {
                 mGestureCallback.onNavBarGestureAttempted(
-                        HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION);
+                        HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION, new PointF());
                 intercepted = true;
             }
         }
+        if (mTouchCameFromNavBar && mGestureCallback != null) {
+            mGestureCallback.setNavBarGestureProgress(motionEvent.getY() - mDownY);
+        }
         mSwipeUpTouchTracker.onMotionEvent(motionEvent);
         return intercepted;
     }
@@ -110,12 +134,16 @@
         OVERVIEW_GESTURE_COMPLETED,
         HOME_NOT_STARTED_TOO_FAR_FROM_EDGE,
         OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE,
-        HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION  // Side swipe on nav bar.
+        HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION,  // Side swipe on nav bar.
+        HOME_OR_OVERVIEW_CANCELLED
     }
 
     /** Callback to let the UI react to attempted nav bar gestures. */
     interface NavBarGestureAttemptCallback {
         /** Called whenever any touch is completed. */
-        void onNavBarGestureAttempted(NavBarGestureResult result);
+        void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity);
+
+        /** Indicates how far a touch originating in the nav bar has moved from the nav bar. */
+        void setNavBarGestureProgress(@Nullable Float displacement);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
new file mode 100644
index 0000000..c636eba
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2020 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.interaction;
+
+import static com.android.quickstep.interaction.TutorialController.TutorialType.OVERVIEW_NAVIGATION_COMPLETE;
+
+import android.annotation.TargetApi;
+import android.graphics.PointF;
+import android.os.Build;
+import android.view.View;
+
+import com.android.launcher3.R;
+import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
+import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult;
+
+/** A {@link TutorialController} for the Overview tutorial. */
+@TargetApi(Build.VERSION_CODES.R)
+final class OverviewGestureTutorialController extends SwipeUpGestureTutorialController {
+
+    OverviewGestureTutorialController(OverviewGestureTutorialFragment fragment,
+            TutorialType tutorialType) {
+        super(fragment, tutorialType);
+    }
+
+    @Override
+    Integer getTitleStringId() {
+        switch (mTutorialType) {
+            case OVERVIEW_NAVIGATION:
+                return R.string.overview_gesture_tutorial_playground_title;
+            case OVERVIEW_NAVIGATION_COMPLETE:
+                return R.string.gesture_tutorial_confirm_title;
+        }
+        return null;
+    }
+
+    @Override
+    Integer getSubtitleStringId() {
+        if (mTutorialType == TutorialType.OVERVIEW_NAVIGATION) {
+            return R.string.overview_gesture_tutorial_playground_subtitle;
+        }
+        return null;
+    }
+
+    @Override
+    Integer getActionButtonStringId() {
+        if (mTutorialType == OVERVIEW_NAVIGATION_COMPLETE) {
+            return R.string.gesture_tutorial_action_button_label_done;
+        }
+        return null;
+    }
+
+    @Override
+    void onActionButtonClicked(View button) {
+        mTutorialFragment.closeTutorial();
+    }
+
+    @Override
+    public void onBackGestureAttempted(BackGestureResult result) {
+        switch (mTutorialType) {
+            case OVERVIEW_NAVIGATION:
+                switch (result) {
+                    case BACK_COMPLETED_FROM_LEFT:
+                    case BACK_COMPLETED_FROM_RIGHT:
+                    case BACK_CANCELLED_FROM_LEFT:
+                    case BACK_CANCELLED_FROM_RIGHT:
+                        showFeedback(R.string.overview_gesture_feedback_swipe_too_far_from_edge);
+                        break;
+                }
+                break;
+            case OVERVIEW_NAVIGATION_COMPLETE:
+                if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT
+                        || result == BackGestureResult.BACK_COMPLETED_FROM_RIGHT) {
+                    mTutorialFragment.closeTutorial();
+                }
+                break;
+        }
+    }
+
+    @Override
+    public void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity) {
+        switch (mTutorialType) {
+            case OVERVIEW_NAVIGATION:
+                switch (result) {
+                    case HOME_GESTURE_COMPLETED: {
+                        animateFakeTaskViewHome(finalVelocity, () ->
+                                showFeedback(R.string.overview_gesture_feedback_home_detected));
+                        break;
+                    }
+                    case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE:
+                    case OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE:
+                        showFeedback(R.string.overview_gesture_feedback_swipe_too_far_from_edge);
+                        break;
+                    case OVERVIEW_GESTURE_COMPLETED:
+                        fadeOutFakeTaskView(true, () ->
+                                mTutorialFragment.changeController(OVERVIEW_NAVIGATION_COMPLETE));
+                        break;
+                    case HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION:
+                    case HOME_OR_OVERVIEW_CANCELLED:
+                        fadeOutFakeTaskView(false, null);
+                        showFeedback(R.string.overview_gesture_feedback_wrong_swipe_direction);
+                        break;
+                }
+                break;
+            case OVERVIEW_NAVIGATION_COMPLETE:
+                if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) {
+                    mTutorialFragment.closeTutorial();
+                }
+                break;
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java
new file mode 100644
index 0000000..3357b70
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 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.interaction;
+
+import com.android.launcher3.R;
+import com.android.quickstep.interaction.TutorialController.TutorialType;
+
+/** Shows the Overview gesture interactive tutorial. */
+public class OverviewGestureTutorialFragment extends TutorialFragment {
+    @Override
+    int getHandAnimationResId() {
+        return R.drawable.overview_gesture;
+    }
+
+    @Override
+    TutorialController createController(TutorialType type) {
+        return new OverviewGestureTutorialController(this, type);
+    }
+
+    @Override
+    Class<? extends TutorialController> getControllerClass() {
+        return OverviewGestureTutorialController.class;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
new file mode 100644
index 0000000..14e00dc
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2020 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.interaction;
+
+import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.quickstep.BaseSwipeUpHandlerV2.MAX_SWIPE_DURATION;
+import static com.android.quickstep.interaction.TutorialController.TutorialType.HOME_NAVIGATION_COMPLETE;
+import static com.android.quickstep.interaction.TutorialController.TutorialType.OVERVIEW_NAVIGATION_COMPLETE;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Insets;
+import android.graphics.Outline;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Build;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.quickstep.AnimatedFloat;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.OverviewComponentObserver;
+import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.SwipeUpAnimationLogic;
+import com.android.quickstep.SwipeUpAnimationLogic.RunningWindowAnim;
+import com.android.quickstep.util.RectFSpringAnim;
+import com.android.quickstep.util.TransformParams;
+import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
+
+@TargetApi(Build.VERSION_CODES.R)
+abstract class SwipeUpGestureTutorialController extends TutorialController {
+    private final ViewSwipeUpAnimation mViewSwipeUpAnimation;
+    private float mFakeTaskViewRadius;
+    private Rect mFakeTaskViewRect = new Rect();
+    private RunningWindowAnim mRunningWindowAnim;
+
+    SwipeUpGestureTutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) {
+        super(tutorialFragment, tutorialType);
+        RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(mContext);
+        OverviewComponentObserver observer = new OverviewComponentObserver(mContext, deviceState);
+        mViewSwipeUpAnimation = new ViewSwipeUpAnimation(mContext, deviceState,
+                new GestureState(observer, -1));
+        observer.onDestroy();
+        deviceState.destroy();
+
+        DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext)
+                .getDeviceProfile(mContext)
+                .copy(mContext);
+        Insets insets = mContext.getSystemService(WindowManager.class)
+                .getCurrentWindowMetrics()
+                .getWindowInsets()
+                .getInsets(WindowInsets.Type.systemBars());
+        dp.updateInsets(new Rect(insets.left, insets.top, insets.right, insets.bottom));
+        mViewSwipeUpAnimation.initDp(dp);
+
+        mFakeTaskViewRadius = QuickStepContract.getWindowCornerRadius(mContext.getResources());
+        mFakeTaskView.setClipToOutline(true);
+        mFakeTaskView.setOutlineProvider(new ViewOutlineProvider() {
+            @Override
+            public void getOutline(View view, Outline outline) {
+                outline.setRoundRect(mFakeTaskViewRect, mFakeTaskViewRadius);
+            }
+        });
+    }
+
+    private void cancelRunningAnimation() {
+        if (mRunningWindowAnim != null) {
+            mRunningWindowAnim.cancel();
+        }
+        mRunningWindowAnim = null;
+    }
+
+    /** Fades the task view, optionally after animating to a fake Overview. */
+    void fadeOutFakeTaskView(boolean toOverviewFirst, @Nullable Runnable onEndRunnable) {
+        hideFeedback();
+        hideHandCoachingAnimation();
+        cancelRunningAnimation();
+        PendingAnimation anim = new PendingAnimation(300);
+        AnimatorListenerAdapter resetTaskView = new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation, boolean isReverse) {
+                mFakeTaskView.setVisibility(View.INVISIBLE);
+                mFakeTaskView.setAlpha(1);
+                mRunningWindowAnim = null;
+            }
+        };
+        if (toOverviewFirst) {
+            anim.setFloat(mViewSwipeUpAnimation.getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL);
+            anim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation, boolean isReverse) {
+                    PendingAnimation fadeAnim = new PendingAnimation(300);
+                    fadeAnim.setViewAlpha(mFakeTaskView, 0, ACCEL);
+                    fadeAnim.addListener(resetTaskView);
+                    AnimatorSet animset = fadeAnim.buildAnim();
+                    animset.setStartDelay(100);
+                    animset.start();
+                    mRunningWindowAnim = RunningWindowAnim.wrap(animset);
+                }
+            });
+        } else {
+            anim.setViewAlpha(mFakeTaskView, 0, ACCEL);
+            anim.addListener(resetTaskView);
+        }
+        if (onEndRunnable != null) {
+            anim.addListener(AnimationSuccessListener.forRunnable(onEndRunnable));
+        }
+        AnimatorSet animset = anim.buildAnim();
+        animset.start();
+        mRunningWindowAnim = RunningWindowAnim.wrap(animset);
+    }
+
+    void animateFakeTaskViewHome(PointF finalVelocity, @Nullable Runnable onEndRunnable) {
+        hideFeedback();
+        hideHandCoachingAnimation();
+        cancelRunningAnimation();
+        RectFSpringAnim rectAnim =
+                mViewSwipeUpAnimation.handleSwipeUpToHome(finalVelocity);
+        // After home animation finishes, fade out and run onEndRunnable.
+        rectAnim.addAnimatorListener(AnimationSuccessListener.forRunnable(
+                () -> fadeOutFakeTaskView(false, onEndRunnable)));
+        mRunningWindowAnim = RunningWindowAnim.wrap(rectAnim);
+    }
+
+    @Override
+    public void setNavBarGestureProgress(@Nullable Float displacement) {
+        if (displacement == null || mTutorialType == HOME_NAVIGATION_COMPLETE
+                || mTutorialType == OVERVIEW_NAVIGATION_COMPLETE) {
+            mFakeTaskView.setVisibility(View.INVISIBLE);
+        } else {
+            mFakeTaskView.setVisibility(View.VISIBLE);
+            if (mRunningWindowAnim == null) {
+                mViewSwipeUpAnimation.updateDisplacement(displacement);
+            }
+        }
+    }
+
+    class ViewSwipeUpAnimation extends SwipeUpAnimationLogic {
+
+        ViewSwipeUpAnimation(Context context, RecentsAnimationDeviceState deviceState,
+                             GestureState gestureState) {
+            super(context, deviceState, gestureState, new FakeTransformParams());
+        }
+
+        void initDp(DeviceProfile dp) {
+            initTransitionEndpoints(dp);
+            mTaskViewSimulator.setPreviewBounds(
+                    new Rect(0, 0, dp.widthPx, dp.heightPx), dp.getInsets());
+        }
+
+        @Override
+        public void updateFinalShift() {
+            float progress = mCurrentShift.value / mDragLengthFactor;
+            mWindowTransitionController.setPlayFraction(progress);
+            mTaskViewSimulator.apply(mTransformParams);
+        }
+
+        AnimatedFloat getCurrentShift() {
+            return mCurrentShift;
+        }
+
+        RectFSpringAnim handleSwipeUpToHome(PointF velocity) {
+            PointF velocityPxPerMs = new PointF(velocity.x, velocity.y);
+            float currentShift = mCurrentShift.value;
+            final float startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y
+                    * getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor);
+            float distanceToTravel = (1 - currentShift) * mTransitionDragLength;
+
+            // we want the page's snap velocity to approximately match the velocity at
+            // which the user flings, so we scale the duration by a value near to the
+            // derivative of the scroll interpolator at zero, ie. 2.
+            long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y));
+            long duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
+            HomeAnimationFactory homeAnimFactory = new HomeAnimationFactory(null) {
+                @Override
+                public AnimatorPlaybackController createActivityAnimationToHome() {
+                    return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
+                }
+
+                @NonNull
+                @Override
+                public RectF getWindowTargetRect() {
+                    int fakeHomeIconSizePx = mDp.allAppsIconSizePx;
+                    int fakeHomeIconLeft = (mDp.widthPx - fakeHomeIconSizePx) / 2;
+                    int fakeHomeIconTop = mDp.heightPx - (mDp.allAppsCellHeightPx * 3);
+                    return new RectF(fakeHomeIconLeft, fakeHomeIconTop,
+                            fakeHomeIconLeft + fakeHomeIconSizePx,
+                            fakeHomeIconTop + fakeHomeIconSizePx);
+                }
+            };
+            RectFSpringAnim windowAnim = createWindowAnimationToHome(startShift, homeAnimFactory);
+            windowAnim.start(mContext, velocityPxPerMs);
+            return windowAnim;
+        }
+    }
+
+    private class FakeTransformParams extends TransformParams {
+
+        @Override
+        public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) {
+            SurfaceParams.Builder builder = new SurfaceParams.Builder((SurfaceControl) null);
+            proxy.onBuildTargetParams(builder, null, this);
+            return new SurfaceParams[] {builder.build()};
+        }
+
+        @Override
+        public void applySurfaceParams(SurfaceParams[] params) {
+            SurfaceParams p = params[0];
+            mFakeTaskView.setAnimationMatrix(p.matrix);
+            mFakeTaskViewRect.set(p.windowCrop);
+            mFakeTaskViewRadius = p.cornerRadius;
+            mFakeTaskView.invalidateOutline();
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index 1e29f44..511c8b6 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -15,6 +15,7 @@
  */
 package com.android.quickstep.interaction;
 
+import android.content.Context;
 import android.graphics.drawable.RippleDrawable;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -39,11 +40,13 @@
 
     final TutorialFragment mTutorialFragment;
     TutorialType mTutorialType;
+    final Context mContext;
 
     final ImageButton mCloseButton;
     final TextView mTitleTextView;
     final TextView mSubtitleTextView;
     final TextView mFeedbackView;
+    final View mFakeTaskView;
     final View mRippleView;
     final RippleDrawable mRippleDrawable;
     final TutorialHandAnimation mHandCoachingAnimation;
@@ -55,6 +58,7 @@
     TutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) {
         mTutorialFragment = tutorialFragment;
         mTutorialType = tutorialType;
+        mContext = mTutorialFragment.getContext();
 
         View rootView = tutorialFragment.getRootView();
         mCloseButton = rootView.findViewById(R.id.gesture_tutorial_fragment_close_button);
@@ -62,6 +66,7 @@
         mTitleTextView = rootView.findViewById(R.id.gesture_tutorial_fragment_title_view);
         mSubtitleTextView = rootView.findViewById(R.id.gesture_tutorial_fragment_subtitle_view);
         mFeedbackView = rootView.findViewById(R.id.gesture_tutorial_fragment_feedback_view);
+        mFakeTaskView = rootView.findViewById(R.id.gesture_tutorial_fake_task_view);
         mRippleView = rootView.findViewById(R.id.gesture_tutorial_ripple_view);
         mRippleDrawable = (RippleDrawable) mRippleView.getBackground();
         mHandCoachingAnimation = tutorialFragment.getHandAnimation();
@@ -135,6 +140,9 @@
     void onActionTextButtonClicked(View button) {}
 
     void showHandCoachingAnimation() {
+        if (isComplete()) {
+            return;
+        }
         mHandCoachingAnimation.startLoopedAnimation(mTutorialType);
     }
 
@@ -148,6 +156,12 @@
         hideFeedback();
         updateTitles();
         updateActionButtons();
+
+        if (isComplete()) {
+            hideHandCoachingAnimation();
+        } else {
+            showHandCoachingAnimation();
+        }
     }
 
     private void updateTitles() {
@@ -185,12 +199,20 @@
         button.setOnClickListener(listener);
     }
 
+    private boolean isComplete() {
+        return mTutorialType == TutorialType.BACK_NAVIGATION_COMPLETE
+                || mTutorialType == TutorialType.HOME_NAVIGATION_COMPLETE
+                || mTutorialType == TutorialType.OVERVIEW_NAVIGATION_COMPLETE;
+    }
+
     /** Denotes the type of the tutorial. */
     enum TutorialType {
         RIGHT_EDGE_BACK_NAVIGATION,
         LEFT_EDGE_BACK_NAVIGATION,
         BACK_NAVIGATION_COMPLETE,
         HOME_NAVIGATION,
-        HOME_NAVIGATION_COMPLETE
+        HOME_NAVIGATION_COMPLETE,
+        OVERVIEW_NAVIGATION,
+        OVERVIEW_NAVIGATION_COMPLETE
     }
 }
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
index 44c1a5d..da6815d 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
@@ -37,11 +37,6 @@
 abstract class TutorialFragment extends Fragment implements OnTouchListener {
 
     private static final String LOG_TAG = "TutorialFragment";
-    private static final String SYSTEM_NAVIGATION_SETTING_INTENT =
-            "#Intent;action=com.android.settings.SEARCH_RESULT_TRAMPOLINE;S"
-                    + ".:settings:fragment_args_key=gesture_system_navigation_input_summary;S"
-                    + ".:settings:show_fragment=com.android.settings.gestures"
-                    + ".SystemNavigationGestureSettings;end";
     static final String KEY_TUTORIAL_TYPE = "tutorial_type";
 
     TutorialType mTutorialType;
@@ -73,6 +68,9 @@
             case HOME_NAVIGATION:
             case HOME_NAVIGATION_COMPLETE:
                 return new HomeGestureTutorialFragment();
+            case OVERVIEW_NAVIGATION:
+            case OVERVIEW_NAVIGATION_COMPLETE:
+                return new OverviewGestureTutorialFragment();
             default:
                 Log.e(LOG_TAG, "Failed to find an appropriate fragment for " + tutorialType.name());
         }
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 69812b6..d4d46fb 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -65,10 +65,10 @@
     }
 
     /**
-     * Logs a {@link LauncherEvent}.
+     * Logs a {@link EventEnum}.
      */
     @Override
-    public void log(LauncherEvent event) {
+    public void log(EventEnum event) {
         log(event, DEFAULT_INSTANCE_ID, LauncherAtom.ItemInfo.getDefaultInstance());
     }
 
@@ -76,7 +76,7 @@
      * Logs an event and accompanying {@link InstanceId}.
      */
     @Override
-    public void log(LauncherEvent event, InstanceId instanceId) {
+    public void log(EventEnum event, InstanceId instanceId) {
         log(event, instanceId, LauncherAtom.ItemInfo.getDefaultInstance());
     }
 
@@ -84,7 +84,7 @@
      * Logs an event and accompanying {@link ItemInfo}.
      */
     @Override
-    public void log(LauncherEvent event, @Nullable LauncherAtom.ItemInfo info) {
+    public void log(EventEnum event, @Nullable LauncherAtom.ItemInfo info) {
         log(event, DEFAULT_INSTANCE_ID, info);
     }
 
@@ -92,7 +92,7 @@
      * Logs an event and accompanying {@link InstanceId} and {@link LauncherAtom.ItemInfo}.
      */
     @Override
-    public void log(LauncherEvent event, InstanceId instanceId,
+    public void log(EventEnum event, InstanceId instanceId,
             @Nullable LauncherAtom.ItemInfo info) {
         logInternal(event, instanceId, info,
                 LAUNCHER_UICHANGED__DST_STATE__HOME,
@@ -102,14 +102,17 @@
     /**
      * Logs an event and accompanying {@link InstanceId} and {@link LauncherAtom.ItemInfo}.
      */
-    private void logInternal(LauncherEvent event, InstanceId instanceId,
+    private void logInternal(EventEnum event, InstanceId instanceId,
             @Nullable LauncherAtom.ItemInfo info, int startState, int endState) {
         info = info == null ? LauncherAtom.ItemInfo.getDefaultInstance() : info;
 
         if (IS_VERBOSE) {
+            String name = (event instanceof LauncherEvent) ? ((LauncherEvent) event).name() :
+                    event.getId() + "";
+
             Log.d(TAG, instanceId == DEFAULT_INSTANCE_ID
-                    ? String.format("\n%s\n%s", event.name(), info)
-                    : String.format("%s(InstanceId:%s)\n%s", event.name(), instanceId, info));
+                    ? String.format("\n%s\n%s", name, info)
+                    : String.format("%s(InstanceId:%s)\n%s", name, instanceId, info));
         }
 
         if (!Utilities.ATLEAST_R) {
diff --git a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
index f5fbf28..0b48a8b 100644
--- a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
+++ b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
@@ -39,6 +39,8 @@
 
 import com.android.launcher3.BuildConfig;
 import com.android.quickstep.SystemUiProxy;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.utilities.BitmapUtil;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -59,8 +61,9 @@
      */
     public static void saveScreenshot(SystemUiProxy systemUiProxy, Bitmap screenshot,
             Rect screenshotBounds,
-            Insets visibleInsets, int taskId) {
-        systemUiProxy.handleImageAsScreenshot(screenshot, screenshotBounds, visibleInsets, taskId);
+            Insets visibleInsets, Task.TaskKey task) {
+        systemUiProxy.handleImageBundleAsScreenshot(BitmapUtil.hardwareBitmapToBundle(screenshot),
+                screenshotBounds, visibleInsets, task);
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index 5745990..498c232 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -208,7 +208,7 @@
         mDisplayRotation = displayRotation;
         mTouchRotation = touchRotation;
 
-        if (mLauncherRotation == mTouchRotation) {
+        if (mLauncherRotation == mTouchRotation || canLauncherRotate()) {
             mOrientationHandler = PagedOrientationHandler.HOME_ROTATED;
             if (DEBUG) {
                 Log.d(TAG, "current RecentsOrientedState: " + this);
@@ -240,7 +240,7 @@
 
     private void setFlag(int mask, boolean enabled) {
         boolean wasRotationEnabled = !TestProtocol.sDisableSensorRotation
-                && mFlags == VALUE_ROTATION_WATCHER_ENABLED;
+                && (mFlags & VALUE_ROTATION_WATCHER_ENABLED) == VALUE_ROTATION_WATCHER_ENABLED;
         if (enabled) {
             mFlags |= mask;
         } else {
@@ -248,7 +248,7 @@
         }
 
         boolean isRotationEnabled = !TestProtocol.sDisableSensorRotation
-                && mFlags == VALUE_ROTATION_WATCHER_ENABLED;
+                && (mFlags & VALUE_ROTATION_WATCHER_ENABLED) == VALUE_ROTATION_WATCHER_ENABLED;
         if (wasRotationEnabled != isRotationEnabled) {
             UI_HELPER_EXECUTOR.execute(() -> {
                 if (isRotationEnabled) {
diff --git a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
index 34eb7f8..79ddf7a 100644
--- a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
+++ b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
@@ -58,9 +58,9 @@
     }
 
     @Test
-    public void onTaskRemoved_reloadsAllTasks() {
+    public void onTaskRemoved_doesNotFetchTasks() {
         mRecentTasksList.onTaskRemoved(0);
-        verify(mockActivityManagerWrapper, times(1))
+        verify(mockActivityManagerWrapper, times(0))
                 .getRecentTasks(anyInt(), anyInt());
     }
 
@@ -77,7 +77,7 @@
         when(mockActivityManagerWrapper.getRecentTasks(anyInt(), anyInt()))
                 .thenReturn(Collections.singletonList(recentTaskInfo));
 
-        List<Task> taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, true);
+        List<Task> taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, -1, true);
 
         assertEquals(1, taskList.size());
         assertNull(taskList.get(0).taskDescription.getLabel());
@@ -91,7 +91,7 @@
         when(mockActivityManagerWrapper.getRecentTasks(anyInt(), anyInt()))
                 .thenReturn(Collections.singletonList(recentTaskInfo));
 
-        List<Task> taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, false);
+        List<Task> taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, -1, false);
 
         assertEquals(1, taskList.size());
         assertEquals(taskDescription, taskList.get(0).taskDescription.getLabel());
diff --git a/res/drawable/gesture_tutorial_ripple.xml b/res/drawable/gesture_tutorial_ripple.xml
index ca45662..782af33 100644
--- a/res/drawable/gesture_tutorial_ripple.xml
+++ b/res/drawable/gesture_tutorial_ripple.xml
@@ -1,6 +1,3 @@
 <?xml version="1.0" encoding="utf-8"?>
 <ripple android:color="@color/gesture_tutorial_ripple_color"
-    xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:id="@android:id/mask"
-        android:drawable="@color/gesture_tutorial_background_color" />
-</ripple>
\ No newline at end of file
+    xmlns:android="http://schemas.android.com/apk/res/android"/>
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index c9c893e..043ad9a 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -38,13 +38,8 @@
     <color name="all_apps_bg_hand_fill">#E5E5E5</color>
     <color name="all_apps_bg_hand_fill_dark">#9AA0A6</color>
 
-    <color name="gesture_tutorial_background_color">#FFFFFFFF</color>
-    <color name="gesture_tutorial_title_color">#FF000000</color>
-    <color name="gesture_tutorial_subtitle_color">#99000000</color> <!-- 60% black -->
-    <color name="gesture_tutorial_feedback_color">#FF000000</color>
     <color name="gesture_tutorial_ripple_color">#A0C2F9</color> <!-- Light Blue -->
+    <color name="gesture_tutorial_fake_task_view_color">#6DA1FF</color> <!-- Light Blue -->
     <color name="gesture_tutorial_action_button_label_color">#FFFFFFFF</color>
     <color name="gesture_tutorial_primary_color">#1A73E8</color> <!-- Blue -->
-
-
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 26b7205..e470c42 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -81,7 +81,6 @@
         <item name="folderFillColor">#CDFFFFFF</item>
         <item name="folderIconBorderColor">#FF80868B</item>
         <item name="folderTextColor">?attr/workspaceTextColor</item>
-
     </style>
 
     <style name="LauncherTheme.Dark" parent="@style/LauncherTheme">
diff --git a/robolectric_tests/config/robolectric.properties b/robolectric_tests/config/robolectric.properties
index c162255..b171712 100644
--- a/robolectric_tests/config/robolectric.properties
+++ b/robolectric_tests/config/robolectric.properties
@@ -13,6 +13,6 @@
     com.android.launcher3.shadows.ShadowLooperExecutor \
     com.android.launcher3.shadows.ShadowMainThreadInitializedObject \
     com.android.launcher3.shadows.ShadowOverrides \
-    com.android.launcher3.shadows.ShadowSyncRtSurfaceTransactionApplierCompat \
+    com.android.launcher3.shadows.ShadowSurfaceTransactionApplier \
 
 application=com.android.launcher3.util.LauncherTestApplication
\ No newline at end of file
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowSyncRtSurfaceTransactionApplierCompat.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowSurfaceTransactionApplier.java
similarity index 83%
rename from robolectric_tests/src/com/android/launcher3/shadows/ShadowSyncRtSurfaceTransactionApplierCompat.java
rename to robolectric_tests/src/com/android/launcher3/shadows/ShadowSurfaceTransactionApplier.java
index 238926c..a9f2f27 100644
--- a/robolectric_tests/src/com/android/launcher3/shadows/ShadowSyncRtSurfaceTransactionApplierCompat.java
+++ b/robolectric_tests/src/com/android/launcher3/shadows/ShadowSurfaceTransactionApplier.java
@@ -26,11 +26,11 @@
 import org.robolectric.annotation.RealObject;
 
 /**
- * Shadow for SyncRtSurfaceTransactionApplierCompat to override default functionality
+ * Shadow for SurfaceTransactionApplier to override default functionality
  */
-@Implements(className = "com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat",
+@Implements(className = "com.android.quickstep.util.SurfaceTransactionApplier",
         isInAndroidSdk = false)
-public class ShadowSyncRtSurfaceTransactionApplierCompat {
+public class ShadowSurfaceTransactionApplier {
 
     @RealObject
     private Object mRealObject;
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 0ac2784..d1d5e26 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -24,6 +24,7 @@
 import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.content.res.Configuration;
+import android.graphics.Insets;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Bundle;
@@ -35,6 +36,8 @@
 import android.view.Display;
 import android.view.View;
 import android.view.View.OnClickListener;
+import android.view.WindowInsets.Type;
+import android.view.WindowMetrics;
 import android.widget.Toast;
 
 import androidx.annotation.Nullable;
@@ -51,6 +54,7 @@
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.TraceHelper;
+import com.android.launcher3.util.WindowBounds;
 
 /**
  * Extension of BaseActivity allowing support for drag-n-drop
@@ -272,15 +276,19 @@
 
     protected abstract void reapplyUi();
 
-    protected Rect getMultiWindowDisplaySize() {
+    protected WindowBounds getMultiWindowDisplaySize() {
         if (Utilities.ATLEAST_R) {
-            return new Rect(getWindowManager().getCurrentWindowMetrics().getBounds());
+            WindowMetrics wm = getWindowManager().getCurrentWindowMetrics();
+
+            Insets insets = wm.getWindowInsets().getInsets(Type.systemBars());
+            return new WindowBounds(wm.getBounds(),
+                    new Rect(insets.left, insets.top, insets.right, insets.bottom));
         }
         // Note: Calls to getSize() can't rely on our cached DefaultDisplay since it can return
         // the app window size
         Display display = getWindowManager().getDefaultDisplay();
         Point mwSize = new Point();
         display.getSize(mwSize);
-        return new Rect(0, 0, mwSize.x, mwSize.y);
+        return new WindowBounds(new Rect(0, 0, mwSize.x, mwSize.y), new Rect());
     }
 }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 51b21aa..72831f4 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -29,6 +29,7 @@
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.icons.IconNormalizer;
 import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.WindowBounds;
 
 public class DeviceProfile {
 
@@ -265,19 +266,16 @@
     /**
      * TODO: Move this to the builder as part of setMultiWindowMode
      */
-    public DeviceProfile getMultiWindowProfile(Context context, Rect windowPosition) {
+    public DeviceProfile getMultiWindowProfile(Context context, WindowBounds windowBounds) {
         // We take the minimum sizes of this profile and it's multi-window variant to ensure that
         // the system decor is always excluded.
-        Point mwSize = new Point(Math.min(availableWidthPx, windowPosition.width()),
-                Math.min(availableHeightPx, windowPosition.height()));
+        Point mwSize = new Point(Math.min(availableWidthPx, windowBounds.availableSize.x),
+                Math.min(availableHeightPx, windowBounds.availableSize.y));
 
-        // In multi-window mode, we can have widthPx = availableWidthPx
-        // and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles'
-        // widthPx and heightPx values where it's needed.
         DeviceProfile profile = toBuilder(context)
                 .setSizeRange(mwSize, mwSize)
-                .setSize(mwSize.x, mwSize.y)
-                .setWindowPosition(windowPosition.left, windowPosition.top)
+                .setSize(windowBounds.bounds.width(), windowBounds.bounds.height())
+                .setWindowPosition(windowBounds.bounds.left, windowBounds.bounds.top)
                 .setMultiWindowMode(true)
                 .build();
 
@@ -299,7 +297,7 @@
     }
 
     /**
-     * Inverse of {@link #getMultiWindowProfile(Context, Rect)}
+     * Inverse of {@link #getMultiWindowProfile(Context, WindowBounds)}
      * @return device profile corresponding to the current orientation in non multi-window mode.
      */
     public DeviceProfile getFullScreenProfile() {
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 8951674..60abc66 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -200,7 +200,7 @@
                 DefaultDisplay.INSTANCE.get(context).getInfo(),
                 getPredefinedDeviceProfiles(context, gridName));
 
-        Info myInfo = new Info(display);
+        Info myInfo = new Info(context, display);
         DisplayOption myDisplayOption = invDistWeightedInterpolate(
                 myInfo, getPredefinedDeviceProfiles(context, gridName));
 
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 48b97fa..e8b5568 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -411,6 +411,11 @@
                         Favorites.BACKUP_TABLE_NAME);
                 return null;
             }
+            case LauncherSettings.Settings.METHOD_REFRESH_HOTSEAT_RESTORE_TABLE: {
+                mOpenHelper.mHotseatRestoreTableExists = tableExists(
+                        mOpenHelper.getReadableDatabase(), Favorites.HYBRID_HOTSEAT_BACKUP_TABLE);
+                return null;
+            }
             case LauncherSettings.Settings.METHOD_RESTORE_BACKUP_TABLE: {
                 final long ts = System.currentTimeMillis();
                 if (ts - mLastRestoreTimestamp > RESTORE_BACKUP_TABLE_DELAY) {
@@ -609,6 +614,7 @@
         private int mMaxItemId = -1;
         private int mMaxScreenId = -1;
         private boolean mBackupTableExists;
+        private boolean mHotseatRestoreTableExists;
 
         static DatabaseHelper createDatabaseHelper(Context context, boolean forMigration) {
             return createDatabaseHelper(context, null, forMigration);
@@ -633,6 +639,8 @@
                 databaseHelper.mBackupTableExists = tableExists(
                         databaseHelper.getReadableDatabase(), Favorites.BACKUP_TABLE_NAME);
             }
+            databaseHelper.mHotseatRestoreTableExists = tableExists(
+                    databaseHelper.getReadableDatabase(), Favorites.HYBRID_HOTSEAT_BACKUP_TABLE);
 
             databaseHelper.initIds();
             return databaseHelper;
@@ -679,6 +687,10 @@
                 dropTable(db, Favorites.BACKUP_TABLE_NAME);
                 mBackupTableExists = false;
             }
+            if (mHotseatRestoreTableExists) {
+                dropTable(db, Favorites.HYBRID_HOTSEAT_BACKUP_TABLE);
+                mHotseatRestoreTableExists = false;
+            }
         }
 
         /**
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 6789072..2a5ee00 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -100,6 +100,11 @@
         public static final String BACKUP_TABLE_NAME = "favorites_bakup";
 
         /**
+         * Backup table created when user hotseat is moved to workspace for hybrid hotseat
+         */
+        public static final String HYBRID_HOTSEAT_BACKUP_TABLE = "hotseat_restore_backup";
+
+        /**
          * Temporary table used specifically for grid migrations during wallpaper preview
          */
         public static final String PREVIEW_TABLE_NAME = "favorites_preview";
@@ -154,9 +159,9 @@
         public static final int CONTAINER_HOTSEAT_PREDICTION = -103;
         public static final int CONTAINER_ALL_APPS = -104;
         public static final int CONTAINER_WIDGETS_TRAY = -105;
-
         // Represents search results view.
         public static final int CONTAINER_SEARCH_RESULTS = -106;
+        public static final int CONTAINER_SHORTCUTS = -107;
 
         public static final String containerToString(int container) {
             switch (container) {
@@ -166,6 +171,7 @@
                 case CONTAINER_ALL_APPS: return "all_apps";
                 case CONTAINER_WIDGETS_TRAY: return "widgets_tray";
                 case CONTAINER_SEARCH_RESULTS: return "search_result";
+                case CONTAINER_SHORTCUTS: return "shortcuts";
                 default: return String.valueOf(container);
             }
         }
@@ -332,6 +338,8 @@
 
         public static final String METHOD_REFRESH_BACKUP_TABLE = "refresh_backup_table";
 
+        public static final String METHOD_REFRESH_HOTSEAT_RESTORE_TABLE = "restore_hotseat_table";
+
         public static final String METHOD_RESTORE_BACKUP_TABLE = "restore_backup_table";
 
         public static final String METHOD_UPDATE_CURRENT_OPEN_HELPER = "update_current_open_helper";
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index aa9edda..99ed0ad 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -6,6 +6,8 @@
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
@@ -227,7 +229,9 @@
         setter.setInt(mScrimView, ScrimView.DRAG_HANDLE_ALPHA,
                 (visibleElements & VERTICAL_SWIPE_INDICATOR) != 0 ? 255 : 0, allAppsFade);
 
-        setter.setViewAlpha(mAppsView, hasAnyVisibleItem ? 1 : 0, allAppsFade);
+        // Set visibility of the container at the very beginning or end of the transition.
+        setter.setViewAlpha(mAppsView, hasAnyVisibleItem ? 1 : 0,
+                hasAnyVisibleItem ? INSTANT : FINAL_FRAME);
     }
 
     public AnimatorListenerAdapter getProgressAnimatorListener() {
diff --git a/src/com/android/launcher3/allapps/SearchUiManager.java b/src/com/android/launcher3/allapps/SearchUiManager.java
index 34bf636..7d5363f 100644
--- a/src/com/android/launcher3/allapps/SearchUiManager.java
+++ b/src/com/android/launcher3/allapps/SearchUiManager.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.allapps;
 
+import static com.android.launcher3.LauncherState.ALL_APPS_HEADER;
+
 import android.graphics.Rect;
 import android.view.KeyEvent;
 import android.view.animation.Interpolator;
@@ -57,6 +59,13 @@
             Interpolator interpolator);
 
     /**
+     * Returns true if the QSB should be visible for the given set of visible elements
+     */
+    default boolean isQsbVisible(int visibleElements) {
+        return (visibleElements & ALL_APPS_HEADER) != 0;
+    }
+
+    /**
      * Called to control how the search UI result should be handled.
      *
      * @param isEnabled when {@code true}, the search is all handled inside AOSP
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index e72e1a8..356c52c 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -19,7 +19,6 @@
 import static android.view.View.MeasureSpec.getSize;
 import static android.view.View.MeasureSpec.makeMeasureSpec;
 
-import static com.android.launcher3.LauncherState.ALL_APPS_HEADER;
 import static com.android.launcher3.Utilities.prefixTextWithIcon;
 import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
 
@@ -213,7 +212,7 @@
     @Override
     public void setContentVisibility(int visibleElements, PropertySetter setter,
             Interpolator interpolator) {
-        setter.setViewAlpha(this, (visibleElements & ALL_APPS_HEADER) != 0 ? 1 : 0, interpolator);
+        setter.setViewAlpha(this, isQsbVisible(visibleElements) ? 1 : 0, interpolator);
     }
 
     @Override
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index fccc120..860cceb 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -61,6 +61,11 @@
     public static final Interpolator EXAGGERATED_EASE;
 
     public static final Interpolator INSTANT = t -> 1;
+    /**
+     * All values of t map to 0 until t == 1. This is primarily useful for setting view visibility,
+     * which should only happen at the very end of the animation (when it's already hidden).
+     */
+    public static final Interpolator FINAL_FRAME = t -> t < 1 ? 0 : 1;
 
     private static final int MIN_SETTLE_DURATION = 200;
     private static final float OVERSHOOT_FACTOR = 0.9f;
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 978dd52..118ce0c 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -103,7 +103,7 @@
             "Adds localized title and keyword search and ranking");
 
     public static final BooleanFlag ENABLE_PREDICTION_DISMISS = getDebugFlag(
-            "ENABLE_PREDICTION_DISMISS", false, "Allow option to dimiss apps from predicted list");
+            "ENABLE_PREDICTION_DISMISS", true, "Allow option to dimiss apps from predicted list");
 
     public static final BooleanFlag ENABLE_QUICK_CAPTURE_GESTURE = getDebugFlag(
             "ENABLE_QUICK_CAPTURE_GESTURE", true, "Swipe from right to left to quick capture");
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index f7fe535..fdf0ea4 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -466,6 +466,7 @@
                 if (!isEmpty(firstLabel)) {
                     mFolderName.setHint("");
                     mFolderName.setText(firstLabel);
+                    mFolderName.selectAll();
                 }
             }
             mFolderName.showKeyboard();
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 4c2c79d..85013d7 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -32,7 +32,7 @@
  */
 public class StatsLogManager implements ResourceBasedOverride {
 
-    interface EventEnum {
+    public interface EventEnum {
         int getId();
     }
 
@@ -63,6 +63,18 @@
                 + "new/same value.")
         LAUNCHER_FOLDER_LABEL_UPDATED(460),
 
+        @UiEvent(doc = "User long pressed on the workspace empty space.")
+        LAUNCHER_WORKSPACE_LONGPRESS(461),
+
+        @UiEvent(doc = "User tapped or long pressed on a wallpaper icon inside launcher settings.")
+        LAUNCHER_WALLPAPER_BUTTON_TAP_OR_LONGPRESS(462),
+
+        @UiEvent(doc = "User tapped or long pressed on settings icon inside launcher settings.")
+        LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS(463),
+
+        @UiEvent(doc = "User tapped or long pressed on widget tray icon inside launcher settings.")
+        LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS(464),
+
         @UiEvent(doc = "A dragged item is dropped on 'Remove' button in the target bar")
         LAUNCHER_ITEM_DROPPED_ON_REMOVE(465),
 
@@ -82,7 +94,25 @@
 
         @UiEvent(doc = "User cancelled uninstalling the package after dropping on "
                 + "the icon onto 'Uninstall' button in the target bar")
-        LAUNCHER_ITEM_UNINSTALL_CANCELLED(470);
+        LAUNCHER_ITEM_UNINSTALL_CANCELLED(470),
+
+        @UiEvent(doc = "User tapped or long pressed on the task icon(aka package icon) "
+                + "from overview to open task menu.")
+        LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS(517),
+
+        @UiEvent(doc = "User opened package specific widgets list by tapping on widgets system "
+                + "shortcut within longpress popup window.")
+        LAUNCHER_SYSTEM_SHORTCUT_WIDGETS_TAP(514),
+
+        @UiEvent(doc = "User opened app info of the package by tapping on appinfo system shortcut "
+                + "within longpress popup window.")
+        LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP(515),
+
+        @UiEvent(doc = "User tapped on split screen icon on a task menu.")
+        LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP(518),
+
+        @UiEvent(doc = "User tapped on free form icon on a task menu.")
+        LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP(519);
         // ADD MORE
 
         private final int mId;
@@ -113,27 +143,27 @@
     }
 
     /**
-     * Logs a {@link LauncherEvent}.
+     * Logs a {@link EventEnum}.
      */
-    public void log(LauncherEvent event) {
+    public void log(EventEnum event) {
     }
 
     /**
      * Logs an event and accompanying {@link InstanceId}.
      */
-    public void log(LauncherEvent event, InstanceId instanceId) {
+    public void log(EventEnum event, InstanceId instanceId) {
     }
 
     /**
      * Logs an event and accompanying {@link ItemInfo}.
      */
-    public void log(LauncherEvent event, @Nullable ItemInfo info) {
+    public void log(EventEnum event, @Nullable ItemInfo itemInfo) {
     }
 
     /**
      * Logs an event and accompanying {@link InstanceId} and {@link ItemInfo}.
      */
-    public void log(LauncherEvent event, InstanceId instanceId, @Nullable ItemInfo info) {
+    public void log(EventEnum event, InstanceId instanceId, @Nullable ItemInfo itemInfo) {
     }
 
     /**
diff --git a/src/com/android/launcher3/model/GridBackupTable.java b/src/com/android/launcher3/model/GridBackupTable.java
index 4a1bc4d..acfc339 100644
--- a/src/com/android/launcher3/model/GridBackupTable.java
+++ b/src/com/android/launcher3/model/GridBackupTable.java
@@ -128,6 +128,32 @@
     }
 
     /**
+     * Creates a new table and populates with copy of Favorites.TABLE_NAME
+     */
+    public void createCustomBackupTable(String tableName) {
+        long profileId = UserCache.INSTANCE.get(mContext).getSerialNumberForUser(
+                Process.myUserHandle());
+        copyTable(mDb, Favorites.TABLE_NAME, tableName, profileId);
+        encodeDBProperties(0);
+    }
+
+    /**
+     *
+     * Restores the contents of a custom table to Favorites.TABLE_NAME
+     */
+
+    public void restoreFromCustomBackupTable(String tableName, boolean dropAfterUse) {
+        if (!tableExists(mDb, tableName)) {
+            return;
+        }
+        long userSerial = UserCache.INSTANCE.get(mContext).getSerialNumberForUser(
+                Process.myUserHandle());
+        copyTable(mDb, tableName, Favorites.TABLE_NAME, userSerial);
+        if (dropAfterUse) {
+            dropTable(mDb, tableName);
+        }
+    }
+    /**
      * Copy valid grid entries from one table to another.
      */
     private static void copyTable(SQLiteDatabase db, String from, String to, long userSerial) {
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 3a89236..0c815d1 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SEARCH_RESULTS;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
@@ -45,6 +46,7 @@
 import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
 import com.android.launcher3.logger.LauncherAtom.PredictionContainer;
 import com.android.launcher3.logger.LauncherAtom.SearchResultContainer;
+import com.android.launcher3.logger.LauncherAtom.ShortcutsContainer;
 import com.android.launcher3.util.ContentWriter;
 
 import java.util.Optional;
@@ -363,6 +365,10 @@
                 return ContainerInfo.newBuilder()
                         .setSearchResultContainer(SearchResultContainer.getDefaultInstance())
                         .build();
+            case CONTAINER_SHORTCUTS:
+                return ContainerInfo.newBuilder()
+                        .setShortcutsContainer(ShortcutsContainer.getDefaultInstance())
+                        .build();
         }
         return ContainerInfo.getDefaultInstance();
     }
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 331298f..614cf14 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.popup;
 
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS;
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.Utilities.squaredTouchSlop;
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
@@ -61,6 +62,7 @@
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.notification.NotificationInfo;
 import com.android.launcher3.notification.NotificationItemView;
 import com.android.launcher3.notification.NotificationKeyData;
@@ -675,8 +677,10 @@
             iconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
 
             DraggableView draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_ICON);
+            WorkspaceItemInfo itemInfo = sv.getFinalInfo();
+            itemInfo.container = CONTAINER_SHORTCUTS;
             DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(), draggableView,
-                    mContainer, sv.getFinalInfo(),
+                    mContainer, itemInfo,
                     new ShortcutDragPreviewProvider(sv.getIconView(), iconShift),
                     new DragOptions());
             dv.animateShift(-iconShift.x, -iconShift.y);
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index 7da86cc..6d3bc14 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.popup;
 
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS;
+
 import android.content.ComponentName;
 import android.content.pm.ShortcutInfo;
 import android.os.Handler;
@@ -160,6 +162,7 @@
                 final WorkspaceItemInfo si = new WorkspaceItemInfo(shortcut, launcher);
                 cache.getUnbadgedShortcutIcon(si, shortcut);
                 si.rank = i;
+                si.container = CONTAINER_SHORTCUTS;
 
                 final DeepShortcutView view = shortcutViews.get(i);
                 uiHandler.post(() -> view.applyShortcutInfo(si, shortcut, container));
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index ae35d4c..58ed5e8 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -1,5 +1,7 @@
 package com.android.launcher3.popup;
 
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_WIDGETS_TAP;
 
 import android.app.ActivityOptions;
 import android.content.Context;
@@ -103,7 +105,6 @@
     };
 
     public static class Widgets extends SystemShortcut<Launcher> {
-
         public Widgets(Launcher target, ItemInfo itemInfo) {
             super(R.drawable.ic_widget, R.string.widget_button_text, target, itemInfo);
         }
@@ -117,6 +118,9 @@
             widgetsBottomSheet.populateAndShow(mItemInfo);
             mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
                     ControlType.WIDGETS_BUTTON, view);
+            // TODO(thiruram): Fix missing container info when item is inside folder.
+            mTarget.getStatsLogManager().log(LAUNCHER_SYSTEM_SHORTCUT_WIDGETS_TAP,
+                    mItemInfo.buildProto());
         }
     }
 
@@ -137,6 +141,9 @@
                     mItemInfo, sourceBounds, ActivityOptions.makeBasic().toBundle());
             mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
                     ControlType.APPINFO_TARGET, view);
+            // TODO(thiruram): Fix missing container info when item is inside folder.
+            mTarget.getStatsLogManager().log(LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP,
+                    mItemInfo.buildProto());
         }
     }
 
diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
index 5bf9173..e80ee2c 100644
--- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
+++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
@@ -237,6 +237,15 @@
             return true;
         });
         sandboxCategory.addPreference(launchHomeTutorialPreference);
+        Preference launchOverviewTutorialPreference = new Preference(context);
+        launchOverviewTutorialPreference.setKey("launchOverviewTutorial");
+        launchOverviewTutorialPreference.setTitle("Launch Overview Tutorial");
+        launchOverviewTutorialPreference.setSummary("Learn how to use the Overview gesture");
+        launchOverviewTutorialPreference.setOnPreferenceClickListener(preference -> {
+            startActivity(launchSandboxIntent.putExtra("tutorial_type", "OVERVIEW_NAVIGATION"));
+            return true;
+        });
+        sandboxCategory.addPreference(launchOverviewTutorialPreference);
     }
 
     private String toName(String action) {
diff --git a/src/com/android/launcher3/states/StateAnimationConfig.java b/src/com/android/launcher3/states/StateAnimationConfig.java
index 1c49867..f90ad3c 100644
--- a/src/com/android/launcher3/states/StateAnimationConfig.java
+++ b/src/com/android/launcher3/states/StateAnimationConfig.java
@@ -69,7 +69,8 @@
             ANIM_ALL_APPS_FADE,
             ANIM_OVERVIEW_SCRIM_FADE,
             ANIM_ALL_APPS_HEADER_FADE,
-            ANIM_OVERVIEW_MODAL
+            ANIM_OVERVIEW_MODAL,
+            ANIM_DEPTH,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AnimType {}
@@ -87,8 +88,9 @@
     public static final int ANIM_OVERVIEW_SCRIM_FADE = 11;
     public static final int ANIM_ALL_APPS_HEADER_FADE = 12; // e.g. predictions
     public static final int ANIM_OVERVIEW_MODAL = 13;
+    public static final int ANIM_DEPTH = 14;
 
-    private static final int ANIM_TYPES_COUNT = 14;
+    private static final int ANIM_TYPES_COUNT = 15;
 
     private final Interpolator[] mInterpolators = new Interpolator[ANIM_TYPES_COUNT];
 
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 44eae56..519b1b9 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -101,4 +101,5 @@
     public static final String PERMANENT_DIAG_TAG = "TaplTarget";
     public static final String PAUSE_NOT_DETECTED = "b/139891609";
     public static final String OVERIEW_NOT_ALLAPPS = "b/156095088";
+    public static final String NO_SWIPE_TO_HOME = "b/158017601";
 }
diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
index e6de06d..7270ce2 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -22,6 +22,7 @@
 import static android.view.MotionEvent.ACTION_UP;
 
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORKSPACE_LONGPRESS;
 
 import android.graphics.PointF;
 import android.graphics.Rect;
@@ -41,8 +42,6 @@
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.views.OptionsPopupView;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 /**
  * Helper class to handle touch on empty space in workspace and show options popup on long press
@@ -175,9 +174,7 @@
 
                 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
                         HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
-                mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
-                        Action.Direction.NONE, ContainerType.WORKSPACE,
-                        mWorkspace.getCurrentPage());
+                mLauncher.getStatsLogManager().log(LAUNCHER_WORKSPACE_LONGPRESS);
                 OptionsPopupView.showDefaultOptions(mLauncher, mTouchDownPoint.x, mTouchDownPoint.y);
             } else {
                 cancelLongPress();
diff --git a/src/com/android/launcher3/util/DefaultDisplay.java b/src/com/android/launcher3/util/DefaultDisplay.java
index fabdb4e..150fb5b 100644
--- a/src/com/android/launcher3/util/DefaultDisplay.java
+++ b/src/com/android/launcher3/util/DefaultDisplay.java
@@ -145,10 +145,11 @@
         }
 
         private Info(Context context) {
-            this(context.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY));
+            this(context, context.getSystemService(DisplayManager.class)
+                    .getDisplay(DEFAULT_DISPLAY));
         }
 
-        public Info(Display display) {
+        public Info(Context context, Display display) {
             id = display.getDisplayId();
             rotation = display.getRotation();
 
@@ -161,8 +162,8 @@
             display.getRealSize(realSize);
             display.getCurrentSizeRange(smallestSize, largestSize);
 
-            metrics = new DisplayMetrics();
-            display.getMetrics(metrics);
+            Context defaultDisplayContext = context.createDisplayContext(display);
+            metrics = defaultDisplayContext.getResources().getDisplayMetrics();
         }
 
         private boolean hasDifferentSize(Info info) {
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 6915953..c37c47c 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -181,6 +181,11 @@
     }
 
     private TouchController findControllerToHandleTouch(MotionEvent ev) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "findControllerToHandleTouch ev=" + ev
+                    + ", isEventInLauncher=" + isEventInLauncher(ev)
+                    + ", topOpenView=" + AbstractFloatingView.getTopOpenView(mActivity));
+        }
         if (isEventInLauncher(ev)) {
             AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
             if (topView != null && topView.onControllerInterceptTouchEvent(ev)) {
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index bd12e06..177aff4 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -59,6 +59,7 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.shortcuts.DeepShortcutView;
+import com.android.launcher3.testing.TestProtocol;
 
 /**
  * A view that is created to look like another view with the purpose of creating fluid animations.
@@ -560,6 +561,11 @@
         view.setVisibility(INVISIBLE);
         parent.addView(view);
         dragLayer.addView(view.mListenerView);
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "getFloatingIconView. listenerView "
+                    + "added to dragLayer. listenerView=" + view.mListenerView + ", fiv=" + view,
+                    new Exception());
+        }
         view.mListenerView.setListener(view::fastFinish);
 
         view.mEndRunnable = () -> {
@@ -639,6 +645,10 @@
     private void finish(DragLayer dragLayer) {
         ((ViewGroup) dragLayer.getParent()).removeView(this);
         dragLayer.removeView(mListenerView);
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "listenerView removed from dragLayer. "
+                    + "listenerView=" + mListenerView + ", fiv=" + this, new Exception());
+        }
         recycle();
         mLauncher.getViewCache().recycleView(R.layout.floating_icon_view, this);
     }
diff --git a/src/com/android/launcher3/views/ListenerView.java b/src/com/android/launcher3/views/ListenerView.java
index 263f7c4..575f864 100644
--- a/src/com/android/launcher3/views/ListenerView.java
+++ b/src/com/android/launcher3/views/ListenerView.java
@@ -17,18 +17,20 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 
 import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.testing.TestProtocol;
 
 /**
  * An invisible AbstractFloatingView that can run a callback when it is being closed.
  */
 public class ListenerView extends AbstractFloatingView {
 
-    public Runnable mCloseListener;
+    private Runnable mCloseListener;
 
     public ListenerView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -36,12 +38,20 @@
     }
 
     public void setListener(Runnable listener) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "ListenerView setListener lv=" + this
+                    + ", listener=" + listener, new Exception());
+        }
         mCloseListener = listener;
     }
 
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "ListenerView onAttachedToWindow lv=" + this,
+                    new Exception());
+        }
         mIsOpen = true;
     }
 
@@ -49,10 +59,19 @@
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         mIsOpen = false;
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "ListenerView onDetachedFromView lv=" + this,
+                    new Exception());
+        }
     }
 
     @Override
     protected void handleClose(boolean animate) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "ListenerView handeClose lv=" + this
+                    + ", mIsOpen=" + mIsOpen + ", mCloseListener=" + mCloseListener
+                    + ", getParent()=" + getParent(), new Exception());
+        }
         if (mIsOpen) {
             if (mCloseListener != null) {
                 mCloseListener.run();
@@ -77,6 +96,10 @@
 
     @Override
     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "ListenerView touchEvent lv=" + this
+                    + ", ev=" + ev, new Exception());
+        }
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             handleClose(false);
         }
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index d5c3c1d..7467186 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -17,6 +17,9 @@
 
 import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_FLAVOR;
 import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_OFFSET;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WALLPAPER_BUTTON_TAP_OR_LONGPRESS;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS;
 
 import android.content.Context;
 import android.content.Intent;
@@ -37,13 +40,12 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.logging.StatsLogManager.EventEnum;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.popup.ArrowPopup;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 import com.android.launcher3.widget.WidgetsFullSheet;
 
 import java.util.ArrayList;
@@ -68,21 +70,21 @@
 
     @Override
     public void onClick(View view) {
-        handleViewClick(view, Action.Touch.TAP);
+        handleViewClick(view);
     }
 
     @Override
     public boolean onLongClick(View view) {
-        return handleViewClick(view, Action.Touch.LONGPRESS);
+        return handleViewClick(view);
     }
 
-    private boolean handleViewClick(View view, int action) {
+    private boolean handleViewClick(View view) {
         OptionItem item = mItemMap.get(view);
         if (item == null) {
             return false;
         }
-        if (item.mControlTypeForLog > 0) {
-            logTap(action, item.mControlTypeForLog);
+        if (item.mEventId.getId() > 0) {
+            mLauncher.getStatsLogManager().log(item.mEventId);
         }
         if (item.mClickListener.onLongClick(view)) {
             close(true);
@@ -91,10 +93,6 @@
         return false;
     }
 
-    private void logTap(int action, int controlType) {
-        mLauncher.getUserEventDispatcher().logActionOnControl(action, controlType);
-    }
-
     @Override
     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() != MotionEvent.ACTION_DOWN) {
@@ -159,13 +157,16 @@
         int resDrawable = Utilities.existsStyleWallpapers(launcher) ?
                 R.drawable.ic_palette : R.drawable.ic_wallpaper;
         options.add(new OptionItem(resString, resDrawable,
-                ControlType.WALLPAPER_BUTTON, OptionsPopupView::startWallpaperPicker));
+                LAUNCHER_WALLPAPER_BUTTON_TAP_OR_LONGPRESS,
+                OptionsPopupView::startWallpaperPicker));
         if (!WidgetsModel.GO_DISABLE_WIDGETS) {
             options.add(new OptionItem(R.string.widget_button_text, R.drawable.ic_widget,
-                    ControlType.WIDGETS_BUTTON, OptionsPopupView::onWidgetsClicked));
+                    LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS,
+                    OptionsPopupView::onWidgetsClicked));
         }
         options.add(new OptionItem(R.string.settings_button_text, R.drawable.ic_setting,
-                ControlType.SETTINGS_BUTTON, OptionsPopupView::startSettings));
+                LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS,
+                OptionsPopupView::startSettings));
 
         show(launcher, target, options);
     }
@@ -224,14 +225,14 @@
 
         private final int mLabelRes;
         private final int mIconRes;
-        private final int mControlTypeForLog;
+        private final EventEnum mEventId;
         private final OnLongClickListener mClickListener;
 
-        public OptionItem(int labelRes, int iconRes, int controlTypeForLog,
+        public OptionItem(int labelRes, int iconRes, EventEnum eventId,
                 OnLongClickListener clickListener) {
             mLabelRes = labelRes;
             mIconRes = iconRes;
-            mControlTypeForLog = controlTypeForLog;
+            mEventId = eventId;
             mClickListener = clickListener;
         }
     }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java b/src_ui_overrides/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java
deleted file mode 100644
index 4913cad..0000000
--- a/src_ui_overrides/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2020 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;
-
-import android.content.Context;
-import android.os.Bundle;
-
-/** Render preview using surface view. */
-public class PreviewSurfaceRenderer {
-
-    /** Handle a received surface view request. */
-    public static void render(Context context, Bundle bundle) { }
-}