Merge "Removes Sandbox from launcher task." into ub-launcher3-rvc-dev
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index 7e8e51e..d1185bd 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -54,6 +54,7 @@
     ShortcutsContainer shortcuts_container = 8;
     SettingsContainer settings_container = 9;
     PredictedHotseatContainer predicted_hotseat_container = 10;
+    TaskSwitcherContainer task_switcher_container = 11;
   }
 }
 
@@ -82,6 +83,9 @@
 message SettingsContainer {
 }
 
+message TaskSwitcherContainer {
+}
+
 enum Attribute {
   UNKNOWN = 0;
   DEFAULT_LAYOUT = 1;       // icon automatically placed in workspace, folder, hotseat
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
index b6a8206..0113570 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
@@ -18,6 +18,7 @@
 
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALL_APPS_RANKED;
 
 import android.app.prediction.AppPredictor;
 import android.app.prediction.AppTarget;
@@ -37,6 +38,8 @@
 import com.android.launcher3.allapps.AllAppsStore.OnUpdateListener;
 import com.android.launcher3.hybridhotseat.HotseatPredictionController;
 import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
+import com.android.launcher3.logger.LauncherAtom;
+import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.shortcuts.ShortcutKey;
@@ -48,6 +51,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.OptionalInt;
 import java.util.stream.IntStream;
 
 /**
@@ -302,6 +306,41 @@
     }
 
     /**
+     * Logs ranking info for launched app within all apps prediction.
+     * Only applicable when {@link ItemInfo#itemType} is one of the followings:
+     * {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION},
+     * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT},
+     * {@link LauncherSettings.Favorites#ITEM_TYPE_DEEP_SHORTCUT}
+     */
+    public void logLaunchedAppRankingInfo(@NonNull ItemInfo itemInfo, InstanceId instanceId) {
+        if (itemInfo.getTargetComponent() == null || itemInfo.user == null
+                || (itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+                && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
+                && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)) {
+            return;
+        }
+
+        Launcher launcher = Launcher.getLauncher(mAppsView.getContext());
+        final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
+        final List<ComponentKeyMapper> predictedApps = getCurrentState().apps;
+        OptionalInt rank = IntStream.range(0, predictedApps.size())
+                .filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
+                .findFirst();
+        if (!rank.isPresent()) {
+            return;
+        }
+
+        LauncherAtom.ItemInfo.Builder atomBuilder = LauncherAtom.ItemInfo.newBuilder();
+        atomBuilder.setRank(rank.getAsInt());
+        atomBuilder.setContainerInfo(
+                LauncherAtom.ContainerInfo.newBuilder().setPredictionContainer(
+                        LauncherAtom.PredictionContainer.newBuilder().build()).build());
+        launcher.getStatsLogManager().log(LAUNCHER_ALL_APPS_RANKED, instanceId,
+                atomBuilder.build());
+    }
+
+
+    /**
      * Fill in predicted_rank field based on app prediction.
      * Only applicable when {@link ItemInfo#itemType} is one of the followings:
      * {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION},
@@ -310,6 +349,7 @@
      */
     public static void fillInPredictedRank(
             @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) {
+
         final PredictionUiStateManager manager = PredictionUiStateManager.INSTANCE.getNoCreate();
         if (manager == null || itemInfo.getTargetComponent() == null || itemInfo.user == null
                 || (itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
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 f1ce72e..522a2dc 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,6 +15,9 @@
  */
 package com.android.launcher3.hybridhotseat;
 
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent
+        .LAUNCHER_HOTSEAT_EDU_ONLY_TIP;
+
 import android.content.Intent;
 import android.view.View;
 
@@ -47,12 +50,12 @@
     public static final String KEY_HOTSEAT_EDU_SEEN = "hotseat_edu_seen";
     public static final String HOTSEAT_EDU_ACTION =
             "com.android.launcher3.action.SHOW_HYBRID_HOTSEAT_EDU";
-    private static final String SETTINGS_ACTION =
+    public static final String SETTINGS_ACTION =
             "android.settings.ACTION_CONTENT_SUGGESTIONS_SETTINGS";
 
     private final Launcher mLauncher;
     private final Hotseat mHotseat;
-    private final HotseatRestoreHelper mRestoreHelper;
+    private HotseatRestoreHelper mRestoreHelper;
     private List<WorkspaceItemInfo> mPredictedApps;
     private HotseatEduDialog mActiveDialog;
 
@@ -71,14 +74,17 @@
      * Checks what type of migration should be used and migrates hotseat
      */
     void migrate() {
-        mRestoreHelper.createBackup();
+        if (mRestoreHelper != null) {
+            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)));
+        Snackbar.show(mLauncher, R.string.hotsaet_tip_prediction_enabled,
+                R.string.hotseat_prediction_settings, null,
+                () -> mLauncher.startActivity(new Intent(SETTINGS_ACTION)));
     }
 
     /**
@@ -223,15 +229,15 @@
 
     void finishOnboarding() {
         mOnOnboardingComplete.run();
-        destroy();
         mLauncher.getSharedPrefs().edit().putBoolean(KEY_HOTSEAT_EDU_SEEN, true).apply();
     }
 
     void showDimissTip() {
         if (mHotseat.getShortcutsAndWidgets().getChildCount()
                 < mLauncher.getDeviceProfile().inv.numHotseatIcons) {
-            Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled, R.string.hotseat_turn_off,
-                    null, () -> mLauncher.startActivity(new Intent(SETTINGS_ACTION)));
+            Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
+                    R.string.hotseat_prediction_settings, null,
+                    () -> mLauncher.startActivity(new Intent(SETTINGS_ACTION)));
         } else {
             new ArrowTipView(mLauncher).show(
                     mLauncher.getString(R.string.hotseat_tip_no_empty_slots), mHotseat.getTop());
@@ -242,12 +248,6 @@
         mPredictedApps = predictedApps;
     }
 
-    void destroy() {
-        if (mActiveDialog != null) {
-            mActiveDialog.setHotseatEduController(null);
-        }
-    }
-
     void showEdu() {
         int childCount = mHotseat.getShortcutsAndWidgets().getChildCount();
         CellLayout cellLayout = mLauncher.getWorkspace().getScreenWithId(Workspace.FIRST_SCREEN_ID);
@@ -265,6 +265,7 @@
                     requiresMigration ? R.string.hotseat_tip_no_empty_slots
                             : R.string.hotseat_auto_enrolled),
                     mHotseat.getTop());
+            mLauncher.getStatsLogManager().logger().log(LAUNCHER_HOTSEAT_EDU_ONLY_TIP);
             finishOnboarding();
         }
     }
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 99cb3b3..2b3f395 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
@@ -15,9 +15,10 @@
  */
 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.logging.StatsLogManager.LauncherEvent
+        .LAUNCHER_HOTSEAT_EDU_ACCEPT;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_EDU_DENY;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_EDU_SEEN;
 
 import android.animation.PropertyValuesHolder;
 import android.content.Context;
@@ -29,15 +30,14 @@
 import android.widget.Button;
 import android.widget.TextView;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.launcher3.Workspace;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.uioverrides.PredictedAppIcon;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -112,15 +112,13 @@
 
         mHotseatEduController.moveHotseatItems();
         mHotseatEduController.finishOnboarding();
-        //TODO: pass actual page index here.
-        // Temporarily we're passing 1 for folder migration and 2 for page migration
-        logUserAction(true, FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get() ? 1 : 2);
+        mLauncher.getStatsLogManager().logger().log(LAUNCHER_HOTSEAT_EDU_ACCEPT);
     }
 
     private void onDismiss(View v) {
         mHotseatEduController.showDimissTip();
         mHotseatEduController.finishOnboarding();
-        logUserAction(false, -1);
+        mLauncher.getStatsLogManager().logger().log(LAUNCHER_HOTSEAT_EDU_DENY);
         handleClose(true);
     }
 
@@ -164,39 +162,6 @@
         }
     }
 
-    private void logUserAction(boolean migrated, int pageIndex) {
-        LauncherLogProto.Action action = new LauncherLogProto.Action();
-        LauncherLogProto.Target target = new LauncherLogProto.Target();
-
-        int hotseatItemsCount = mLauncher.getHotseat().getShortcutsAndWidgets().getChildCount();
-        // -1 to exclude smart space
-        int workspaceItemCount = mLauncher.getWorkspace().getScreenWithId(
-                Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets().getChildCount() - 1;
-
-        action.type = LauncherLogProto.Action.Type.TOUCH;
-        action.touch = LauncherLogProto.Action.Touch.TAP;
-        target.containerType = LauncherLogProto.ContainerType.TIP;
-        target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT;
-        target.controlType = migrated ? LauncherLogProto.ControlType.HYBRID_HOTSEAT_ACCEPTED
-                : HYBRID_HOTSEAT_CANCELED;
-        target.rank = MIGRATION_EXPERIMENT_IDENTIFIER;
-        // encoding migration type on pageIndex
-        target.pageIndex = pageIndex;
-        target.cardinality = (workspaceItemCount * 1000) + hotseatItemsCount;
-        LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
-        UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null);
-    }
-
-    private void logOnBoardingSeen() {
-        LauncherLogProto.Action action = new LauncherLogProto.Action();
-        LauncherLogProto.Target target = new LauncherLogProto.Target();
-        action.type = LauncherLogProto.Action.Type.TIP;
-        target.containerType = LauncherLogProto.ContainerType.TIP;
-        target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT;
-        LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
-        UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null);
-    }
-
     private void animateOpen() {
         if (mIsOpen || mOpenCloseAnimator.isRunning()) {
             return;
@@ -245,8 +210,9 @@
                 || mHotseatEduController == null) {
             return;
         }
+        AbstractFloatingView.closeAllOpenViews(mLauncher);
         attachToContainer();
-        logOnBoardingSeen();
+        mLauncher.getStatsLogManager().logger().log(LAUNCHER_HOTSEAT_EDU_SEEN);
         animateOpen();
         populatePreview(predictions);
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java
new file mode 100644
index 0000000..c15a596
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java
@@ -0,0 +1,129 @@
+/*
+ * 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 android.content.Context;
+import android.os.Handler;
+import android.util.Log;
+
+import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.MainThreadInitializedObject;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.PrintWriter;
+import java.text.DateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * Helper class to allow hot seat file logging
+ */
+public class HotseatFileLog {
+
+    public static final int LOG_DAYS = 10;
+    private static final String FILE_NAME_PREFIX = "hotseat-log-";
+    private static final DateFormat DATE_FORMAT =
+            DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
+    public static final MainThreadInitializedObject<HotseatFileLog> INSTANCE =
+            new MainThreadInitializedObject<>(HotseatFileLog::new);
+
+
+    private final Handler mHandler = new Handler(
+            Executors.createAndStartNewLooper("hotseat-logger"));
+    private final File mLogsDir;
+    private PrintWriter mCurrentWriter;
+    private String mFileName;
+
+    private HotseatFileLog(Context context) {
+        mLogsDir = context.getFilesDir();
+    }
+
+    /**
+     * Prints log values to disk
+     */
+    public void log(String tag, String msg) {
+        String out = String.format("%s %s %s", DATE_FORMAT.format(new Date()), tag, msg);
+
+        mHandler.post(() -> {
+            synchronized (this) {
+                PrintWriter writer = getWriter();
+                if (writer != null) {
+                    writer.println(out);
+                }
+            }
+        });
+    }
+
+    private PrintWriter getWriter() {
+        String fName = FILE_NAME_PREFIX + (LOG_DAYS % 10);
+        if (fName.equals(mFileName)) return mCurrentWriter;
+
+        Calendar cal = Calendar.getInstance();
+
+        boolean append = false;
+        File logFile = new File(mLogsDir, fName);
+        if (logFile.exists()) {
+            Calendar modifiedTime = Calendar.getInstance();
+            modifiedTime.setTimeInMillis(logFile.lastModified());
+
+            // If the file was modified more that 36 hours ago, purge the file.
+            // We use instead of 24 to account for day-365 followed by day-1
+            modifiedTime.add(Calendar.HOUR, 36);
+            append = cal.before(modifiedTime);
+        }
+
+
+        if (mCurrentWriter != null) {
+            mCurrentWriter.close();
+        }
+        try {
+            mCurrentWriter = new PrintWriter(new FileWriter(logFile, append));
+            mFileName = fName;
+        } catch (Exception ex) {
+            Log.e("HotseatLogs", "Error writing logs to file", ex);
+            closeWriter();
+        }
+        return mCurrentWriter;
+    }
+
+
+    private synchronized void closeWriter() {
+        mFileName = null;
+        if (mCurrentWriter != null) {
+            mCurrentWriter.close();
+        }
+        mCurrentWriter = null;
+    }
+
+
+    /**
+     * Returns a list of all log files
+     */
+    public synchronized File[] getLogFiles() {
+        File[] files = new File[LOG_DAYS + FileLog.LOG_DAYS];
+        //include file log files here
+        System.arraycopy(FileLog.getLogFiles(), 0, files, 0, FileLog.LOG_DAYS);
+
+        closeWriter();
+        for (int i = 0; i < LOG_DAYS; i++) {
+            files[FileLog.LOG_DAYS + i] = new File(mLogsDir, FILE_NAME_PREFIX + i);
+        }
+        return files;
+    }
+}
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 bd4d713..30a34e4 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
@@ -17,6 +17,8 @@
 
 import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.hybridhotseat.HotseatEduController.SETTINGS_ACTION;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_RANKED;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -27,6 +29,8 @@
 import android.app.prediction.AppTarget;
 import android.app.prediction.AppTargetEvent;
 import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Process;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -50,7 +54,8 @@
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.logger.LauncherAtom;
+import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
@@ -64,6 +69,8 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IntArray;
+import com.android.launcher3.views.ArrowTipView;
+import com.android.launcher3.views.Snackbar;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -107,8 +114,6 @@
     private boolean mIsCacheEmpty;
     private boolean mIsDestroyed = false;
 
-    private HotseatEduController mHotseatEduController;
-
 
     private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
 
@@ -146,11 +151,47 @@
     }
 
     /**
-     * Transitions to NORMAL workspace mode and shows edu
+     * Shows appropriate hotseat education based on prediction enabled and migration states.
      */
     public void showEdu() {
-        if (mHotseatEduController == null) return;
-        mHotseatEduController.showEdu();
+        if (mComponentKeyMappers.isEmpty()) {
+            // launcher has empty predictions set
+            Snackbar.show(mLauncher, R.string.hotsaet_tip_prediction_disabled,
+                    R.string.hotseat_prediction_settings, null,
+                    () -> mLauncher.startActivity(
+                            new Intent(SETTINGS_ACTION)));
+        } else if (isEduSeen()) {
+            // user has already went through education
+            new ArrowTipView(mLauncher).show(
+                    mLauncher.getString(R.string.hotsaet_tip_prediction_enabled),
+                    mHotseat.getTop());
+        } else {
+            HotseatEduController eduController = new HotseatEduController(mLauncher, mRestoreHelper,
+                    this::createPredictor);
+            eduController.setPredictedApps(mapToWorkspaceItemInfo(mComponentKeyMappers));
+            eduController.showEdu();
+        }
+    }
+
+    /**
+     * Shows educational tip for hotseat if user does not go through Tips app.
+     */
+    public void showDiscoveryTip() {
+        if (getPredictedIcons().size() == mHotSeatItemsCount) {
+            new ArrowTipView(mLauncher).show(
+                    mLauncher.getString(R.string.hotseat_tip_no_empty_slots), mHotseat.getTop());
+        } else {
+            Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
+                    R.string.hotseat_prediction_settings, null,
+                    () -> mLauncher.startActivity(new Intent(SETTINGS_ACTION)));
+        }
+    }
+
+    /**
+     * Returns if hotseat client has predictions
+     */
+    public boolean hasPredictions() {
+        return !mComponentKeyMappers.isEmpty();
     }
 
     @Override
@@ -250,10 +291,6 @@
         if (mAppPredictor != null) {
             mAppPredictor.destroy();
         }
-        if (mHotseatEduController != null) {
-            mHotseatEduController.destroy();
-            mHotseatEduController = null;
-        }
     }
 
     /**
@@ -299,20 +336,20 @@
             mAppPredictor.requestPredictionUpdate();
         });
         setPauseUIUpdate(false);
-        if (!isEduSeen()) {
-            mHotseatEduController = new HotseatEduController(mLauncher, mRestoreHelper,
-                    this::createPredictor);
-        }
     }
 
     /**
      * Create WorkspaceItemInfo objects and binds PredictedAppIcon views for cached predicted items.
      */
-    public void showCachedItems(List<AppInfo> apps,  IntArray ranks) {
+    public void showCachedItems(List<AppInfo> apps, IntArray ranks) {
+        if (hasPredictions() && mAppPredictor != null) {
+            mAppPredictor.requestPredictionUpdate();
+            fillGapsWithPrediction();
+            return;
+        }
         mIsCacheEmpty = apps.isEmpty();
         int count = Math.min(ranks.size(), apps.size());
         List<WorkspaceItemInfo> items = new ArrayList<>(count);
-        mComponentKeyMappers.clear();
         for (int i = 0; i < count; i++) {
             WorkspaceItemInfo item = new WorkspaceItemInfo(apps.get(i));
             ComponentKey componentKey = new ComponentKey(item.getTargetComponent(), item.user);
@@ -324,6 +361,7 @@
         updateDependencies();
         bindItems(items, false, null);
     }
+
     private void setPredictedApps(List<AppTarget> appTargets) {
         mComponentKeyMappers.clear();
         if (appTargets.isEmpty()) {
@@ -347,12 +385,11 @@
             mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache));
         }
         predictionLog.append("]");
-        if (Utilities.IS_DEBUG_DEVICE) FileLog.d(TAG, predictionLog.toString());
+        if (Utilities.IS_DEBUG_DEVICE) {
+            HotseatFileLog.INSTANCE.get(mLauncher).log(TAG, predictionLog.toString());
+        }
         updateDependencies();
         fillGapsWithPrediction();
-        if (!isEduSeen() && mHotseatEduController != null) {
-            mHotseatEduController.setPredictedApps(mapToWorkspaceItemInfo(mComponentKeyMappers));
-        }
         cachePredictionComponentKeysIfNecessary(componentKeys);
     }
 
@@ -602,6 +639,48 @@
         mHotseat.fillInLogContainerData(childInfo, child, parents);
     }
 
+    /**
+     * Logs rank info based on current list of predicted items
+     */
+    public void logLaunchedAppRankingInfo(@NonNull ItemInfo itemInfo, InstanceId instanceId) {
+        if (Utilities.IS_DEBUG_DEVICE) {
+            final String pkg = itemInfo.getTargetComponent() != null
+                    ? itemInfo.getTargetComponent().getPackageName() : "unknown";
+            HotseatFileLog.INSTANCE.get(mLauncher).log("UserEvent",
+                    "appLaunch: packageName:" + pkg + ",isWorkApp:" + (itemInfo.user != null
+                            && !Process.myUserHandle().equals(itemInfo.user))
+                            + ",launchLocation:" + itemInfo.container);
+        }
+
+        final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
+
+        final List<ComponentKeyMapper> predictedApps = new ArrayList<>(mComponentKeyMappers);
+        OptionalInt rank = IntStream.range(0, predictedApps.size())
+                .filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
+                .findFirst();
+        if (!rank.isPresent()) {
+            return;
+        }
+        LauncherAtom.PredictedHotseatContainer.Builder containerBuilder =
+                LauncherAtom.PredictedHotseatContainer.newBuilder();
+        LauncherAtom.ItemInfo.Builder atomBuilder = LauncherAtom.ItemInfo.newBuilder();
+        int cardinality = 0;
+        for (PredictedAppIcon icon : getPredictedIcons()) {
+            ItemInfo info = (ItemInfo) icon.getTag();
+            cardinality |= 1 << info.screenId;
+        }
+        containerBuilder.setCardinality(cardinality);
+        atomBuilder.setRank(rank.getAsInt());
+        if (itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
+            containerBuilder.setIndex(rank.getAsInt());
+        }
+        atomBuilder.setContainerInfo(
+                LauncherAtom.ContainerInfo.newBuilder().setPredictedHotseatContainer(
+                        containerBuilder).build());
+        mLauncher.getStatsLogManager().log(LAUNCHER_HOTSEAT_RANKED, instanceId,
+                atomBuilder.build());
+    }
+
     private class PinPrediction extends SystemShortcut<QuickstepLauncher> {
 
         private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 0ace4cc..597c17b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -57,7 +57,7 @@
         LauncherAccessibilityDelegate.AccessibilityActionHandler {
 
     private static final int RING_SHADOW_COLOR = 0x99000000;
-    private static final float RING_EFFECT_RATIO = 0.08f;
+    private static final float RING_EFFECT_RATIO = 0.095f;
 
     boolean mIsDrawingDot = false;
     private final DeviceProfile mDeviceProfile;
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 494a98d..0e690eb 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
@@ -42,10 +42,12 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.appprediction.PredictionUiStateManager;
 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.logging.InstanceId;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -88,14 +90,20 @@
      */
     public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) ->
             SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2);
-    private HotseatPredictionController mHotseatPredictionController;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        if (mHotseatPredictionController != null) {
+            mHotseatPredictionController.createPredictor();
+        }
+    }
+
+    @Override
+    protected void setupViews() {
+        super.setupViews();
         if (FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
             mHotseatPredictionController = new HotseatPredictionController(this);
-            mHotseatPredictionController.createPredictor();
         }
     }
 
@@ -114,6 +122,15 @@
     }
 
     @Override
+    protected void logAppLaunch(ItemInfo info, InstanceId instanceId) {
+        super.logAppLaunch(info, instanceId);
+        if (mHotseatPredictionController != null) {
+            mHotseatPredictionController.logLaunchedAppRankingInfo(info, instanceId);
+        }
+        PredictionUiStateManager.INSTANCE.get(this).logLaunchedAppRankingInfo(info, instanceId);
+    }
+
+    @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         onStateOrResumeChanging(false /* inTransition */);
@@ -169,13 +186,6 @@
     }
 
     /**
-     * Returns Prediction controller for hybrid hotseat
-     */
-    public HotseatPredictionController getHotseatPredictionController() {
-        return mHotseatPredictionController;
-    }
-
-    /**
      * Recents logic that triggers when launcher state changes or launcher activity stops/resumes.
      */
     private void onStateOrResumeChanging(boolean inTransition) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
index d5b0687..fc0dcd5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -20,6 +20,7 @@
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.quickstep.views.RecentsView;
 
@@ -56,6 +57,17 @@
         return 1.0f;
     }
 
+    @Override
+    public void onBackPressed(Launcher launcher) {
+        launcher.getStateManager().goToState(LauncherState.OVERVIEW);
+        RecentsView recentsView = launcher.<RecentsView>getOverviewPanel();
+        if (recentsView != null) {
+            recentsView.resetModalVisuals();
+        } else {
+            super.onBackPressed(launcher);
+        }
+    }
+
     public static float[] getOverviewScaleAndOffsetForModalState(BaseDraggingActivity activity) {
         Rect out = new Rect();
         activity.<RecentsView>getOverviewPanel().getTaskSize(out);
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 966e25b..4cc8256 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -24,6 +24,7 @@
 import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
 import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
 
+import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.graphics.PointF;
@@ -59,6 +60,8 @@
 
     private boolean mDidTouchStartInNavBar;
     private boolean mReachedOverview;
+    private boolean mIsOverviewRehidden;
+    private boolean mIsHomeStaggeredAnimFinished;
     // The last recorded displacement before we reached overview.
     private PointF mStartDisplacement = new PointF();
 
@@ -144,6 +147,13 @@
         }
     }
 
+    // Used if flinging back to home after reaching overview
+    private void maybeSwipeInteractionToHomeComplete() {
+        if (mIsHomeStaggeredAnimFinished && mIsOverviewRehidden) {
+            onSwipeInteractionCompleted(NORMAL, Touch.FLING);
+        }
+    }
+
     @Override
     protected boolean handlingOverviewAnim() {
         return mDidTouchStartInNavBar && super.handlingOverviewAnim();
@@ -180,9 +190,17 @@
                 stateManager.goToState(NORMAL, true,
                         () -> onSwipeInteractionCompleted(NORMAL, Touch.FLING));
             } else {
+                mIsHomeStaggeredAnimFinished = mIsOverviewRehidden = false;
+
                 StaggeredWorkspaceAnim staggeredWorkspaceAnim = new StaggeredWorkspaceAnim(
                         mLauncher, velocity, false /* animateOverviewScrim */);
-                staggeredWorkspaceAnim.start();
+                staggeredWorkspaceAnim.addAnimatorListener(new AnimationSuccessListener() {
+                    @Override
+                    public void onAnimationSuccess(Animator animator) {
+                        mIsHomeStaggeredAnimFinished = true;
+                        maybeSwipeInteractionToHomeComplete();
+                    }
+                }).start();
 
                 // StaggeredWorkspaceAnim doesn't animate overview, so we handle it here.
                 stateManager.cancelAnimation();
@@ -191,8 +209,10 @@
                 config.animFlags = PLAY_ATOMIC_OVERVIEW_PEEK;
                 AnimatorSet anim = stateManager.createAtomicAnimation(
                         stateManager.getState(), NORMAL, config);
-                anim.addListener(AnimationSuccessListener.forRunnable(
-                        () -> onSwipeInteractionCompleted(NORMAL, Touch.SWIPE)));
+                anim.addListener(AnimationSuccessListener.forRunnable(() -> {
+                    mIsOverviewRehidden = true;
+                    maybeSwipeInteractionToHomeComplete();
+                }));
                 anim.start();
             }
         }
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 b5fb31a..f5c5874 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -32,7 +32,6 @@
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.VibratorWrapper;
@@ -96,8 +95,8 @@
      * depend on proper class initialization.
      */
     protected void initAfterSubclassConstructor() {
-        initTransitionEndpoints(InvariantDeviceProfile.INSTANCE.get(mContext)
-                .getDeviceProfile(mContext));
+        initTransitionEndpoints(
+                mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile());
     }
 
     protected void performHapticFeedback() {
@@ -144,13 +143,14 @@
                 TaskView nextTask = mRecentsView.getTaskView(taskId);
                 if (nextTask != null) {
                     mGestureState.updateLastStartedTaskId(taskId);
+                    boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
+                            .contains(taskId);
                     nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
                             success -> {
                                 resultCallback.accept(success);
                                 if (success) {
-                                    if (mRecentsView.indexOfChild(nextTask)
-                                            == getLastAppearedTaskIndex()) {
-                                        onRestartLastAppearedTask();
+                                    if (hasTaskPreviouslyAppeared) {
+                                        onRestartPreviouslyAppearedTask();
                                     }
                                 } else {
                                     mActivityInterface.onLaunchTaskFailed();
@@ -171,7 +171,7 @@
      * start A again to ensure it stays on top.
      */
     @CallSuper
-    protected void onRestartLastAppearedTask() {
+    protected void onRestartPreviouslyAppearedTask() {
         // Finish the controller here, since we won't get onTaskAppeared() for a task that already
         // appeared.
         if (mRecentsAnimationController != null) {
@@ -205,7 +205,7 @@
         mRecentsAnimationController = recentsAnimationController;
         mRecentsAnimationTargets = targets;
         mTransformParams.setTargetSet(mRecentsAnimationTargets);
-        DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext);
+        DeviceProfile dp = mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile();
         RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(
                 mGestureState.getRunningTaskId());
 
@@ -300,8 +300,7 @@
             if (TestProtocol.sDebugTracing) {
                 Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.2");
             }
-            initTransitionEndpoints(InvariantDeviceProfile.INSTANCE.get(mContext)
-                .getDeviceProfile(mContext));
+            initTransitionEndpoints(createdActivity.getDeviceProfile());
         }
         return true;
     }
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 414d7ae..e825c5f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
@@ -131,7 +131,7 @@
 
     private static final int STATE_CAPTURE_SCREENSHOT =
             getFlagForIndex(10, "STATE_CAPTURE_SCREENSHOT");
-    private static final int STATE_SCREENSHOT_CAPTURED =
+    protected static final int STATE_SCREENSHOT_CAPTURED =
             getFlagForIndex(11, "STATE_SCREENSHOT_CAPTURED");
     private static final int STATE_SCREENSHOT_VIEW_SHOWN =
             getFlagForIndex(12, "STATE_SCREENSHOT_VIEW_SHOWN");
@@ -1122,8 +1122,8 @@
     }
 
     @Override
-    protected void onRestartLastAppearedTask() {
-        super.onRestartLastAppearedTask();
+    protected void onRestartPreviouslyAppearedTask() {
+        super.onRestartPreviouslyAppearedTask();
         reset();
     }
 
@@ -1192,7 +1192,7 @@
         mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
     }
 
-    private void switchToScreenshot() {
+    protected void switchToScreenshot() {
         final int runningTaskId = mGestureState.getRunningTaskId();
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             if (mRecentsAnimationController != null) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
index 96913c6..fc7a119 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -111,6 +111,16 @@
     }
 
     @Override
+    protected void switchToScreenshot() {
+        if (mRunningOverHome) {
+            // When the current task is home, then we don't need to capture anything
+            mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
+        } else {
+            super.switchToScreenshot();
+        }
+    }
+
+    @Override
     protected void notifyGestureAnimationStartToRecents() {
         if (mRunningOverHome) {
             mRecentsView.onGestureAnimationStartOnHome(mGestureState.getRunningTask());
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 a28dabc..ebc83c6 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -37,7 +37,7 @@
             case TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT: {
                 final float swipeHeight =
                         LayoutUtils.getShelfTrackingDistance(mContext, mDeviceProfile,
-                                PagedOrientationHandler.HOME_ROTATED);
+                                PagedOrientationHandler.PORTRAIT);
                 response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
                 return response;
             }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
index 852a51a..1701020 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
@@ -139,13 +139,12 @@
      */
     protected DeviceProfile createDeviceProfile() {
         DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(this).getDeviceProfile(this);
-        DeviceProfile dp1 = InvariantDeviceProfile.INSTANCE.get(this).getDeviceProfile(this);
 
         // In case we are reusing IDP, create a copy so that we don't conflict with Launcher
         // activity.
         return (mRecentsRootView != null) && isInMultiWindowMode()
                 ? dp.getMultiWindowProfile(this, getMultiWindowDisplaySize())
-                : dp1.copy(this);
+                : dp.copy(this);
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java
index b17730b..dc8f1c5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -83,15 +83,15 @@
         mGestureState = gestureState;
         mTaskViewSimulator = new TaskViewSimulator(context, gestureState.getActivityInterface());
         mTransformParams = transformParams;
+
+        mTaskViewSimulator.setLayoutRotation(
+                mDeviceState.getCurrentActiveRotation(), mDeviceState.getDisplayRotation());
     }
 
     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());
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 97cd11b..ed07062 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
@@ -16,7 +16,10 @@
 
 package com.android.quickstep;
 
+import static android.view.Surface.ROTATION_0;
+
 import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
+import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED;
 
 import android.annotation.SuppressLint;
 import android.content.Context;
@@ -24,6 +27,7 @@
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.os.Build;
+import android.view.View;
 import android.widget.Toast;
 
 import androidx.annotation.RequiresApi;
@@ -31,9 +35,12 @@
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.R;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.ResourceBasedOverride;
+import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.TaskThumbnailView;
 import com.android.quickstep.views.TaskView;
@@ -58,6 +65,28 @@
                 shortcuts.add(shortcut);
             }
         }
+        RecentsOrientedState orientedState = taskView.getRecentsView().getPagedViewOrientedState();
+        boolean canLauncherRotate = orientedState.canLauncherRotate();
+        boolean isInLandscape = orientedState.getTouchRotation() != ROTATION_0;
+
+        // Add overview actions to the menu when in in-place rotate landscape mode.
+        if (!canLauncherRotate && isInLandscape) {
+            // Add screenshot action to task menu.
+            SystemShortcut screenshotShortcut = TaskShortcutFactory.SCREENSHOT
+                    .getShortcut(activity, taskView);
+            if (screenshotShortcut != null) {
+                shortcuts.add(screenshotShortcut);
+            }
+
+            // Add modal action only if display orientation is the same as the device orientation.
+            if (orientedState.getDisplayRotation() == ROTATION_0) {
+                SystemShortcut modalShortcut = TaskShortcutFactory.MODAL
+                        .getShortcut(activity, taskView);
+                if (modalShortcut != null) {
+                    shortcuts.add(modalShortcut);
+                }
+            }
+        }
         return shortcuts;
     }
 
@@ -94,10 +123,14 @@
         protected final TaskThumbnailView mThumbnailView;
 
         private T mActionsView;
+        private ImageActionsApi mImageApi;
+        private boolean mIsAllowedByPolicy;
 
         protected TaskOverlay(TaskThumbnailView taskThumbnailView) {
             mApplicationContext = taskThumbnailView.getContext().getApplicationContext();
             mThumbnailView = taskThumbnailView;
+            mImageApi = new ImageActionsApi(
+                mApplicationContext, mThumbnailView::getThumbnail);
         }
 
         protected T getActionsView() {
@@ -111,16 +144,17 @@
         /**
          * Called when the current task is interactive for the user
          */
-        public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix) {
-            ImageActionsApi imageApi = new ImageActionsApi(
-                    mApplicationContext, mThumbnailView::getThumbnail);
+        public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix,
+                boolean rotated) {
             final boolean isAllowedByPolicy = thumbnail.isRealSnapshot;
 
+            mActionsView.updateDisabledFlags(DISABLED_ROTATED, rotated);
+
             getActionsView().setCallbacks(new OverlayUICallbacks() {
                 @Override
                 public void onShare() {
                     if (isAllowedByPolicy) {
-                        imageApi.startShareActivity();
+                        mImageApi.startShareActivity();
                     } else {
                         showBlockedByPolicyMessage();
                     }
@@ -129,16 +163,23 @@
                 @SuppressLint("NewApi")
                 @Override
                 public void onScreenshot() {
-                    if (isAllowedByPolicy) {
-                        imageApi.saveScreenshot(mThumbnailView.getThumbnail(),
-                                getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key);
-                    } else {
-                        showBlockedByPolicyMessage();
-                    }
+                    saveScreenshot(task);
                 }
             });
         }
 
+        /**
+         * Called to save screenshot of the task thumbnail.
+         */
+        @SuppressLint("NewApi")
+        private void saveScreenshot(Task task) {
+            if (mThumbnailView.isRealSnapshot()) {
+                mImageApi.saveScreenshot(mThumbnailView.getThumbnail(),
+                        getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key);
+            } else {
+                showBlockedByPolicyMessage();
+            }
+        }
 
         /**
          * Called when the overlay is no longer used.
@@ -147,6 +188,26 @@
         }
 
         /**
+         * Called when the system wants to reset the modal visuals.
+         */
+        public void resetModalVisuals() {
+        }
+
+        /**
+         * Gets the modal state system shortcut.
+         */
+        public SystemShortcut getModalStateSystemShortcut(WorkspaceItemInfo itemInfo) {
+            return null;
+        }
+
+        /**
+         * Gets the system shortcut for the screenshot that will be added to the task menu.
+         */
+        public SystemShortcut getScreenshotShortcut(BaseDraggingActivity activity,
+                ItemInfo iteminfo) {
+            return new ScreenshotSystemShortcut(activity, iteminfo);
+        }
+        /**
          * Gets the task snapshot as it is displayed on the screen.
          *
          * @return the bounds of the snapshot in screen coordinates.
@@ -175,6 +236,22 @@
                     R.string.blocked_by_policy,
                     Toast.LENGTH_LONG).show();
         }
+
+        private class ScreenshotSystemShortcut extends SystemShortcut {
+
+            private final BaseDraggingActivity mActivity;
+
+            ScreenshotSystemShortcut(BaseDraggingActivity activity, ItemInfo itemInfo) {
+                super(R.drawable.ic_screenshot, R.string.action_screenshot, activity, itemInfo);
+                mActivity = activity;
+            }
+
+            @Override
+            public void onClick(View view) {
+                saveScreenshot(mThumbnailView.getTaskView().getTask());
+                dismissTaskMenuView(mActivity);
+            }
+        }
     }
 
     /**
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 3623e67..ff051b6 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java
@@ -18,29 +18,26 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_SELECTIONS;
 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;
 import android.app.ActivityOptions;
-import android.content.ComponentName;
-import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.UserHandle;
 import android.view.View;
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
-import com.android.launcher3.logger.LauncherAtom;
 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;
 import com.android.launcher3.popup.SystemShortcut.AppInfo;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -69,28 +66,7 @@
 
     SystemShortcut getShortcut(BaseDraggingActivity activity, TaskView view);
 
-    static WorkspaceItemInfo dummyInfo(TaskView view) {
-        Task task = view.getTask();
-
-        WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo(){
-            /**
-             * Helps to log events as {@link LauncherAtom.Task}
-             * instead of {@link LauncherAtom.ItemInfo}.
-             */
-            @Override
-            public LauncherAtom.ItemInfo buildProto() {
-                return view.buildProto();
-            }
-        };
-        dummyInfo.intent = new Intent();
-        ComponentName component = task.getTopComponent();
-        dummyInfo.getIntent().setComponent(component);
-        dummyInfo.user = UserHandle.of(task.key.userId);
-        dummyInfo.title = TaskUtils.getTitle(view.getContext(), task);
-        return dummyInfo;
-    }
-
-    TaskShortcutFactory APP_INFO = (activity, view) -> new AppInfo(activity, dummyInfo(view));
+    TaskShortcutFactory APP_INFO = (activity, view) -> new AppInfo(activity, view.getItemInfo());
 
     abstract class MultiWindowFactory implements TaskShortcutFactory {
 
@@ -134,7 +110,7 @@
 
         public MultiWindowSystemShortcut(int iconRes, int textRes, BaseDraggingActivity activity,
                 TaskView taskView, MultiWindowFactory factory, LauncherEvent launcherEvent) {
-            super(iconRes, textRes, activity, dummyInfo(taskView));
+            super(iconRes, textRes, activity, taskView.getItemInfo());
             mLauncherEvent = launcherEvent;
             mHandler = new Handler(Looper.getMainLooper());
             mTaskView = taskView;
@@ -220,7 +196,8 @@
                 WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
                         future, animStartedListener, mHandler, true /* scaleUp */,
                         taskKey.displayId);
-                mTarget.getStatsLogManager().log(mLauncherEvent, mTaskView.buildProto());
+                mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getItemInfo())
+                        .log(mLauncherEvent);
             }
         }
     }
@@ -304,7 +281,7 @@
         private final TaskView mTaskView;
 
         public PinSystemShortcut(BaseDraggingActivity target, TaskView tv) {
-            super(R.drawable.ic_pin, R.string.recent_task_option_pin, target, dummyInfo(tv));
+            super(R.drawable.ic_pin, R.string.recent_task_option_pin, target, tv.getItemInfo());
             mTaskView = tv;
         }
 
@@ -320,16 +297,31 @@
             };
             mTaskView.launchTask(true, resultCallback, Executors.MAIN_EXECUTOR.getHandler());
             dismissTaskMenuView(mTarget);
-            mTarget.getStatsLogManager().log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP,
-                    mTaskView.buildProto());
+            mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getItemInfo())
+                    .log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP);
         }
     }
 
     TaskShortcutFactory INSTALL = (activity, view) ->
             InstantAppResolver.newInstance(activity).isInstantApp(activity,
                  view.getTask().getTopComponent().getPackageName())
-                    ? new SystemShortcut.Install(activity, dummyInfo(view)) : null;
+                    ? new SystemShortcut.Install(activity, view.getItemInfo()) : null;
 
     TaskShortcutFactory WELLBEING = (activity, view) ->
-            WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, dummyInfo(view));
+            WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, view.getItemInfo());
+
+    TaskShortcutFactory SCREENSHOT = (activity, tv) -> {
+        if (ENABLE_OVERVIEW_ACTIONS.get()) {
+            return tv.getThumbnail().getTaskOverlay()
+                .getScreenshotShortcut(activity, tv.getItemInfo());
+        }
+        return null;
+    };
+
+    TaskShortcutFactory MODAL = (activity, tv) -> {
+        if (ENABLE_OVERVIEW_ACTIONS.get() && ENABLE_OVERVIEW_SELECTIONS.get()) {
+            return tv.getThumbnail().getTaskOverlay().getModalStateSystemShortcut(tv.getItemInfo());
+        }
+        return null;
+    };
 }
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 4ca4e4c..37314ea 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -539,6 +539,8 @@
         if (mTaskAnimationManager.isRecentsAnimationRunning()) {
             gestureState.updateRunningTask(mGestureState.getRunningTask());
             gestureState.updateLastStartedTaskId(mGestureState.getLastStartedTaskId());
+            gestureState.updatePreviouslyAppearedTaskIds(
+                    mGestureState.getPreviouslyAppearedTaskIds());
         } else {
             gestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.0",
                     () -> mAM.getRunningTask(false /* filterOnlyVisibleRecents */)));
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 14215a1..a9f138e 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
@@ -267,10 +267,6 @@
                 if (!mPassedSlopOnThisGesture && passedSlop) {
                     mPassedSlopOnThisGesture = true;
                 }
-                // Until passing slop, we don't know what direction we're going, so assume we might
-                // be quick switching to avoid translating recents away when continuing the gesture.
-                boolean isLikelyToStartNewTask = !mPassedSlopOnThisGesture
-                        || horizontalDist > upDist;
 
                 if (!mPassedPilferInputSlop) {
                     if (passedSlop) {
@@ -304,6 +300,13 @@
                     }
 
                     if (mDeviceState.isFullyGesturalNavMode()) {
+                        // Until passing slop, we don't know what direction we're going, so assume
+                        // we're quick switching to avoid translating recents away when continuing
+                        // the gesture.
+                        boolean haveNotPassedSlopOnContinuedGesture =
+                                !mPassedSlopOnThisGesture && mPassedPilferInputSlop;
+                        boolean isLikelyToStartNewTask = haveNotPassedSlopOnContinuedGesture
+                                || horizontalDist > upDist;
                         mMotionPauseDetector.setDisallowPause(upDist < mMotionPauseMinDisplacement
                                 || isLikelyToStartNewTask);
                         mMotionPauseDetector.addPosition(ev);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index 11fee2f..32b1c58 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -36,8 +36,6 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
-import java.util.function.Predicate;
-
 /**
  * Input consumer for handling touch on the recents/Launcher activity.
  */
@@ -50,8 +48,6 @@
     private final InputMonitorCompat mInputMonitor;
 
     private final int[] mLocationOnScreen = new int[2];
-    private final boolean mProxyTouch;
-    private final Predicate<MotionEvent> mEventReceiver;
 
     private final boolean mStartingInActivityBounds;
     private boolean mTargetHandledTouch;
@@ -64,15 +60,7 @@
         mActivityInterface = gestureState.getActivityInterface();
 
         mTarget = activity.getDragLayer();
-        if (startingInActivityBounds) {
-            mEventReceiver = mTarget::dispatchTouchEvent;
-            mProxyTouch = true;
-        } else {
-            // Only proxy touches to controllers if we are starting touch from nav bar.
-            mEventReceiver = mTarget::proxyTouchEvent;
-            mTarget.getLocationOnScreen(mLocationOnScreen);
-            mProxyTouch = mTarget.prepareProxyEventStarting();
-        }
+        mTarget.getLocationOnScreen(mLocationOnScreen);
     }
 
     @Override
@@ -87,10 +75,6 @@
 
     @Override
     public void onMotionEvent(MotionEvent ev) {
-        if (!mProxyTouch) {
-            return;
-        }
-
         int flags = ev.getEdgeFlags();
         if (!mStartingInActivityBounds) {
             ev.setEdgeFlags(flags | Utilities.EDGE_NAV_BAR);
@@ -99,7 +83,7 @@
         if (TestProtocol.sDebugTracing) {
             Log.d(TestProtocol.PAUSE_NOT_DETECTED, "OverviewInputConsumer");
         }
-        boolean handled = mEventReceiver.test(ev);
+        boolean handled = mTarget.proxyTouchEvent(ev, mStartingInActivityBounds);
         ev.offsetLocation(mLocationOnScreen[0], mLocationOnScreen[1]);
         ev.setEdgeFlags(flags);
 
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 8daa982..3cafd42 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
@@ -175,6 +175,11 @@
         return mAnimators;
     }
 
+    public StaggeredWorkspaceAnim addAnimatorListener(Animator.AnimatorListener listener) {
+        mAnimators.addListener(listener);
+        return this;
+    }
+
     /**
      * Starts the animation.
      */
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java
index 6b99f90..0979c07 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java
@@ -232,7 +232,7 @@
                 launcher, parent);
         view.init(launcher);
         launcher.getDragLayer().addView(view);
-        launcher.getStatsLogManager().log(LAUNCHER_ALL_APPS_EDU_SHOWN);
+        launcher.getStatsLogManager().logger().log(LAUNCHER_ALL_APPS_EDU_SHOWN);
 
         view.requestLayout();
         view.playAnimation();
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 95eb10f..a2da398 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
@@ -24,7 +24,6 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.FrameLayout;
@@ -39,6 +38,7 @@
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.TaskOverlayFactory.OverlayUICallbacks;
+import com.android.quickstep.util.LayoutUtils;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -68,6 +68,15 @@
     public static final int HIDDEN_GESTURE_RUNNING = 1 << 4;
     public static final int HIDDEN_NO_RECENTS = 1 << 5;
 
+    @IntDef(flag = true, value = {
+            DISABLED_SCROLLING,
+            DISABLED_ROTATED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ActionsDisabledFlags { }
+
+    public static final int DISABLED_SCROLLING = 1 << 0;
+    public static final int DISABLED_ROTATED = 1 << 1;
+
     private static final int INDEX_CONTENT_ALPHA = 0;
     private static final int INDEX_VISIBILITY_ALPHA = 1;
     private static final int INDEX_FULLSCREEN_ALPHA = 2;
@@ -78,6 +87,9 @@
     @ActionsHiddenFlags
     private int mHiddenFlags;
 
+    @ActionsDisabledFlags
+    protected int mDisabledFlags;
+
     protected T mCallbacks;
 
     public OverviewActionsView(Context context) {
@@ -117,7 +129,6 @@
     @Override
     public void onClick(View view) {
         if (mCallbacks == null) {
-            Log.d("OverviewActionsView", "Callbacks null onClick");
             return;
         }
         int id = view.getId();
@@ -158,6 +169,25 @@
         setVisibility(isHidden ? INVISIBLE : VISIBLE);
     }
 
+    /**
+     * Updates the proper disabled flag to indicate whether OverviewActionsView should be enabled.
+     * Ignores DISABLED_ROTATED flag for determining enabled. Flag is used to enable/disable
+     * buttons individually, currently done for select button in subclass.
+     *
+     * @param disabledFlags The flag to update.
+     * @param enable        Whether to enable the disable flag: True will cause view to be disabled.
+     */
+    public void updateDisabledFlags(@ActionsDisabledFlags int disabledFlags, boolean enable) {
+        if (enable) {
+            mDisabledFlags |= disabledFlags;
+        } else {
+            mDisabledFlags &= ~disabledFlags;
+        }
+        //
+        boolean isEnabled = (mDisabledFlags & ~DISABLED_ROTATED) == 0;
+        LayoutUtils.setViewEnabled(this, isEnabled);
+    }
+
     public AlphaProperty getContentAlpha() {
         return mMultiValueAlpha.getProperty(INDEX_CONTENT_ALPHA);
     }
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 98784ef..885ea5a 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
@@ -30,6 +30,7 @@
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.Utilities.squaredTouchSlop;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -487,7 +488,8 @@
             return;
         }
         mModel.getIconCache().clear();
-        reset();
+        unloadVisibleTaskData();
+        loadVisibleTaskData();
     }
 
     public void init(OverviewActionsView actionsView) {
@@ -607,17 +609,28 @@
         }
     }
 
+    /**
+     * Whether the Clear All button is hidden or fully visible. Used to determine if center
+     * displayed page is a task or the Clear All button.
+     *
+     * @return True = Clear All button not fully visible, center page is a task. False = Clear All
+     * button fully visible, center page is Clear All button.
+     */
+    public boolean isClearAllHidden() {
+        return mClearAllButton.getAlpha() != 1f;
+    }
+
     @Override
     protected void onPageBeginTransition() {
         super.onPageBeginTransition();
-        LayoutUtils.setViewEnabled(mActionsView, false);
+        mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, true);
     }
 
     @Override
     protected void onPageEndTransition() {
         super.onPageEndTransition();
-        if (getScrollX() == getScrollForPage(getPageNearestToCenterOfScreen())) {
-            LayoutUtils.setViewEnabled(mActionsView, true);
+        if (isClearAllHidden()) {
+            mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false);
         }
         if (getNextPage() > 0) {
             setSwipeDownShouldLaunchApp(true);
@@ -1335,8 +1348,8 @@
             ComponentKey compKey = TaskUtils.getLaunchComponentKeyForTask(taskView.getTask().key);
             mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
                     endState.logAction, Direction.UP, index, compKey);
-            mActivity.getStatsLogManager().log(
-                    LAUNCHER_TASK_DISMISS_SWIPE_UP, taskView.buildProto());
+            mActivity.getStatsLogManager().logger().withItemInfo(taskView.getItemInfo())
+                    .log(LAUNCHER_TASK_DISMISS_SWIPE_UP);
         }
     }
 
@@ -1716,11 +1729,13 @@
         setPivotY(mTempPointF.y);
         setTaskModalness(mTaskModalness);
         updatePageOffsets();
+        setImportantForAccessibility(isModal() ? IMPORTANT_FOR_ACCESSIBILITY_NO
+                : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
     }
 
     private void updatePageOffsets() {
         float offset = mAdjacentPageOffset * getWidth();
-        float modalOffset = mTaskModalness * getWidth();
+        float modalOffset = ACCEL_0_75.getInterpolation(mTaskModalness) * getWidth();
         if (mIsRtl) {
             offset = -offset;
             modalOffset = -modalOffset;
@@ -1748,6 +1763,16 @@
         return Math.max(getWidth(), 1);
     }
 
+    /**
+     * Resets the visuals when exit modal state.
+     */
+    public void resetModalVisuals() {
+        TaskView taskView = getCurrentPageTaskView();
+        if (taskView != null) {
+            taskView.getThumbnail().getTaskOverlay().resetModalVisuals();
+        }
+    }
+
     private void updateDeadZoneRects() {
         // Get the deadzone rect surrounding the clear all button to not dismiss overview to home
         mClearAllButtonDeadZoneRect.setEmpty();
@@ -1918,8 +1943,8 @@
                     mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
                             endState.logAction, Direction.DOWN, indexOfChild(tv),
                             TaskUtils.getLaunchComponentKeyForTask(task.key));
-                    mActivity.getStatsLogManager().log(
-                            LAUNCHER_TASK_LAUNCH_SWIPE_DOWN, tv.buildProto());
+                    mActivity.getStatsLogManager().logger().withItemInfo(tv.getItemInfo())
+                            .log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN);
                 }
             } else {
                 onTaskLaunched(false);
@@ -2089,6 +2114,12 @@
         return mClearAllButton;
     }
 
+    @Override
+    protected boolean onOverscroll(int amount) {
+        // overscroll should only be accepted on -1 direction (for clear all button)
+        if ((amount > 0 && !mIsRtl) || (amount < 0 && mIsRtl)) return false;
+        return super.onOverscroll(amount);
+    }
 
     /**
      * @return How many pixels the running task is offset on the currently laid out dominant axis.
@@ -2182,6 +2213,10 @@
         if (getCurrentPageTaskView() != null) {
             getCurrentPageTaskView().setModalness(modalness);
         }
+        // Only show actions view when it's modal for in-place landscape mode.
+        boolean inPlaceLandscape = !mOrientationState.canLauncherRotate()
+                && mOrientationState.getTouchRotation() != ROTATION_0;
+        mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION, modalness < 1 && inPlaceLandscape);
     }
 
     @Nullable
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
index a371dd2..b2f937f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -357,10 +357,9 @@
     }
 
     private void updateOverlay() {
-        // The overlay doesn't really work when the screenshot is rotated, so don't add it.
-        if (mOverlayEnabled && !mPreviewPositionHelper.mIsOrientationChanged
-                && mBitmapShader != null && mThumbnailData != null) {
-            mOverlay.initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.mMatrix);
+        if (mOverlayEnabled && mBitmapShader != null && mThumbnailData != null) {
+            mOverlay.initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.mMatrix,
+                    mPreviewPositionHelper.mIsOrientationChanged);
         } else {
             mOverlay.reset();
         }
@@ -448,6 +447,16 @@
     }
 
     /**
+     * Returns whether the snapshot is real.
+     */
+    public boolean isRealSnapshot() {
+        if (mThumbnailData == null) {
+            return false;
+        }
+        return mThumbnailData.isRealSnapshot;
+    }
+
+    /**
      * Utility class to position the thumbnail in the TaskView
      */
     public static class PreviewPositionHelper {
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 cadf6c4..411bab4 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
@@ -40,6 +40,7 @@
 import android.animation.ValueAnimator;
 import android.app.ActivityOptions;
 import android.content.Context;
+import android.content.Intent;
 import android.graphics.Outline;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -48,7 +49,6 @@
 import android.graphics.drawable.InsetDrawable;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.Process;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.Log;
@@ -61,13 +61,14 @@
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
@@ -213,7 +214,8 @@
             mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
                     Touch.TAP, Direction.NONE, getRecentsView().indexOfChild(this),
                     TaskUtils.getLaunchComponentKeyForTask(getTask().key));
-            mActivity.getStatsLogManager().log(LAUNCHER_TASK_LAUNCH_TAP, buildProto());
+            mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo())
+                    .log(LAUNCHER_TASK_LAUNCH_TAP);
         });
 
         mCurrentFullscreenParams = new FullscreenDrawParams(context);
@@ -226,14 +228,16 @@
     /**
      * Builds proto for logging
      */
-    public LauncherAtom.ItemInfo buildProto() {
+    public WorkspaceItemInfo getItemInfo() {
         ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(getTask().key);
-        LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder();
-        itemBuilder.setIsWork(componentKey.user != Process.myUserHandle());
-        itemBuilder.setTask(LauncherAtom.Task.newBuilder()
-                .setComponentName(componentKey.componentName.flattenToShortString())
-                .setIndex(getRecentsView().indexOfChild(this)));
-        return itemBuilder.build();
+        WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo();
+        dummyInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK;
+        dummyInfo.container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER;
+        dummyInfo.user = componentKey.user;
+        dummyInfo.intent = new Intent().setComponent(componentKey.componentName);
+        dummyInfo.title = TaskUtils.getTitle(getContext(), getTask());
+        dummyInfo.screenId = getRecentsView().indexOfChild(this);
+        return dummyInfo;
     }
 
     @Override
@@ -425,13 +429,17 @@
     }
 
     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) {
-            mMenuView.addOnAttachStateChangeListener(mTaskMenuStateListener);
+        if (!getRecentsView().isClearAllHidden()) {
+            getRecentsView().snapToPage(getRecentsView().indexOfChild(this));
+        } else {
+            mMenuView = TaskMenuView.showForTask(this);
+            mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo())
+                    .log(LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS);
+            UserEventDispatcher.newInstance(getContext()).logActionOnItem(action, Direction.NONE,
+                    LauncherLogProto.ItemType.TASK_ICON);
+            if (mMenuView != null) {
+                mMenuView.addOnAttachStateChangeListener(mTaskMenuStateListener);
+            }
         }
         return mMenuView != null;
     }
@@ -969,6 +977,9 @@
     }
 
     void updateCurrentFullscreenParams(PreviewPositionHelper previewPositionHelper) {
+        if (getRecentsView() == null) {
+            return;
+        }
         mCurrentFullscreenParams.setProgress(
                 mFullscreenProgress,
                 getRecentsView().getScaleX(),
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index a27c127..1b82826 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -76,8 +76,8 @@
     <!-- Button text to dismiss opt in for fully predicted hotseat -->
     <string name="hotseat_edu_dismiss">No thanks</string>
 
-    <!-- action shown to turn off predictions after onboarding -->
-    <string name="hotseat_turn_off">Settings</string>
+    <!-- action shown to toggle predictions after onboarding -->
+    <string name="hotseat_prediction_settings">Settings</string>
 
     <!-- tip shown if user has no items in hotseat to migrate -->
     <string name="hotseat_auto_enrolled">Most-used apps appear here, and change based on routines</string>
@@ -86,7 +86,9 @@
     <!-- 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>
+    <string name="hotsaet_tip_prediction_enabled">App suggestions enabled</string>
+    <!-- tip shown when hotseat edu is requested while predicions are disabled -->
+    <string name="hotsaet_tip_prediction_disabled">App suggestions are disabled</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>
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 4874307..d2e0339 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -31,6 +31,7 @@
 import android.os.CancellationSignal;
 
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.hybridhotseat.HotseatPredictionController;
 import com.android.launcher3.model.WellbeingModel;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.proxy.ProxyActivityStarter;
@@ -75,6 +76,7 @@
     private final ShelfPeekAnim mShelfPeekAnim = new ShelfPeekAnim(this);
 
     private OverviewActionsView mActionsView;
+    protected HotseatPredictionController mHotseatPredictionController;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -305,6 +307,13 @@
         return mShelfPeekAnim;
     }
 
+    /**
+     * Returns Prediction controller for hybrid hotseat
+     */
+    public HotseatPredictionController getHotseatPredictionController() {
+        return mHotseatPredictionController;
+    }
+
     public void setHintUserWillBeActive() {
         addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE);
     }
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index ebe9e26..2b08dcd 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -32,6 +32,7 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.states.StateAnimationConfig;
+import com.android.systemui.shared.system.BlurUtils;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.SurfaceControlCompat;
 import com.android.systemui.shared.system.TransactionCompat;
@@ -110,6 +111,10 @@
     }
 
     private void ensureDependencies() {
+        if (mWallpaperManager == null) {
+            mMaxBlurRadius = mLauncher.getResources().getInteger(R.integer.max_depth_blur_radius);
+            mWallpaperManager = new WallpaperManagerCompat(mLauncher);
+        }
         if (mLauncher.getRootView() != null && mOnAttachListener == null) {
             mOnAttachListener = new View.OnAttachStateChangeListener() {
                 @Override
@@ -127,11 +132,6 @@
             };
             mLauncher.getRootView().addOnAttachStateChangeListener(mOnAttachListener);
         }
-        if (mWallpaperManager != null) {
-            return;
-        }
-        mMaxBlurRadius = mLauncher.getResources().getInteger(R.integer.max_depth_blur_radius);
-        mWallpaperManager = new WallpaperManagerCompat(mLauncher);
     }
 
     /**
@@ -205,7 +205,8 @@
             return;
         }
 
-        if (mSurface == null || !mSurface.isValid()) {
+        boolean supportsBlur = BlurUtils.supportsBlursOnWindows();
+        if (supportsBlur && (mSurface == null || !mSurface.isValid())) {
             return;
         }
         mDepth = depthF;
@@ -214,17 +215,20 @@
         if (windowToken != null) {
             mWallpaperManager.setWallpaperZoomOut(windowToken, mDepth);
         }
-        final int blur;
-        if (mLauncher.isInState(LauncherState.ALL_APPS) && mDepth == 1) {
-            // All apps has a solid background. We don't need to draw blurs after it's fully
-            // visible. This will take us out of GPU composition, saving battery and increasing
-            // performance.
-            blur = 0;
-        } else {
-            blur = (int) (mDepth * mMaxBlurRadius);
+
+        if (supportsBlur) {
+            final int blur;
+            if (mLauncher.isInState(LauncherState.ALL_APPS) && mDepth == 1) {
+                // All apps has a solid background. We don't need to draw blurs after it's fully
+                // visible. This will take us out of GPU composition, saving battery and increasing
+                // performance.
+                blur = 0;
+            } else {
+                blur = (int) (mDepth * mMaxBlurRadius);
+            }
+            new TransactionCompat()
+                    .setBackgroundBlurRadius(mSurface, blur)
+                    .apply();
         }
-        new TransactionCompat()
-                .setBackgroundBlurRadius(mSurface, blur)
-                .apply();
     }
 }
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 188072a..00b5eb9 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -30,6 +30,8 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * Manages the state for an active system gesture, listens for events from the system and Launcher,
@@ -128,6 +130,7 @@
     private ActivityManager.RunningTaskInfo mRunningTask;
     private GestureEndTarget mEndTarget;
     private RemoteAnimationTargetCompat mLastAppearedTaskTarget;
+    private Set<Integer> mPreviouslyAppearedTaskIds = new HashSet<>();
     private int mLastStartedTaskId = -1;
 
     public GestureState(OverviewComponentObserver componentObserver, int gestureId) {
@@ -147,6 +150,7 @@
         mRunningTask = other.mRunningTask;
         mEndTarget = other.mEndTarget;
         mLastAppearedTaskTarget = other.mLastAppearedTaskTarget;
+        mPreviouslyAppearedTaskIds = other.mPreviouslyAppearedTaskIds;
         mLastStartedTaskId = other.mLastStartedTaskId;
     }
 
@@ -234,6 +238,9 @@
      */
     public void updateLastAppearedTaskTarget(RemoteAnimationTargetCompat lastAppearedTaskTarget) {
         mLastAppearedTaskTarget = lastAppearedTaskTarget;
+        if (lastAppearedTaskTarget != null) {
+            mPreviouslyAppearedTaskIds.add(lastAppearedTaskTarget.taskId);
+        }
     }
 
     /**
@@ -243,6 +250,14 @@
         return mLastAppearedTaskTarget != null ? mLastAppearedTaskTarget.taskId : -1;
     }
 
+    public void updatePreviouslyAppearedTaskIds(Set<Integer> previouslyAppearedTaskIds) {
+        mPreviouslyAppearedTaskIds = previouslyAppearedTaskIds;
+    }
+
+    public Set<Integer> getPreviouslyAppearedTaskIds() {
+        return mPreviouslyAppearedTaskIds;
+    }
+
     /**
      * Updates the last task that we started via startActivityFromRecents() during this gesture.
      */
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 07aed52..79b38f2 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -16,6 +16,7 @@
 package com.android.quickstep;
 
 import static android.content.Intent.ACTION_USER_UNLOCKED;
+import static android.view.Surface.ROTATION_0;
 
 import static com.android.launcher3.util.DefaultDisplay.CHANGE_ALL;
 import static com.android.launcher3.util.DefaultDisplay.CHANGE_FRAME_DELAY;
@@ -48,15 +49,18 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.view.MotionEvent;
+import android.view.OrientationEventListener;
 
 import androidx.annotation.BinderThread;
 
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.testing.TestProtocol;
 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.quickstep.util.RecentsOrientedState;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
@@ -112,9 +116,53 @@
             }
             enableMultipleRegions(false);
         }
+
+        @Override
+        public void onActivityRotation(int displayId) {
+            super.onActivityRotation(displayId);
+            // This always gets called before onDisplayInfoChanged() so we know how to process
+            // the rotation in that method. This is done to avoid having a race condition between
+            // the sensor readings and onDisplayInfoChanged() call
+            if (displayId != mDisplayId) {
+                return;
+            }
+
+            mPrioritizeDeviceRotation = true;
+            if (mInOverview) {
+                // reset, launcher must be rotating
+                mExitOverviewRunnable.run();
+            }
+        }
+    };
+
+    private Runnable mExitOverviewRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mInOverview = false;
+            enableMultipleRegions(false);
+        }
     };
 
     private OrientationTouchTransformer mOrientationTouchTransformer;
+    /**
+     * Used to listen for when the device rotates into the orientation of the current
+     * foreground app. For example, if a user quickswitches from a portrait to a fixed landscape
+     * app and then rotates rotates the device to match that orientation, this triggers calls to
+     * sysui to adjust the navbar.
+     */
+    private OrientationEventListener mOrientationListener;
+    private int mPreviousRotation = ROTATION_0;
+    /**
+     * This is the configuration of the foreground app or the app that will be in the foreground
+     * once a quickstep gesture finishes.
+     */
+    private int mCurrentAppRotation = -1;
+    /**
+     * This flag is set to true when the device physically changes orientations. When true,
+     * we will always report the current rotation of the foreground app whenever the display
+     * changes, as it would indicate the user's intention to rotate the foreground app.
+     */
+    private boolean mPrioritizeDeviceRotation = false;
 
     private Region mExclusionRegion;
     private SystemGestureExclusionListenerCompat mExclusionListener;
@@ -193,6 +241,26 @@
             userSetupObserver.register();
             runOnDestroy(userSetupObserver::unregister);
         }
+
+        mOrientationListener = new OrientationEventListener(context) {
+            @Override
+            public void onOrientationChanged(int degrees) {
+                int newRotation = RecentsOrientedState.getRotationForUserDegreesRotated(degrees,
+                        mPreviousRotation);
+                if (newRotation == mPreviousRotation) {
+                    return;
+                }
+
+                mPreviousRotation = newRotation;
+                mPrioritizeDeviceRotation = true;
+
+                if (newRotation == mCurrentAppRotation) {
+                    // When user rotates device to the orientation of the foreground app after
+                    // quickstepping
+                    toggleSecondaryNavBarsForRotation(false);
+                }
+            }
+        };
     }
 
     private void setupOrientationSwipeHandler() {
@@ -268,6 +336,18 @@
         mNavBarPosition = new NavBarPosition(mMode, info);
         updateGestureTouchRegions();
         mOrientationTouchTransformer.createOrAddTouchRegion(info);
+        mCurrentAppRotation = mDisplayRotation;
+
+        /* Update nav bars on the following:
+         * a) if we're not expecting quickswitch, this is coming from an activity rotation
+         * b) we launch an app in the orientation that user is already in
+         * c) We're not in overview, since overview will always be portrait (w/o home rotation)
+         */
+        if ((mPrioritizeDeviceRotation
+                || mCurrentAppRotation == mPreviousRotation) // switch to an app of orientation user is in
+                && !mInOverview) {
+            toggleSecondaryNavBarsForRotation(false);
+        }
     }
 
     /**
@@ -553,9 +633,13 @@
         mOrientationTouchTransformer.transform(event);
     }
 
-    void enableMultipleRegions(boolean enable) {
-        mOrientationTouchTransformer.enableMultipleRegions(enable, mDefaultDisplay.getInfo());
-        notifySysuiForRotation(mOrientationTouchTransformer.getQuickStepStartingRotation());
+    private void enableMultipleRegions(boolean enable) {
+        toggleSecondaryNavBarsForRotation(enable);
+        if (enable && !TestProtocol.sDisableSensorRotation) {
+            mOrientationListener.enable();
+        } else {
+            mOrientationListener.disable();
+        }
     }
 
     private void notifySysuiForRotation(int rotation) {
@@ -581,10 +665,7 @@
                 // If we're in landscape w/o ever quickswitching, show the navbar in landscape
                 enableMultipleRegions(true);
             }
-            activityInterface.onExitOverview(this, () -> {
-                mInOverview = false;
-                enableMultipleRegions(false);
-            });
+            activityInterface.onExitOverview(this, mExitOverviewRunnable);
         } else if (endTarget == GestureState.GestureEndTarget.HOME) {
             enableMultipleRegions(false);
         } else if (endTarget == GestureState.GestureEndTarget.NEW_TASK) {
@@ -594,6 +675,11 @@
             } else {
                 notifySysuiForRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
             }
+
+            // A new gesture is starting, reset the current device rotation
+            // This is done under the assumption that the user won't rotate the phone and then
+            // quickswitch in the old orientation.
+            mPrioritizeDeviceRotation = false;
         } else if (endTarget == GestureState.GestureEndTarget.LAST_TASK) {
             if (!mTaskListFrozen) {
                 // touched nav bar but didn't go anywhere and not quickswitching, do nothing
@@ -603,7 +689,24 @@
         }
     }
 
-    int getCurrentActiveRotation() {
+    private void notifySysuiOfCurrentRotation(int rotation) {
+        UI_HELPER_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(mContext)
+                .onQuickSwitchToNewTask(rotation));
+    }
+
+    /**
+     * Disables/Enables multiple nav bars on {@link OrientationTouchTransformer} and then
+     * notifies system UI of the primary rotation the user is interacting with
+     *
+     * @param enable if {@code true}, this will report to sysUI the navbar of the region the gesture
+     *               started in (during ACTION_DOWN), otherwise will report {@param displayRotation}
+     */
+    private void toggleSecondaryNavBarsForRotation(boolean enable) {
+        mOrientationTouchTransformer.enableMultipleRegions(enable, mDefaultDisplay.getInfo());
+        notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getQuickStepStartingRotation());
+    }
+
+    public int getCurrentActiveRotation() {
         if (!mMode.hasGestures) {
             // touch rotation should always match that of display for 3 button
             return mDisplayRotation;
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index be8eb48..0863aa2 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -30,7 +30,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherState;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.InstanceId;
@@ -47,6 +46,7 @@
 import com.android.systemui.shared.system.SysUiStatsLog;
 
 import java.util.ArrayList;
+import java.util.OptionalInt;
 
 /**
  * This class calls StatsLog compile time generated methods.
@@ -64,45 +64,50 @@
     private static Context sContext;
 
     private static final InstanceId DEFAULT_INSTANCE_ID = InstanceId.fakeInstanceId(0);
+    // LauncherAtom.ItemInfo.getDefaultInstance() should be used but until launcher proto migrates
+    // from nano to lite, bake constant to prevent robo test failure.
+    private static final int DEFAULT_PAGE_INDEX = -2;
     private static final int FOLDER_HIERARCHY_OFFSET = 100;
 
     public StatsLogCompatManager(Context context) {
         sContext = context;
     }
 
-    /**
-     * Logs a {@link EventEnum}.
-     */
     @Override
-    public void log(EventEnum event) {
-        log(event, DEFAULT_INSTANCE_ID, LauncherAtom.ItemInfo.getDefaultInstance());
+    public StatsLogger logger() {
+        return new StatsCompatLogger();
     }
 
     /**
-     * Logs an event and accompanying {@link InstanceId}.
+     * Logs an event.
+     *
+     * @param event an enum implementing EventEnum interface.
+     * @param atomInfo item typically containing app or task launch related information.
      */
-    @Override
-    public void log(EventEnum event, InstanceId instanceId) {
-        log(event, instanceId, LauncherAtom.ItemInfo.getDefaultInstance());
+    public void log(EventEnum event, InstanceId instanceId, LauncherAtom.ItemInfo atomInfo) {
+        LauncherAppState.getInstance(sContext).getModel().enqueueModelUpdateTask(
+                new BaseModelUpdateTask() {
+                    @Override
+                    public void execute(LauncherAppState app, BgDataModel dataModel,
+                            AllAppsList apps) {
+                        write(event, instanceId, atomInfo, LAUNCHER_UICHANGED__DST_STATE__HOME,
+                                LAUNCHER_UICHANGED__DST_STATE__BACKGROUND, OptionalInt.empty());
+                    }
+                });
     }
 
     /**
-     * Logs an event and accompanying {@link ItemInfo}.
+     * Logs an event.
+     *
+     * @param event an enum implementing EventEnum interface.
+     * @param atomItemInfo item typically containing app or task launch related information.
      */
     @Override
-    public void log(EventEnum event, @Nullable LauncherAtom.ItemInfo info) {
-        log(event, DEFAULT_INSTANCE_ID, info);
-    }
-
-    /**
-     * Logs an event and accompanying {@link InstanceId} and {@link LauncherAtom.ItemInfo}.
-     */
-    @Override
-    public void log(EventEnum event, InstanceId instanceId,
-            @Nullable LauncherAtom.ItemInfo info) {
-        logInternal(event, instanceId, info,
-                LAUNCHER_UICHANGED__DST_STATE__HOME,
-                LAUNCHER_UICHANGED__DST_STATE__BACKGROUND);
+    public void log(EventEnum event, @Nullable LauncherAtom.ItemInfo atomItemInfo, int srcState,
+            int dstState) {
+        write(event, DEFAULT_INSTANCE_ID,
+                atomItemInfo == null ? LauncherAtom.ItemInfo.getDefaultInstance() : atomItemInfo,
+                srcState, dstState, OptionalInt.empty());
     }
 
     /**
@@ -118,43 +123,19 @@
                 position /* position_picked = 4; */);
     }
 
-    /**
-     * Logs an event and accompanying {@link LauncherState}s. If either of the state refers
-     * to workspace state, then use pageIndex to pass in index of workspace.
-     */
-    @Override
-    public void log(EventEnum event, int srcState, int dstState, int pageIndex) {
-        LauncherAtom.ItemInfo info = LauncherAtom.ItemInfo.getDefaultInstance();
-        if (srcState == LAUNCHER_UICHANGED__DST_STATE__HOME
-                || dstState == LAUNCHER_UICHANGED__SRC_STATE__HOME) {
-            info = LauncherAtom.ItemInfo.newBuilder().setContainerInfo(
-                    LauncherAtom.ContainerInfo.newBuilder().setWorkspace(
-                            LauncherAtom.WorkspaceContainer.newBuilder().setPageIndex(pageIndex)
-                    )).build();
-        }
-        logInternal(event, DEFAULT_INSTANCE_ID, info, srcState, dstState);
-    }
-
-    /**
-     * Logs an event and accompanying {@link InstanceId} and {@link LauncherAtom.ItemInfo}.
-     */
-    private void logInternal(EventEnum event, InstanceId instanceId,
-            @Nullable LauncherAtom.ItemInfo info, int srcState, int dstState) {
-        info = info == null ? LauncherAtom.ItemInfo.getDefaultInstance() : info;
-
+    private  void write(EventEnum event, InstanceId instanceId,
+            LauncherAtom.ItemInfo atomInfo,
+            int srcState, int dstState, OptionalInt mRank) {
         if (IS_VERBOSE) {
-            String name = (event instanceof LauncherEvent) ? ((LauncherEvent) event).name() :
+            String name = (event instanceof Enum) ? ((Enum) event).name() :
                     event.getId() + "";
 
             Log.d(TAG, instanceId == DEFAULT_INSTANCE_ID
-                    ? String.format("\n%s (State:%s->%s) \n%s", name, getStateString(srcState),
-                            getStateString(dstState), info)
-                    : String.format("\n%s (State:%s->%s) (InstanceId:%s)\n%s", name, instanceId,
-                            getStateString(srcState), getStateString(dstState), info));
-        }
-
-        if (!Utilities.ATLEAST_R) {
-            return;
+                    ? String.format("\n%s (State:%s->%s)\n%s", name, getStateString(srcState),
+                    getStateString(dstState),  atomInfo)
+                    : String.format("\n%s (State:%s->%s) (InstanceId:%s)\n%s", name,
+                            getStateString(srcState), getStateString(dstState), instanceId,
+                            atomInfo));
         }
 
         SysUiStatsLog.write(
@@ -165,24 +146,24 @@
                 null /* launcher extensions, deprecated */,
                 false /* quickstep_enabled, deprecated */,
                 event.getId() /* event_id */,
-                info.getItemCase().getNumber() /* target_id */,
+                atomInfo.getItemCase().getNumber() /* target_id */,
                 instanceId.getId() /* instance_id TODO */,
                 0 /* uid TODO */,
-                getPackageName(info) /* package_name */,
-                getComponentName(info) /* component_name */,
-                getGridX(info, false) /* grid_x */,
-                getGridY(info, false) /* grid_y */,
-                getPageId(info, false) /* page_id */,
-                getGridX(info, true) /* grid_x_parent */,
-                getGridY(info, true) /* grid_y_parent */,
-                getPageId(info, true) /* page_id_parent */,
-                getHierarchy(info) /* hierarchy */,
-                info.getIsWork() /* is_work_profile */,
-                info.getRank() /* rank */,
-                info.getFolderIcon().getFromLabelState().getNumber() /* fromState */,
-                info.getFolderIcon().getToLabelState().getNumber() /* toState */,
-                info.getFolderIcon().getLabelInfo() /* edittext */,
-                getCardinality(info) /* cardinality */);
+                getPackageName(atomInfo) /* package_name */,
+                getComponentName(atomInfo) /* component_name */,
+                getGridX(atomInfo, false) /* grid_x */,
+                getGridY(atomInfo, false) /* grid_y */,
+                getPageId(atomInfo, false) /* page_id */,
+                getGridX(atomInfo, true) /* grid_x_parent */,
+                getGridY(atomInfo, true) /* grid_y_parent */,
+                getPageId(atomInfo, true) /* page_id_parent */,
+                getHierarchy(atomInfo) /* hierarchy */,
+                atomInfo.getIsWork() /* is_work_profile */,
+                mRank.orElse(atomInfo.getRank()) /* rank */,
+                atomInfo.getFolderIcon().getFromLabelState().getNumber() /* fromState */,
+                atomInfo.getFolderIcon().getToLabelState().getNumber() /* toState */,
+                atomInfo.getFolderIcon().getLabelInfo() /* edittext */,
+                getCardinality(atomInfo) /* cardinality */);
     }
 
     /**
@@ -251,6 +232,72 @@
                 info.getWidget().getSpanY());
     }
 
+    /**
+     * Helps to construct and write statsd compatible log message.
+     */
+    private class StatsCompatLogger implements StatsLogger {
+        private ItemInfo mItemInfo = new ItemInfo();
+        private InstanceId mInstanceId = DEFAULT_INSTANCE_ID;
+        private OptionalInt mRank = OptionalInt.empty();
+        private int mSrcState = LAUNCHER_UICHANGED__SRC_STATE__HOME;
+        private int mDstState = LAUNCHER_UICHANGED__DST_STATE__BACKGROUND;
+
+        @Override
+        public StatsLogger withItemInfo(ItemInfo itemInfo) {
+            this.mItemInfo = itemInfo;
+            return this;
+        }
+
+        @Override
+        public StatsLogger withInstanceId(InstanceId instanceId) {
+            this.mInstanceId = instanceId;
+            return this;
+        }
+
+        @Override
+        public StatsLogger withRank(int rank) {
+            this.mRank = OptionalInt.of(rank);
+            return this;
+        }
+
+        @Override
+        public StatsLogger withSrcState(int srcState) {
+            this.mSrcState = srcState;
+            return this;
+        }
+
+        @Override
+        public StatsLogger withDstState(int dstState) {
+            this.mDstState = dstState;
+            return this;
+        }
+
+        @Override
+        public void log(EventEnum event) {
+            if (!Utilities.ATLEAST_R) {
+                return;
+            }
+
+            if (mItemInfo.container < 0) {
+                // Item is not within a folder. Write to StatsLog in same thread.
+                write(event, mInstanceId, mItemInfo.buildProto(), mSrcState, mDstState, mRank);
+            } else {
+                // Item is inside the folder, fetch folder info in a BG thread
+                // and then write to StatsLog.
+                LauncherAppState.getInstance(sContext).getModel().enqueueModelUpdateTask(
+                        new BaseModelUpdateTask() {
+                            @Override
+                            public void execute(LauncherAppState app, BgDataModel dataModel,
+                                    AllAppsList apps) {
+                                FolderInfo folderInfo = dataModel.folders.get(mItemInfo.container);
+                                write(event, mInstanceId, mItemInfo.buildProto(folderInfo),
+                                        mSrcState, mDstState, mRank);
+                            }
+                        });
+            }
+        }
+    }
+
     private static int getCardinality(LauncherAtom.ItemInfo info) {
         return info.getContainerInfo().getContainerCase().equals(PREDICTED_HOTSEAT_CONTAINER)
                 ? info.getContainerInfo().getPredictedHotseatContainer().getCardinality()
@@ -333,7 +380,7 @@
     }
 
     private static String getStateString(int state) {
-        switch(state) {
+        switch (state) {
             case LAUNCHER_UICHANGED__DST_STATE__BACKGROUND:
                 return "BACKGROUND";
             case LAUNCHER_UICHANGED__DST_STATE__HOME:
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index 3f58e01..cefab1b 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -74,17 +74,14 @@
 
     /**
      * Recursively sets view and all children enabled/disabled.
-     * @param viewGroup Top most parent view to change.
+     * @param view Top most parent view to change.
      * @param enabled True = enable, False = disable.
      */
-    public static void setViewEnabled(ViewGroup viewGroup, boolean enabled) {
-        viewGroup.setEnabled(enabled);
-        for (int i = 0; i < viewGroup.getChildCount(); i++) {
-            View child = viewGroup.getChildAt(i);
-            if (child instanceof ViewGroup) {
-                setViewEnabled((ViewGroup) child, enabled);
-            } else {
-                child.setEnabled(enabled);
+    public static void setViewEnabled(View view, boolean enabled) {
+        view.setEnabled(enabled);
+        if (view instanceof ViewGroup) {
+            for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
+                setViewEnabled(((ViewGroup) view).getChildAt(i), enabled);
             }
         }
     }
diff --git a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
index 7e8222c..1abe903 100644
--- a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
+++ b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
@@ -31,6 +31,7 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.hybridhotseat.HotseatPredictionController;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.launcher3.util.OnboardingPrefs;
@@ -100,6 +101,28 @@
             });
         }
 
+        if (!hasReachedMaxCount(HOTSEAT_DISCOVERY_TIP_COUNT)) {
+            stateManager.addStateListener(new StateListener<LauncherState>() {
+                boolean mFromAllApps = false;
+
+                @Override
+                public void onStateTransitionStart(LauncherState toState) {
+                    mFromAllApps = mLauncher.getStateManager().getCurrentStableState() == ALL_APPS;
+                }
+
+                @Override
+                public void onStateTransitionComplete(LauncherState finalState) {
+                    HotseatPredictionController client = mLauncher.getHotseatPredictionController();
+                    if (mFromAllApps && finalState == NORMAL && client.hasPredictions()) {
+                        if (incrementEventCount(HOTSEAT_DISCOVERY_TIP_COUNT)) {
+                            client.showDiscoveryTip();
+                            stateManager.removeStateListener(this);
+                        }
+                    }
+                }
+            });
+        }
+
         if (SysUINavigationMode.getMode(launcher) == NO_BUTTON
                 && FeatureFlags.ENABLE_ALL_APPS_EDU.get()) {
             stateManager.addStateListener(new StateListener<LauncherState>() {
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index 7715cca..8bd2281 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -26,8 +26,8 @@
 import static com.android.launcher3.logging.LoggerUtils.extractObjectNameAndAddress;
 import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
 import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
+
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.content.ContentResolver;
@@ -49,6 +49,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.PagedOrientationHandler;
@@ -197,7 +198,7 @@
         mPreviousRotation = touchRotation;
 
         if (mLauncherRotation == mTouchRotation || canLauncherRotate()) {
-            mOrientationHandler = PagedOrientationHandler.HOME_ROTATED;
+            mOrientationHandler = PagedOrientationHandler.PORTRAIT;
             if (DEBUG) {
                 Log.d(TAG, "current RecentsOrientedState: " + this);
             }
@@ -445,7 +446,8 @@
                 }
                 break;
             case ROTATION_270:
-                if (degrees < (90 - threshold)) {
+                if (degrees < (90 - threshold) ||
+                        (degrees > (270 + threshold) && degrees < 360)) {
                     return ROTATION_0;
                 }
                 if (degrees > (90 + threshold) && degrees < 180) {
@@ -468,7 +470,8 @@
                 if (degrees < (270 - threshold) && degrees > 90) {
                     return ROTATION_180;
                 }
-                if (degrees > (270 + threshold) && degrees < 360) {
+                if (degrees > (270 + threshold) && degrees < 360
+                        || (degrees >= 0 && degrees < threshold)) {
                     return ROTATION_0;
                 }
                 // flip from landscape to seascape
@@ -524,4 +527,15 @@
                 + " mFlags=" + mFlags
                 + "]";
     }
+
+    /**
+     * Returns the device profile based on expected launcher rotation
+     */
+    public DeviceProfile getLauncherDeviceProfile() {
+        InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mContext);
+        // TODO also check the natural orientation is landscape or portrait
+        return  (mLauncherRotation == ROTATION_90 || mLauncherRotation == ROTATION_270)
+                ? idp.landscapeProfile
+                : idp.portraitProfile;
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
index f5498c9..19e278b 100644
--- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
+++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
@@ -23,6 +23,7 @@
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
+import static com.android.launcher3.util.SystemUiController.UI_STATE_SCRIM_VIEW;
 
 import android.content.Context;
 import android.graphics.Canvas;
@@ -187,6 +188,7 @@
             mShelfTopAtThreshold = mShiftRange * SCRIM_CATCHUP_THRESHOLD + mTopOffset;
         }
         updateColors();
+        updateSysUiColors();
         updateDragHandleAlpha();
         invalidate();
     }
@@ -241,6 +243,22 @@
     }
 
     @Override
+    protected void updateSysUiColors() {
+        if (mDrawingFlatColor) {
+            super.updateSysUiColors();
+        } else {
+            // Use a light system UI (dark icons) if all apps is behind at least half of the
+            // status bar.
+            boolean forceChange = mShelfTop <= mLauncher.getDeviceProfile().getInsets().top / 2f;
+            if (forceChange) {
+                mLauncher.getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, !mIsScrimDark);
+            } else {
+                mLauncher.getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, 0);
+            }
+        }
+    }
+
+    @Override
     protected boolean shouldDragHandleBeVisible() {
         boolean needsAllAppsEdu = mIsTwoZoneSwipeModel
                 && !mOnboardingPrefs.hasReachedMaxCount(OnboardingPrefs.ALL_APPS_COUNT);
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index a726052..bd8ab08 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -37,7 +37,6 @@
 import static org.junit.Assert.assertTrue;
 
 import android.app.Instrumentation;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -58,10 +57,8 @@
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.rule.FailureRewriterRule;
 import com.android.launcher3.util.rule.FailureWatcher;
-import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 import com.android.quickstep.views.RecentsView;
 
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.RuleChain;
@@ -95,7 +92,8 @@
         mDevice = UiDevice.getInstance(instrumentation);
         mDevice.setOrientationNatural();
         mLauncher = new LauncherInstrumentation();
-        mLauncher.enableCheckEventsForSuccessfulGestures();
+        // b/143488140
+        //mLauncher.enableCheckEventsForSuccessfulGestures();
 
         if (TestHelpers.isInLauncherProcess()) {
             Utilities.enableRunningInTestHarnessForTests();
@@ -122,6 +120,9 @@
                     TestCommandReceiver.callCommand(TestCommandReceiver.DISABLE_TEST_LAUNCHER);
                     UiDevice.getInstance(getInstrumentation()).executeShellCommand(
                             getLauncherCommand(getLauncherInMyProcess()));
+                    // b/143488140
+                    mDevice.pressHome();
+                    mDevice.waitForIdle();
                 }
             }
         };
@@ -132,9 +133,9 @@
         }
     }
 
-    @NavigationModeSwitch
+    // b/143488140
+    //@NavigationModeSwitch
     @Test
-    @Ignore // b/143488140
     public void goToOverviewFromHome() {
         mDevice.pressHome();
         assertTrue("Fallback Launcher not visible", mDevice.wait(Until.hasObject(By.pkg(
@@ -143,9 +144,9 @@
         mLauncher.getBackground().switchToOverview();
     }
 
-    @NavigationModeSwitch
+    // b/143488140
+    //@NavigationModeSwitch
     @Test
-    @Ignore // b/143488140
     public void goToOverviewFromApp() {
         startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
 
@@ -178,9 +179,9 @@
         return mLauncher.getBackground().switchToOverview();
     }
 
-    @NavigationModeSwitch
+    // b/143488140
+    //@NavigationModeSwitch
     @Test
-    @Ignore // b/143488140
     public void testOverview() {
         startAppFast(getAppPackageName());
         startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index f20a0ba..0c5b9ad 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -45,6 +45,8 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
+        // b/143488140
+        mLauncher.pressHome();
         // Start an activity where the gestures start.
         startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
     }
@@ -98,7 +100,6 @@
             // The test action.
             mLauncher.getBackground().switchToOverview();
         }
-        // Workaround for b/157099707
         mLauncher.pressHome();
     }
 }
\ No newline at end of file
diff --git a/res/layout/arrow_toast.xml b/res/layout/arrow_toast.xml
index 087e45a..0ec9981 100644
--- a/res/layout/arrow_toast.xml
+++ b/res/layout/arrow_toast.xml
@@ -34,6 +34,8 @@
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_weight="1"
+            android:paddingTop="5dp"
+            android:paddingBottom="5dp"
             android:gravity="center"
             android:layout_gravity="center_vertical"
             android:textColor="@android:color/white"
@@ -58,6 +60,5 @@
         android:elevation="2dp"
         android:layout_width="10dp"
         android:layout_height="8dp"
-        android:layout_marginTop="-2dp"
-        android:layout_gravity="center_horizontal"/>
+        android:layout_marginTop="-2dp"/>
 </merge>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 2efa66f..935bb40 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -172,8 +172,10 @@
     <string name="folder_closed">Folder closed</string>
     <!-- Folder renamed format -->
     <string name="folder_renamed">Folder renamed to <xliff:g id="name" example="Games">%1$s</xliff:g></string>
-    <!-- Folder name format -->
-    <string name="folder_name_format">Folder: <xliff:g id="name" example="Games">%1$s</xliff:g></string>
+    <!-- Folder name format when folder has less than 4 items -->
+    <string name="folder_name_format_exact">Folder: <xliff:g id="name" example="Games">%1$s</xliff:g>, <xliff:g id="size" example="2">%2$d</xliff:g> items</string>
+    <!-- Folder name format when folder has 4 or more items shown in preview-->
+    <string name="folder_name_format_overflow">Folder: <xliff:g id="name" example="Games">%1$s</xliff:g>, <xliff:g id="size" example="2">%2$d</xliff:g> or more items</string>
 
     <!-- Strings for the customization mode -->
     <!-- Text for widget add button -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index a922183..ac00488 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -50,7 +50,7 @@
         <item name="folderFillColor">#CDFFFFFF</item>
         <item name="folderIconBorderColor">?android:attr/colorPrimary</item>
         <item name="folderTextColor">#FF212121</item>
-        <item name="folderHintColor">#FF616161</item>
+        <item name="folderHintColor">#89616161</item>
         <item name="loadingIconColor">#CCFFFFFF</item>
         <item name="iconOnlyShortcutColor">?android:attr/textColorSecondary</item>
         <item name="workProfileOverlayTextColor">#FF212121</item>
@@ -106,7 +106,7 @@
         <item name="folderFillColor">#DD3C4043</item> <!-- 87% GM2 800 -->
         <item name="folderIconBorderColor">#FF80868B</item>
         <item name="folderTextColor">@android:color/white</item>
-        <item name="folderHintColor">#FFCCCCCC</item>
+        <item name="folderHintColor">#89CCCCCC</item>
         <item name="isMainColorDark">true</item>
         <item name="loadingIconColor">#99FFFFFF</item>
         <item name="iconOnlyShortcutColor">#B3FFFFFF</item>
diff --git a/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java b/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
index 7ca416d..8f3a83e 100644
--- a/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
@@ -90,6 +90,22 @@
     }
 
     @Test
+    public void testCustomProfileLoaded_with_folder_custom_title() throws Exception {
+        writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0).putFolder("CustomFolder")
+                .addApp(TEST_PACKAGE, TEST_PACKAGE)
+                .addApp(TEST_PACKAGE, TEST_PACKAGE)
+                .addApp(TEST_PACKAGE, TEST_PACKAGE)
+                .build());
+
+        // Verify folder
+        assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
+        ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0);
+        assertEquals(LauncherSettings.Favorites.ITEM_TYPE_FOLDER, info.itemType);
+        assertEquals(3, ((FolderInfo) info).contents.size());
+        assertEquals("CustomFolder", info.title.toString());
+    }
+
+    @Test
     public void testCustomProfileLoaded_with_widget() throws Exception {
         String pendingAppPkg = "com.test.pending";
 
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java b/robolectric_tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java
index d3659eb..4e21dce 100644
--- a/robolectric_tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java
+++ b/robolectric_tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java
@@ -47,6 +47,7 @@
     private static final String ATTR_PACKAGE_NAME = "packageName";
     private static final String ATTR_CLASS_NAME = "className";
     private static final String ATTR_TITLE = "title";
+    private static final String ATTR_TITLE_TEXT = "titleText";
     private static final String ATTR_SCREEN = "screen";
 
     // x and y can be specified as negative integers, in which case -1 represents the
@@ -145,8 +146,17 @@
         }
 
         public FolderBuilder putFolder(int titleResId) {
-            FolderBuilder folderBuilder = new FolderBuilder();
             items.put(ATTR_TITLE, Integer.toString(titleResId));
+            return putFolder();
+        }
+
+        public FolderBuilder putFolder(String title) {
+            items.put(ATTR_TITLE_TEXT, title);
+            return putFolder();
+        }
+
+        private FolderBuilder putFolder() {
+            FolderBuilder folderBuilder = new FolderBuilder();
             items.put(ATTR_CHILDREN, folderBuilder.mChildren);
             mNodes.add(Pair.create(TAG_FOLDER, items));
             return folderBuilder;
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 5971a02..432073e 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -132,6 +132,7 @@
     private static final String ATTR_PACKAGE_NAME = "packageName";
     private static final String ATTR_CLASS_NAME = "className";
     private static final String ATTR_TITLE = "title";
+    private static final String ATTR_TITLE_TEXT = "titleText";
     private static final String ATTR_SCREEN = "screen";
 
     // x and y can be specified as negative integers, in which case -1 represents the
@@ -585,7 +586,8 @@
             if (titleResId != 0) {
                 title = mSourceRes.getString(titleResId);
             } else {
-                title = "";
+                String titleText = getAttributeValue(parser, ATTR_TITLE_TEXT);
+                title = TextUtils.isEmpty(titleText) ? "" : titleText;
             }
 
             mValues.put(Favorites.TITLE, title);
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 268b910..9cb8cf2 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -43,6 +43,8 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -188,7 +190,8 @@
             }
             getUserEventDispatcher().logAppLaunch(v, intent, user);
             if (item != null) {
-                getStatsLogManager().log(LAUNCHER_APP_LAUNCH_TAP, item.buildProto());
+                InstanceId instanceId = new InstanceIdSequence().newInstanceId();
+                logAppLaunch(item, instanceId);
             }
             return true;
         } catch (NullPointerException|ActivityNotFoundException|SecurityException e) {
@@ -198,6 +201,11 @@
         return false;
     }
 
+    protected void logAppLaunch(ItemInfo info, InstanceId instanceId) {
+        getStatsLogManager().logger().withItemInfo(info).withInstanceId(instanceId)
+                .log(LAUNCHER_APP_LAUNCH_TAP);
+    }
+
     private void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info,
             @Nullable String sourceContainer) {
         try {
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index d75d712..2857497 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -126,11 +126,9 @@
             d.dragInfo.container = NO_ID;
         }
         super.onDrop(d, options);
-        mStatsLogManager.log(
-                mControlType == ControlType.REMOVE_TARGET
-                        ? LAUNCHER_ITEM_DROPPED_ON_REMOVE
-                        : LAUNCHER_ITEM_DROPPED_ON_CANCEL,
-                d.logInstanceId);
+        mStatsLogManager.logger().withInstanceId(d.logInstanceId)
+                .log(mControlType == ControlType.REMOVE_TARGET ? LAUNCHER_ITEM_DROPPED_ON_REMOVE
+                        : LAUNCHER_ITEM_DROPPED_ON_CANCEL);
     }
 
     @Override
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 60abc66..e39e89c 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -148,6 +148,7 @@
         iconSize = p.iconSize;
         iconShapePath = p.iconShapePath;
         landscapeIconSize = p.landscapeIconSize;
+        iconBitmapSize = p.iconBitmapSize;
         iconTextSize = p.iconTextSize;
         numHotseatIcons = p.numHotseatIcons;
         numAllAppsColumns = p.numAllAppsColumns;
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 208d565..5512654 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -163,6 +163,7 @@
         public static final int CONTAINER_SEARCH_RESULTS = -106;
         public static final int CONTAINER_SHORTCUTS = -107;
         public static final int CONTAINER_SETTINGS = -108;
+        public static final int CONTAINER_TASKSWITCHER = -109;
 
         public static final String containerToString(int container) {
             switch (container) {
@@ -250,6 +251,12 @@
         public static final int ITEM_TYPE_DEEP_SHORTCUT = 6;
 
         /**
+         * Type of the item is recents task.
+         * TODO(hyunyoungs): move constants not related to Favorites DB to a better location.
+         */
+        public static final int ITEM_TYPE_TASK = 7;
+
+        /**
          * The appWidgetId of the widget
          *
          * <P>Type: INTEGER</P>
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index fbac0bd..499b54f 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -220,9 +220,11 @@
         d.dragSource = new DeferredOnComplete(d.dragSource, getContext());
         super.onDrop(d, options);
         if (mCurrentAccessibilityAction == UNINSTALL) {
-            mStatsLogManager.log(LAUNCHER_ITEM_DROPPED_ON_UNINSTALL, d.logInstanceId);
+            mStatsLogManager.logger().withInstanceId(d.logInstanceId)
+                    .log(LAUNCHER_ITEM_DROPPED_ON_UNINSTALL);
         } else if (mCurrentAccessibilityAction == DISMISS_PREDICTION) {
-            mStatsLogManager.log(LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST, d.logInstanceId);
+            mStatsLogManager.logger().withInstanceId(d.logInstanceId)
+                    .log(LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST);
         }
     }
 
@@ -348,10 +350,12 @@
                     mDragObject.dragInfo.user, PackageManager.MATCH_UNINSTALLED_PACKAGES) == null) {
                 mDragObject.dragSource = mOriginal;
                 mOriginal.onDropCompleted(SecondaryDropTarget.this, mDragObject, true);
-                mStatsLogManager.log(LAUNCHER_ITEM_UNINSTALL_COMPLETED, mDragObject.logInstanceId);
+                mStatsLogManager.logger().withInstanceId(mDragObject.logInstanceId)
+                        .log(LAUNCHER_ITEM_UNINSTALL_COMPLETED);
             } else {
                 sendFailure();
-                mStatsLogManager.log(LAUNCHER_ITEM_UNINSTALL_CANCELLED, mDragObject.logInstanceId);
+                mStatsLogManager.logger().withInstanceId(mDragObject.logInstanceId)
+                        .log(LAUNCHER_ITEM_UNINSTALL_CANCELLED);
             }
         }
 
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 8e33406..bf63788 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -606,6 +606,7 @@
                 outObj[0] = activityInfo;
                 return activityInfo.getFullResIcon(appState.getIconCache());
             }
+            if (info.getIntent() == null || info.getIntent().getPackage() == null) return null;
             List<ShortcutInfo> si = ShortcutKey.fromItemInfo(info)
                     .buildRequest(launcher)
                     .query(ShortcutRequest.ALL);
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index f6c392b..fb58f21 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -415,14 +415,9 @@
 
         // Always enter the spring loaded mode
         mLauncher.getStateManager().goToState(SPRING_LOADED);
-        mStatsLogManager.log(
-                LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED,
-                dragObject.logInstanceId,
-                dragObject.dragSource instanceof Folder
-                        ? dragObject.originalDragInfo
-                                .buildProto(((Folder) dragObject.dragSource).mInfo)
-                        : dragObject.originalDragInfo.buildProto()
-        );
+        mStatsLogManager.logger().withItemInfo(dragObject.originalDragInfo)
+                .withInstanceId(dragObject.logInstanceId)
+                .log(LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED);
     }
 
     public void deferRemoveExtraEmptyScreen() {
@@ -1649,10 +1644,8 @@
             Rect folderLocation = new Rect();
             float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
             target.removeView(v);
-            mStatsLogManager.log(
-                    LauncherEvent.LAUNCHER_ITEM_DROP_FOLDER_CREATED,
-                    d.logInstanceId,
-                    destInfo.buildProto(null));
+            mStatsLogManager.logger().withItemInfo(destInfo).withInstanceId(d.logInstanceId)
+                    .log(LauncherEvent.LAUNCHER_ITEM_DROP_FOLDER_CREATED);
             FolderIcon fi = mLauncher.addFolder(target, container, screenId, targetCell[0],
                     targetCell[1]);
             destInfo.cellX = -1;
@@ -1690,10 +1683,8 @@
         if (dropOverView instanceof FolderIcon) {
             FolderIcon fi = (FolderIcon) dropOverView;
             if (fi.acceptDrop(d.dragInfo)) {
-                mStatsLogManager.log(
-                        LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED,
-                        d.logInstanceId,
-                        fi.mInfo.buildProto(null));
+                mStatsLogManager.logger().withItemInfo(fi.mInfo).withInstanceId(d.logInstanceId)
+                        .log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED);
                 fi.onDrop(d, false /* itemReturnedOnFailedDrop */);
 
                 // if the drag started here, we need to remove it from the workspace
@@ -1896,10 +1887,8 @@
 
             mLauncher.getStateManager().goToState(
                     NORMAL, SPRING_LOADED_EXIT_DELAY, onCompleteRunnable);
-            mStatsLogManager.log(
-                    LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED,
-                    d.logInstanceId,
-                    d.dragInfo.buildProto(null));
+            mStatsLogManager.logger().withItemInfo(d.dragInfo).withInstanceId(d.logInstanceId)
+                    .log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED);
         }
 
         if (d.stateAnnouncer != null && !droppedOnOriginalCell) {
@@ -2437,10 +2426,9 @@
                     // widgets/shortcuts/folders in a slightly different way
                     mLauncher.addPendingItem(pendingInfo, container, screenId, mTargetCell,
                             item.spanX, item.spanY);
-                    mStatsLogManager.log(
-                            LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED,
-                            d.logInstanceId,
-                            d.dragInfo.buildProto(null));
+                    mStatsLogManager.logger().withItemInfo(d.dragInfo)
+                            .withInstanceId(d.logInstanceId)
+                            .log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED);
                 }
             };
             boolean isWidget = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
@@ -2529,10 +2517,8 @@
                 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view, this);
                 resetTransitionTransform();
             }
-            mStatsLogManager.log(
-                    LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED,
-                    d.logInstanceId,
-                    d.dragInfo.buildProto(null));
+            mStatsLogManager.logger().withItemInfo(d.dragInfo).withInstanceId(d.logInstanceId)
+                    .log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED);
         }
 
     }
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 8260336..a9b030e 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -14,7 +14,6 @@
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_HEADER_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
-import static com.android.launcher3.util.SystemUiController.UI_STATE_ALL_APPS;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -37,7 +36,6 @@
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
-import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ScrimView;
 import com.android.systemui.plugins.AllAppsSearchPlugin;
 import com.android.systemui.plugins.PluginListener;
@@ -75,7 +73,6 @@
     private ScrimView mScrimView;
 
     private final Launcher mLauncher;
-    private final boolean mIsDarkTheme;
     private boolean mIsVerticalLayout;
 
     // Animation in this class is controlled by a single variable {@link mProgress}.
@@ -98,7 +95,6 @@
         mShiftRange = mLauncher.getDeviceProfile().heightPx;
         mProgress = 1f;
 
-        mIsDarkTheme = Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark);
         mIsVerticalLayout = mLauncher.getDeviceProfile().isVerticalBarLayout();
         mLauncher.addOnDeviceProfileChangeListener(this);
     }
@@ -137,16 +133,6 @@
         if (mPlugin != null) {
             mPlugin.setProgress(progress);
         }
-
-        // Use a light system UI (dark icons) if all apps is behind at least half of the
-        // status bar.
-        boolean forceChange = Math.min(shiftCurrent, mScrimView.getVisualTop())
-                <= mLauncher.getDeviceProfile().getInsets().top / 2f;
-        if (forceChange) {
-            mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, !mIsDarkTheme);
-        } else {
-            mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, 0);
-        }
     }
 
     public float getProgress() {
@@ -173,7 +159,9 @@
             StateAnimationConfig config, PendingAnimation builder) {
         float targetProgress = toState.getVerticalProgress(mLauncher);
         if (Float.compare(mProgress, targetProgress) == 0) {
-            setAlphas(toState, config, builder);
+            if (!config.onlyPlayAtomicComponent()) {
+                setAlphas(toState, config, builder);
+            }
             // Fail fast
             onProgressAnimationEnd();
             return;
diff --git a/src/com/android/launcher3/anim/FlingSpringAnim.java b/src/com/android/launcher3/anim/FlingSpringAnim.java
index 06d0f1c..6ea38ec 100644
--- a/src/com/android/launcher3/anim/FlingSpringAnim.java
+++ b/src/com/android/launcher3/anim/FlingSpringAnim.java
@@ -35,6 +35,7 @@
 
     private final FlingAnimation mFlingAnim;
     private SpringAnimation mSpringAnim;
+    private final boolean mSkipFlingAnim;
 
     private float mTargetPosition;
 
@@ -57,6 +58,10 @@
                 .setMaxValue(maxValue);
         mTargetPosition = targetPosition;
 
+        // We are already past the fling target, so skip it to avoid losing a frame of the spring.
+        mSkipFlingAnim = startPosition <= minValue && startVelocity < 0
+                || startPosition >= maxValue && startVelocity > 0;
+
         mFlingAnim.addEndListener(((animation, canceled, value, velocity) -> {
             mSpringAnim = new SpringAnimation(object, property)
                     .setStartValue(value)
@@ -84,6 +89,9 @@
 
     public void start() {
         mFlingAnim.start();
+        if (mSkipFlingAnim) {
+            mFlingAnim.cancel();
+        }
     }
 
     public void end() {
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index c7487cb..7998c2d 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -1337,8 +1337,8 @@
         if (d.stateAnnouncer != null) {
             d.stateAnnouncer.completeAction(R.string.item_moved);
         }
-        mStatsLogManager
-                .log(LAUNCHER_ITEM_DROP_COMPLETED, d.logInstanceId, d.dragInfo.buildProto(mInfo));
+        mStatsLogManager.logger().withItemInfo(d.dragInfo).withInstanceId(d.logInstanceId)
+                .log(LAUNCHER_ITEM_DROP_COMPLETED);
     }
 
     // This is used so the item doesn't immediately appear in the folder when added. In one case
@@ -1443,7 +1443,7 @@
             if (hasFocus) {
                 startEditingFolderName();
             } else {
-                mStatsLogManager.log(LAUNCHER_FOLDER_LABEL_UPDATED, mInfo.buildProto());
+                mStatsLogManager.logger().withItemInfo(mInfo).log(LAUNCHER_FOLDER_LABEL_UPDATED);
                 logFolderLabelState();
                 mFolderName.dispatchBackKey();
             }
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 153d6bc..b40b1e2 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -201,8 +201,7 @@
         icon.mActivity = activity;
         icon.mDotRenderer = grid.mDotRendererWorkSpace;
 
-        icon.setContentDescription(
-                group.getContext().getString(R.string.folder_name_format, folderInfo.title));
+        icon.setContentDescription(icon.getAccessiblityTitle(folderInfo.title));
 
         // Keep the notification dot up to date with the sum of all the content's dots.
         FolderDotInfo folderDotInfo = new FolderDotInfo();
@@ -449,8 +448,8 @@
             return;
         }
         mInfo.setTitle(nameInfos[0].getLabel());
-        StatsLogManager.newInstance(getContext())
-                .log(LAUNCHER_FOLDER_LABEL_UPDATED, instanceId, mInfo.buildProto());
+        StatsLogManager.newInstance(getContext()).logger().withItemInfo(mInfo)
+                .withInstanceId(instanceId).log(LAUNCHER_FOLDER_LABEL_UPDATED);
         onTitleChanged(mInfo.title);
         mFolder.mFolderName.setText(mInfo.title);
         mFolder.mLauncher.getModelWriter().updateItemInDatabase(mInfo);
@@ -665,6 +664,7 @@
         mDotInfo.addDotInfo(mActivity.getDotInfoForItem(item));
         boolean isDotted = mDotInfo.hasDot();
         updateDotScale(wasDotted, isDotted);
+        setContentDescription(getAccessiblityTitle(mInfo.title));
         invalidate();
         requestLayout();
     }
@@ -675,13 +675,14 @@
         mDotInfo.subtractDotInfo(mActivity.getDotInfoForItem(item));
         boolean isDotted = mDotInfo.hasDot();
         updateDotScale(wasDotted, isDotted);
+        setContentDescription(getAccessiblityTitle(mInfo.title));
         invalidate();
         requestLayout();
     }
 
     public void onTitleChanged(CharSequence title) {
         mFolderName.setText(title);
-        setContentDescription(getContext().getString(R.string.folder_name_format, title));
+        setContentDescription(getAccessiblityTitle(title));
     }
 
     @Override
@@ -775,4 +776,17 @@
     public void getWorkspaceVisualDragBounds(Rect bounds) {
         getPreviewBounds(bounds);
     }
+
+    /**
+     * Returns a formatted accessibility title for folder
+     */
+    public String getAccessiblityTitle(CharSequence title) {
+        int size = mInfo.contents.size();
+        if (size < MAX_NUM_ITEMS_IN_PREVIEW) {
+            return getContext().getString(R.string.folder_name_format_exact, title, size);
+        } else {
+            return getContext().getString(R.string.folder_name_format_overflow, title,
+                    MAX_NUM_ITEMS_IN_PREVIEW);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/folder/FolderNameEditText.java b/src/com/android/launcher3/folder/FolderNameEditText.java
index edf2c70..6038a05 100644
--- a/src/com/android/launcher3/folder/FolderNameEditText.java
+++ b/src/com/android/launcher3/folder/FolderNameEditText.java
@@ -70,8 +70,11 @@
         for (int i = 0; i < cnt; i++) {
             cInfo[i] = new CompletionInfo(i, i, suggestList.get(i));
         }
-        post(() -> getContext().getSystemService(InputMethodManager.class)
-                .displayCompletions(this, cInfo));
+        // post it to future frame so that onSelectionChanged, onFocusChanged, all other
+        // TextView flag change and IME animation has settled. Ideally, there should be IMM
+        // callback to notify when the IME animation and state handling is finished.
+        postDelayed(() -> getContext().getSystemService(InputMethodManager.class)
+                .displayCompletions(this, cInfo), 40 /* 2~3 frame delay */);
     }
 
     /**
diff --git a/src/com/android/launcher3/logging/InstanceIdSequence.java b/src/com/android/launcher3/logging/InstanceIdSequence.java
index a4b7953..ee6a5a4 100644
--- a/src/com/android/launcher3/logging/InstanceIdSequence.java
+++ b/src/com/android/launcher3/logging/InstanceIdSequence.java
@@ -45,6 +45,13 @@
     }
 
     /**
+     * Constructs a sequence with identifiers [1, InstanceId.INSTANCE_ID_MAX].
+     */
+    public InstanceIdSequence() {
+        this(InstanceId.INSTANCE_ID_MAX);
+    }
+
+    /**
      * Gets the next instance from the sequence.  Safe for concurrent use.
      * @return new InstanceId
      */
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index f216f81..8fd779d 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -21,8 +21,9 @@
 
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
-import com.android.launcher3.logger.LauncherAtom.ItemInfo;
+import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.StatsLogUtils.LogStateProvider;
+import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.util.ResourceBasedOverride;
 
 /**
@@ -127,9 +128,29 @@
         LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP(522),
 
         @UiEvent(doc = "User is shown All Apps education view.")
-        LAUNCHER_ALL_APPS_EDU_SHOWN(523);
-        // ADD MORE
+        LAUNCHER_ALL_APPS_EDU_SHOWN(523),
 
+        @UiEvent(doc = "User opened a folder.")
+        LAUNCHER_FOLDER_OPEN(551),
+
+        @UiEvent(doc = "Hotseat education half sheet seen")
+        LAUNCHER_HOTSEAT_EDU_SEEN(479),
+
+        @UiEvent(doc = "Hotseat migration accepted")
+        LAUNCHER_HOTSEAT_EDU_ACCEPT(480),
+
+        @UiEvent(doc = "Hotseat migration denied")
+        LAUNCHER_HOTSEAT_EDU_DENY(481),
+
+        @UiEvent(doc = "Hotseat education tip shown")
+        LAUNCHER_HOTSEAT_EDU_ONLY_TIP(482),
+
+        @UiEvent(doc = "App launch ranking logged for all apps predictions")
+        LAUNCHER_ALL_APPS_RANKED(552),
+
+        @UiEvent(doc = "App launch ranking logged for hotseat predictions)")
+        LAUNCHER_HOTSEAT_RANKED(553);
+        // ADD MORE
         private final int mId;
 
         LauncherEvent(int id) {
@@ -160,6 +181,62 @@
         }
     }
 
+    /**
+     * Helps to construct and write the log message.
+     */
+    public interface StatsLogger {
+
+        /**
+         * Sets log fields from provided {@link ItemInfo}.
+         */
+        default StatsLogger withItemInfo(ItemInfo itemInfo) {
+            return this;
+        }
+
+
+        /**
+         * Sets {@link InstanceId} of log message.
+         */
+        default StatsLogger withInstanceId(InstanceId instanceId) {
+            return this;
+        }
+
+        /**
+         * Sets rank field of log message.
+         */
+        default StatsLogger withRank(int rank) {
+            return this;
+        }
+
+        /**
+         * Sets source launcher state field of log message.
+         */
+        default StatsLogger withSrcState(int srcState) {
+            return this;
+        }
+
+        /**
+         * Sets destination launcher state field of log message.
+         */
+        default StatsLogger withDstState(int dstState) {
+            return this;
+        }
+
+        /**
+         * Builds the final message and logs it as {@link EventEnum}.
+         */
+        default void log(EventEnum event) {
+        }
+    }
+
+    /**
+     * Returns new logger object.
+     */
+    public StatsLogger logger() {
+        return new StatsLogger() {
+        };
+    }
+
     protected LogStateProvider mStateProvider;
 
     /**
@@ -177,34 +254,29 @@
     }
 
     /**
-     * Logs a {@link EventEnum}.
+     * Logs an event.
+     *
+     * @param event an enum implementing EventEnum interface.
+     * @param atomInfo item typically containing app or task launch related information.
      */
-    public void log(EventEnum event) {
+    public void log(EventEnum event, InstanceId instanceId, LauncherAtom.ItemInfo atomInfo) {
     }
 
     /**
-     * Logs an event and accompanying {@link InstanceId}.
+     * Logs an event and accompanying {@link LauncherState}s.
+     *
+     * @param event an enum implementing EventEnum interface.
+     * @param launcherAtomItemInfo item typically containing app or task launch related information.
      */
-    public void log(EventEnum event, InstanceId instanceId) {
-    }
-
-    /**
-     * Logs an event and accompanying {@link ItemInfo}.
-     */
-    public void log(EventEnum event, @Nullable ItemInfo itemInfo) {
-    }
-
-    /**
-     * Logs an event and accompanying {@link InstanceId} and {@link ItemInfo}.
-     */
-    public void log(EventEnum event, InstanceId instanceId, @Nullable ItemInfo itemInfo) {
+    public void log(EventEnum event, @Nullable LauncherAtom.ItemInfo launcherAtomItemInfo,
+            int srcState, int dstState) {
     }
 
     /**
      * Log an event with ranked-choice information along with package. Does nothing if event.getId()
      * <= 0.
      *
-     * @param rankingEvent an enum implementing UiEventEnum interface.
+     * @param rankingEvent an enum implementing EventEnum interface.
      * @param instanceId An identifier obtained from an InstanceIdSequence.
      * @param packageName the package name of the relevant app, if known (null otherwise).
      * @param position the position picked.
@@ -214,13 +286,6 @@
     }
 
     /**
-     * Logs an event and accompanying {@link LauncherState}s. If either of the state refers
-     * to workspace state, then use pageIndex to pass in index of workspace.
-     */
-    public void log(EventEnum event, int srcState, int dstState, int pageIndex) {
-    }
-
-    /**
      * Logs snapshot, or impression of the current workspace.
      */
     public void logSnapshot() {
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 7818ff5..e094cab 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -48,7 +48,6 @@
 import com.android.launcher3.DropTarget;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.userevent.LauncherLogProto;
@@ -143,14 +142,6 @@
             fillIntentInfo(itemTarget, intent, userHandle);
         }
         LauncherEvent event = newLauncherEvent(action,  targets);
-        ItemInfo info = v == null ? null : (ItemInfo) v.getTag();
-        if (info != null && Utilities.IS_DEBUG_DEVICE && FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
-            final String pkg = info.getTargetComponent() != null
-                    ? info.getTargetComponent().getPackageName() : "unknown";
-            FileLog.d(TAG, "appLaunch: packageName:" + pkg
-                    + ",isWorkApp:" + (info.user != null && !Process.myUserHandle().equals(
-                    userHandle)) + ",launchLocation:" + info.container);
-        }
         dispatchUserEvent(event, intent);
         mAppOrTaskLaunch = true;
     }
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
index 5ba2c8e..25a2c69 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -59,7 +59,6 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-import java.util.stream.Collectors;
 
 /**
  * This class takes care of shrinking the workspace (by maximum of one row and one column), as a
@@ -248,12 +247,25 @@
 
     /** Return what's in the src but not in the dest */
     private static List<DbEntry> calcDiff(List<DbEntry> src, List<DbEntry> dest) {
-        Set<String> destSet = dest.parallelStream().map(DbEntry::getIntentStr).collect(
-                Collectors.toSet());
+        Set<String> destIntentSet = new HashSet<>();
+        Set<Set<String>> destFolderIntentSet = new HashSet<>();
+        for (DbEntry entry : dest) {
+            if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
+                destFolderIntentSet.add(entry.mFolderItems.keySet());
+            } else {
+                destIntentSet.add(entry.mIntent);
+            }
+        }
         List<DbEntry> diff = new ArrayList<>();
         for (DbEntry entry : src) {
-            if (!destSet.contains(entry.mIntent)) {
-                diff.add(entry);
+            if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
+                if (!destFolderIntentSet.contains(entry.mFolderItems.keySet())) {
+                    diff.add(entry);
+                }
+            } else {
+                if (!destIntentSet.contains(entry.mIntent)) {
+                    diff.add(entry);
+                }
             }
         }
         return diff;
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index d52b7eb..66c3cbb 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -24,11 +24,13 @@
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SEARCH_RESULTS;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SETTINGS;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_TASKSWITCHER;
 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;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_TASK;
 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.CONTAINER_NOT_SET;
 
 import android.content.ComponentName;
@@ -49,6 +51,7 @@
 import com.android.launcher3.logger.LauncherAtom.SearchResultContainer;
 import com.android.launcher3.logger.LauncherAtom.SettingsContainer;
 import com.android.launcher3.logger.LauncherAtom.ShortcutsContainer;
+import com.android.launcher3.logger.LauncherAtom.TaskSwitcherContainer;
 import com.android.launcher3.util.ContentWriter;
 
 import java.util.Optional;
@@ -298,6 +301,12 @@
                                 .setSpanX(spanX)
                                 .setSpanY(spanY));
                 break;
+            case ITEM_TYPE_TASK:
+                itemBuilder
+                        .setTask(LauncherAtom.Task.newBuilder()
+                                .setComponentName(getTargetComponent().flattenToShortString())
+                                .setIndex(screenId));
+                break;
             default:
                 break;
         }
@@ -378,6 +387,11 @@
                 return ContainerInfo.newBuilder()
                         .setSettingsContainer(SettingsContainer.getDefaultInstance())
                         .build();
+            case CONTAINER_TASKSWITCHER:
+                return ContainerInfo.newBuilder()
+                        .setTaskSwitcherContainer(TaskSwitcherContainer.getDefaultInstance())
+                        .build();
+
         }
         return ContainerInfo.getDefaultInstance();
     }
diff --git a/src/com/android/launcher3/notification/NotificationInfo.java b/src/com/android/launcher3/notification/NotificationInfo.java
index fa1bdfb..835f72d 100644
--- a/src/com/android/launcher3/notification/NotificationInfo.java
+++ b/src/com/android/launcher3/notification/NotificationInfo.java
@@ -107,8 +107,8 @@
         try {
             intent.send(null, 0, null, null, null, null, activityOptions);
             launcher.getUserEventDispatcher().logNotificationLaunch(view, intent);
-            launcher.getStatsLogManager()
-                    .log(LAUNCHER_NOTIFICATION_LAUNCH_TAP, mItemInfo.buildProto());
+            launcher.getStatsLogManager().logger().withItemInfo(mItemInfo)
+                    .log(LAUNCHER_NOTIFICATION_LAUNCH_TAP);
         } catch (PendingIntent.CanceledException e) {
             e.printStackTrace();
         }
diff --git a/src/com/android/launcher3/popup/RemoteActionShortcut.java b/src/com/android/launcher3/popup/RemoteActionShortcut.java
index 58251e8..61829c0 100644
--- a/src/com/android/launcher3/popup/RemoteActionShortcut.java
+++ b/src/com/android/launcher3/popup/RemoteActionShortcut.java
@@ -77,8 +77,8 @@
     @Override
     public void onClick(View view) {
         AbstractFloatingView.closeAllOpenViews(mTarget);
-        mTarget.getStatsLogManager()
-                .log(LAUNCHER_SYSTEM_SHORTCUT_PAUSE_TAP, mItemInfo.buildProto());
+        mTarget.getStatsLogManager().logger().withItemInfo(mItemInfo)
+                .log(LAUNCHER_SYSTEM_SHORTCUT_PAUSE_TAP);
 
         final String actionIdentity = mAction.getTitle() + ", "
                 + mItemInfo.getTargetComponent().getPackageName();
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index ea8caf5..fd292a3 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -119,9 +119,8 @@
             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());
+            mTarget.getStatsLogManager().logger().withItemInfo(mItemInfo)
+                    .log(LAUNCHER_SYSTEM_SHORTCUT_WIDGETS_TAP);
         }
     }
 
@@ -142,9 +141,8 @@
                     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());
+            mTarget.getStatsLogManager().logger().withItemInfo(mItemInfo)
+                    .log(LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP);
         }
     }
 
diff --git a/src/com/android/launcher3/touch/HomeRotatedPageHandler.java b/src/com/android/launcher3/touch/HomeRotatedPageHandler.java
deleted file mode 100644
index db5c659..0000000
--- a/src/com/android/launcher3/touch/HomeRotatedPageHandler.java
+++ /dev/null
@@ -1,55 +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.touch;
-
-import android.graphics.RectF;
-import android.view.Surface;
-import android.widget.LinearLayout;
-
-public class HomeRotatedPageHandler extends PortraitPagedViewHandler {
-    @Override
-    public void offsetTaskRect(RectF rect, float value, int displayRotation, int launcherRotation) {
-        if (launcherRotation == Surface.ROTATION_0) {
-            super.offsetTaskRect(rect, value, displayRotation, launcherRotation);
-        } else if (launcherRotation == Surface.ROTATION_90) {
-            if (displayRotation == Surface.ROTATION_0) {
-                rect.offset(0, value);
-            } else if (displayRotation == Surface.ROTATION_90) {
-                rect.offset(value, 0);
-            } else if (displayRotation == Surface.ROTATION_180) {
-                rect.offset(-value, 0);
-            } else {
-                rect.offset(-value, 0);
-            }
-        } else if (launcherRotation == Surface.ROTATION_270) {
-            if (displayRotation == Surface.ROTATION_0) {
-                rect.offset(0, -value);
-            } else if (displayRotation == Surface.ROTATION_90) {
-                rect.offset(value, 0);
-            } else if (displayRotation == Surface.ROTATION_180) {
-                rect.offset(0, -value);
-            } else {
-                rect.offset(value, 0);
-            }
-        } // TODO (b/149609488) handle 180 case as well
-    }
-
-    @Override
-    public int getTaskMenuLayoutOrientation(LinearLayout taskMenuLayout) {
-        return taskMenuLayout.getOrientation();
-    }
-}
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 6abca76..8486666 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.Launcher.REQUEST_BIND_PENDING_APPWIDGET;
 import static com.android.launcher3.Launcher.REQUEST_RECONFIGURE_APPWIDGET;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
 import static com.android.launcher3.model.AppLaunchTracker.CONTAINER_ALL_APPS;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_BY_PUBLISHER;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
@@ -45,6 +46,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
@@ -111,6 +113,8 @@
         if (!folder.isOpen() && !folder.isDestroyed()) {
             // Open the requested folder
             folder.animateOpen();
+            StatsLogManager.newInstance(v.getContext()).logger().withItemInfo(folder.mInfo)
+                    .log(LAUNCHER_FOLDER_OPEN);
         }
     }
 
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index d02c731..48c7734 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -181,19 +181,6 @@
     }
 
     @Override
-    public void offsetTaskRect(RectF rect, float value, int displayRotation, int launcherRotation) {
-        if (displayRotation == Surface.ROTATION_0) {
-            rect.offset(0, value);
-        } else if (displayRotation == Surface.ROTATION_90) {
-            rect.offset(value, 0);
-        } else if (displayRotation == Surface.ROTATION_180) {
-            rect.offset(0, -value);
-        } else {
-            rect.offset(-value, 0);
-        }
-    }
-
-    @Override
     public int getChildStart(View view) {
         return view.getTop();
     }
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index 2e0268d..65b1a7a 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -43,7 +43,6 @@
     PagedOrientationHandler PORTRAIT = new PortraitPagedViewHandler();
     PagedOrientationHandler LANDSCAPE = new LandscapePagedViewHandler();
     PagedOrientationHandler SEASCAPE = new SeascapePagedViewHandler();
-    PagedOrientationHandler HOME_ROTATED = new HomeRotatedPageHandler();
 
     interface Int2DAction<T> {
         void call(T target, int x, int y);
@@ -82,7 +81,6 @@
     boolean getRecentsRtlSetting(Resources resources);
     float getDegreesRotated();
     int getRotation();
-    void offsetTaskRect(RectF rect, float value, int delta, int launcherRotation);
     int getPrimaryValue(int x, int y);
     int getSecondaryValue(int x, int y);
     void delegateScrollTo(PagedView pagedView, int secondaryScroll, int primaryScroll);
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 2fc7a9f..79e5c87 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -179,19 +179,6 @@
     }
 
     @Override
-    public void offsetTaskRect(RectF rect, float value, int displayRotation, int launcherRotation) {
-        if (displayRotation == Surface.ROTATION_0) {
-            rect.offset(value, 0);
-        } else if (displayRotation == Surface.ROTATION_90) {
-            rect.offset(0, -value);
-        } else if (displayRotation == Surface.ROTATION_180) {
-            rect.offset(-value, 0);
-        } else {
-            rect.offset(0, value);
-        }
-    }
-
-    @Override
     public int getChildStart(View view) {
         return view.getLeft();
     }
@@ -250,7 +237,7 @@
 
     @Override
     public int getTaskMenuLayoutOrientation(LinearLayout taskMenuLayout) {
-        return LinearLayout.VERTICAL;
+        return taskMenuLayout.getOrientation();
     }
 
     @Override
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index 4c1700e..d5ae2dc 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -18,7 +18,6 @@
 
 import android.content.res.Resources;
 import android.graphics.PointF;
-import android.graphics.RectF;
 import android.view.Surface;
 import android.view.View;
 
@@ -42,19 +41,6 @@
     }
 
     @Override
-    public void offsetTaskRect(RectF rect, float value, int displayRotation, int launcherRotation) {
-        if (displayRotation == Surface.ROTATION_0) {
-            rect.offset(0, value);
-        } else if (displayRotation == Surface.ROTATION_90) {
-            rect.offset(value, 0);
-        } else if (displayRotation == Surface.ROTATION_180) {
-            rect.offset(0, -value);
-        } else {
-            rect.offset(-value, 0);
-        }
-    }
-
-    @Override
     public float getDegreesRotated() {
         return 270;
     }
diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
index 7270ce2..4fa658e 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -174,7 +174,7 @@
 
                 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
                         HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
-                mLauncher.getStatsLogManager().log(LAUNCHER_WORKSPACE_LONGPRESS);
+                mLauncher.getStatsLogManager().logger().log(LAUNCHER_WORKSPACE_LONGPRESS);
                 OptionsPopupView.showDefaultOptions(mLauncher, mTouchDownPoint.x, mTouchDownPoint.y);
             } else {
                 cancelLongPress();
diff --git a/src/com/android/launcher3/util/OnboardingPrefs.java b/src/com/android/launcher3/util/OnboardingPrefs.java
index 1620289..90a1c82 100644
--- a/src/com/android/launcher3/util/OnboardingPrefs.java
+++ b/src/com/android/launcher3/util/OnboardingPrefs.java
@@ -37,13 +37,14 @@
     public static final String HOME_BOUNCE_COUNT = "launcher.home_bounce_count";
     public static final String SHELF_BOUNCE_COUNT = "launcher.shelf_bounce_count";
     public static final String ALL_APPS_COUNT = "launcher.all_apps_count";
+    public static final String HOTSEAT_DISCOVERY_TIP_COUNT = "launcher.hotseat_discovery_tip_count";
 
     /**
      * Events that either have happened or have not (booleans).
      */
     @StringDef(value = {
             HOME_BOUNCE_SEEN,
-            SHELF_BOUNCE_SEEN,
+            SHELF_BOUNCE_SEEN
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface EventBoolKey {}
@@ -55,6 +56,7 @@
             HOME_BOUNCE_COUNT,
             SHELF_BOUNCE_COUNT,
             ALL_APPS_COUNT,
+            HOTSEAT_DISCOVERY_TIP_COUNT
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface EventCountKey {}
@@ -65,6 +67,7 @@
         maxCounts.put(HOME_BOUNCE_COUNT, 3);
         maxCounts.put(SHELF_BOUNCE_COUNT, 3);
         maxCounts.put(ALL_APPS_COUNT, 5);
+        maxCounts.put(HOTSEAT_DISCOVERY_TIP_COUNT, 5);
         MAX_COUNTS = Collections.unmodifiableMap(maxCounts);
     }
 
diff --git a/src/com/android/launcher3/util/SystemUiController.java b/src/com/android/launcher3/util/SystemUiController.java
index 53cc157..275c024 100644
--- a/src/com/android/launcher3/util/SystemUiController.java
+++ b/src/com/android/launcher3/util/SystemUiController.java
@@ -30,7 +30,7 @@
 
     // Various UI states in increasing order of priority
     public static final int UI_STATE_BASE_WINDOW = 0;
-    public static final int UI_STATE_ALL_APPS = 1;
+    public static final int UI_STATE_SCRIM_VIEW = 1;
     public static final int UI_STATE_WIDGET_BOTTOM_SHEET = 2;
     public static final int UI_STATE_OVERVIEW = 3;
 
@@ -61,25 +61,38 @@
         // Apply the state flags in priority order
         int newFlags = oldFlags;
         for (int stateFlag : mStates) {
-            if (Utilities.ATLEAST_OREO) {
-                if ((stateFlag & FLAG_LIGHT_NAV) != 0) {
-                    newFlags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
-                } else if ((stateFlag & FLAG_DARK_NAV) != 0) {
-                    newFlags &= ~(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
-                }
-            }
-
-            if ((stateFlag & FLAG_LIGHT_STATUS) != 0) {
-                newFlags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
-            } else if ((stateFlag & FLAG_DARK_STATUS) != 0) {
-                newFlags &= ~(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
-            }
+            newFlags = getSysUiVisibilityFlags(stateFlag, newFlags);
         }
         if (newFlags != oldFlags) {
             mWindow.getDecorView().setSystemUiVisibility(newFlags);
         }
     }
 
+    /**
+     * Returns the sysui visibility for the base layer
+     */
+    public int getBaseSysuiVisibility() {
+        return getSysUiVisibilityFlags(
+                mStates[UI_STATE_BASE_WINDOW], mWindow.getDecorView().getSystemUiVisibility());
+    }
+
+    private int getSysUiVisibilityFlags(int stateFlag, int currentVisibility) {
+        if (Utilities.ATLEAST_OREO) {
+            if ((stateFlag & FLAG_LIGHT_NAV) != 0) {
+                currentVisibility |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+            } else if ((stateFlag & FLAG_DARK_NAV) != 0) {
+                currentVisibility &= ~(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
+            }
+        }
+
+        if ((stateFlag & FLAG_LIGHT_STATUS) != 0) {
+            currentVisibility |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+        } else if ((stateFlag & FLAG_DARK_STATUS) != 0) {
+            currentVisibility &= ~(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+        }
+        return currentVisibility;
+    }
+
     @Override
     public String toString() {
         return "mStates=" + Arrays.toString(mStates);
diff --git a/src/com/android/launcher3/views/ArrowTipView.java b/src/com/android/launcher3/views/ArrowTipView.java
index a7575d1..b4a6b14 100644
--- a/src/com/android/launcher3/views/ArrowTipView.java
+++ b/src/com/android/launcher3/views/ArrowTipView.java
@@ -125,11 +125,35 @@
      * Show Tip with specified string and Y location
      */
     public ArrowTipView show(String text, int top) {
+        return show(text, Gravity.CENTER_HORIZONTAL, 0, top);
+    }
+
+    /**
+     * Show the ArrowTipView (tooltip) center, start, or end aligned.
+     *
+     * @param text The text to be shown in the tooltip.
+     * @param gravity The gravity aligns the tooltip center, start, or end.
+     * @param arrowMarginStart The margin from start to place arrow (ignored if center)
+     * @param top  The Y coordinate of the bottom of tooltip.
+     * @return The tooltip.
+     */
+    public ArrowTipView show(String text, int gravity, int arrowMarginStart, int top) {
         ((TextView) findViewById(R.id.text)).setText(text);
-        mActivity.getDragLayer().addView(this);
+        ViewGroup parent = mActivity.getDragLayer();
+        parent.addView(this);
 
         DragLayer.LayoutParams params = (DragLayer.LayoutParams) getLayoutParams();
-        params.gravity = Gravity.CENTER_HORIZONTAL;
+        params.gravity = gravity;
+        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) findViewById(
+                R.id.arrow).getLayoutParams();
+        lp.gravity = gravity;
+        if (gravity == Gravity.END) {
+            lp.setMarginEnd(parent.getMeasuredWidth() - arrowMarginStart);
+        } else if (gravity == Gravity.START) {
+            lp.setMarginStart(arrowMarginStart);
+        }
+        requestLayout();
+
         params.leftMargin = mActivity.getDeviceProfile().workspacePadding.left;
         params.rightMargin = mActivity.getDeviceProfile().workspacePadding.right;
         post(() -> setY(top - getHeight()));
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index f54edc2..b010b4b 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -90,13 +90,23 @@
                 }
             };
 
-    // Touch is being dispatched through the normal view dispatch system
-    private static final int TOUCH_DISPATCHING_VIEW = 1 << 0;
+    // Touch coming from normal view system is being dispatched.
+    private static final int TOUCH_DISPATCHING_FROM_VIEW = 1 << 0;
     // Touch is being dispatched through the normal view dispatch system, and started at the
-    // system gesture region
-    private static final int TOUCH_DISPATCHING_GESTURE = 1 << 1;
-    // Touch is being dispatched through a proxy from InputMonitor
-    private static final int TOUCH_DISPATCHING_PROXY = 1 << 2;
+    // system gesture region. In this case we prevent internal gesture handling and only allow
+    // normal view event handling.
+    private static final int TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION = 1 << 1;
+    // Touch coming from InputMonitor proxy is being dispatched 'only to gestures'. Note that both
+    // this and view-system can be active at the same time where view-system would go to the views,
+    // and this would go to the gestures.
+    // Note that this is not set when events are coming from proxy, but going through full dispatch
+    // process (both views and gestures) to allow view-system to easily take over in case it
+    // comes later.
+    private static final int TOUCH_DISPATCHING_FROM_PROXY = 1 << 2;
+    // ACTION_DOWN has been dispatched to child views and ACTION_UP or ACTION_CANCEL is pending.
+    // Note that the event source can either be view-dispatching or proxy-dispatching based on if
+    // TOUCH_DISPATCHING_VIEW is present or not.
+    private static final int TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS = 1 << 3;
 
     protected final float[] mTmpXY = new float[2];
     protected final float[] mTmpRectPoints = new float[4];
@@ -204,7 +214,8 @@
 
     protected boolean findActiveController(MotionEvent ev) {
         mActiveController = null;
-        if ((mTouchDispatchState & (TOUCH_DISPATCHING_GESTURE | TOUCH_DISPATCHING_PROXY)) == 0) {
+        if ((mTouchDispatchState & (TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION
+                | TOUCH_DISPATCHING_FROM_PROXY)) == 0) {
             // Only look for controllers if we are not dispatching from gesture area and proxy is
             // not active
             mActiveController = findControllerToHandleTouch(ev);
@@ -283,19 +294,28 @@
     public boolean dispatchTouchEvent(MotionEvent ev) {
         switch (ev.getAction()) {
             case ACTION_DOWN: {
-                mTouchDispatchState |= TOUCH_DISPATCHING_VIEW;
+                if ((mTouchDispatchState & TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS) != 0) {
+                    // Cancel the previous touch
+                    int action = ev.getAction();
+                    ev.setAction(ACTION_CANCEL);
+                    super.dispatchTouchEvent(ev);
+                    ev.setAction(action);
+                }
+                mTouchDispatchState |= TOUCH_DISPATCHING_FROM_VIEW
+                        | TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS;
 
                 if (isEventInLauncher(ev)) {
-                    mTouchDispatchState &= ~TOUCH_DISPATCHING_GESTURE;
+                    mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION;
                 } else {
-                    mTouchDispatchState |= TOUCH_DISPATCHING_GESTURE;
+                    mTouchDispatchState |= TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION;
                 }
                 break;
             }
             case ACTION_CANCEL:
             case ACTION_UP:
-                mTouchDispatchState &= ~TOUCH_DISPATCHING_GESTURE;
-                mTouchDispatchState &= ~TOUCH_DISPATCHING_VIEW;
+                mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION;
+                mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_VIEW;
+                mTouchDispatchState &= ~TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS;
                 break;
         }
         super.dispatchTouchEvent(ev);
@@ -305,43 +325,53 @@
     }
 
     /**
-     * Called before we are about to receive proxy events.
-     *
-     * @return false if we can't handle proxy at this time
-     */
-    public boolean prepareProxyEventStarting() {
-        mProxyTouchController = null;
-        if ((mTouchDispatchState & TOUCH_DISPATCHING_VIEW) != 0 && mActiveController != null) {
-            // We are already dispatching using view system and have an active controller, we can't
-            // handle another controller.
-
-            // This flag was already cleared in proxy ACTION_UP or ACTION_CANCEL. Added here just
-            // to be safe
-            mTouchDispatchState &= ~TOUCH_DISPATCHING_PROXY;
-            return false;
-        }
-
-        mTouchDispatchState |= TOUCH_DISPATCHING_PROXY;
-        return true;
-    }
-
-    /**
      * Proxies the touch events to the gesture handlers
      */
-    public boolean proxyTouchEvent(MotionEvent ev) {
-        boolean handled;
-        if (mProxyTouchController != null) {
-            handled = mProxyTouchController.onControllerTouchEvent(ev);
+    public boolean proxyTouchEvent(MotionEvent ev, boolean allowViewDispatch) {
+        int actionMasked = ev.getActionMasked();
+        boolean isViewDispatching = (mTouchDispatchState & TOUCH_DISPATCHING_FROM_VIEW) != 0;
+
+        // Only do view dispatch if another view-dispatching is not running, or we already started
+        // proxy-dispatching before. Note that view-dispatching can always take over the proxy
+        // dispatching at anytime, but not vice-versa.
+        allowViewDispatch = allowViewDispatch && !isViewDispatching
+                && (actionMasked == ACTION_DOWN
+                    || ((mTouchDispatchState & TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS) != 0));
+
+        if (allowViewDispatch) {
+            mTouchDispatchState |= TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS;
+            super.dispatchTouchEvent(ev);
+
+            if (actionMasked == ACTION_UP || actionMasked == ACTION_CANCEL) {
+                mTouchDispatchState &= ~TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS;
+                mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_PROXY;
+            }
+            return true;
         } else {
-            mProxyTouchController = findControllerToHandleTouch(ev);
-            handled = mProxyTouchController != null;
+            boolean handled;
+            if (mProxyTouchController != null) {
+                handled = mProxyTouchController.onControllerTouchEvent(ev);
+            } else {
+                if (actionMasked == ACTION_DOWN) {
+                    if (isViewDispatching && mActiveController != null) {
+                        // A controller is already active, we can't initiate our own controller
+                        mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_PROXY;
+                    } else {
+                        // We will control the handler via proxy
+                        mTouchDispatchState |= TOUCH_DISPATCHING_FROM_PROXY;
+                    }
+                }
+                if ((mTouchDispatchState & TOUCH_DISPATCHING_FROM_PROXY) != 0) {
+                    mProxyTouchController = findControllerToHandleTouch(ev);
+                }
+                handled = mProxyTouchController != null;
+            }
+            if (actionMasked == ACTION_UP || actionMasked == ACTION_CANCEL) {
+                mProxyTouchController = null;
+                mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_PROXY;
+            }
+            return handled;
         }
-        int action = ev.getAction();
-        if (action == ACTION_UP || action == ACTION_CANCEL) {
-            mProxyTouchController = null;
-            mTouchDispatchState &= ~TOUCH_DISPATCHING_PROXY;
-        }
-        return handled;
     }
 
     /**
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 5431ba1..d558781 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -86,7 +86,7 @@
             return false;
         }
         if (item.mEventId.getId() > 0) {
-            mLauncher.getStatsLogManager().log(item.mEventId);
+            mLauncher.getStatsLogManager().logger().log(item.mEventId);
         }
         if (item.mClickListener.onLongClick(view)) {
             close(true);
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index a2c7d14..22faf97 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -27,6 +27,7 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
+import static com.android.launcher3.util.SystemUiController.UI_STATE_SCRIM_VIEW;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -53,6 +54,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.core.graphics.ColorUtils;
 import androidx.core.view.ViewCompat;
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
@@ -77,7 +79,6 @@
 
 import java.util.List;
 
-
 /**
  * Simple scrim which draws a flat color
  */
@@ -115,6 +116,7 @@
     private final WallpaperColorInfo mWallpaperColorInfo;
     private final AccessibilityManager mAM;
     protected final int mEndScrim;
+    protected final boolean mIsScrimDark;
 
     private final StateListener<LauncherState> mAccessibilityLauncherStateListener =
             new StateListener<LauncherState>() {
@@ -156,6 +158,7 @@
         mLauncher = Launcher.cast(Launcher.getLauncher(context));
         mWallpaperColorInfo = WallpaperColorInfo.INSTANCE.get(context);
         mEndScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
+        mIsScrimDark = ColorUtils.calculateLuminance(mEndScrim) < 0.5f;
 
         mMaxScrimAlpha = 0.7f;
 
@@ -233,6 +236,7 @@
             mProgress = progress;
             stopDragHandleEducationAnim();
             updateColors();
+            updateSysUiColors();
             updateDragHandleAlpha();
             invalidate();
         }
@@ -245,6 +249,17 @@
                 mEndFlatColor, Math.round((1 - mProgress) * mEndFlatColorAlpha));
     }
 
+    protected void updateSysUiColors() {
+        // Use a light system UI (dark icons) if all apps is behind at least half of the
+        // status bar.
+        boolean forceChange = mProgress <= 0.1f;
+        if (forceChange) {
+            mLauncher.getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, !mIsScrimDark);
+        } else {
+            mLauncher.getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, 0);
+        }
+    }
+
     protected void updateDragHandleAlpha() {
         if (mDragHandle != null) {
             mDragHandle.setAlpha(mDragHandleAlpha);