Merge "Fix quick switching to any task that already appeared" into ub-launcher3-rvc-dev
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index d552daf..7e8e51e 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -38,7 +38,7 @@
   optional ContainerInfo container_info = 7;
 
   // Stores the origin of the Item
-  optional Origin source = 8;
+  optional Attribute attribute = 8;
 }
 
 // Represents various launcher surface where items are placed.
@@ -52,6 +52,8 @@
     PredictionContainer prediction_container = 6;
     SearchResultContainer search_result_container = 7;
     ShortcutsContainer shortcuts_container = 8;
+    SettingsContainer settings_container = 9;
+    PredictedHotseatContainer predicted_hotseat_container = 10;
   }
 }
 
@@ -75,7 +77,12 @@
 message ShortcutsContainer {
 }
 
-enum Origin {
+// Container for generic system shortcuts for launcher specific settings.
+// Typically shown up as popup window by longpressing on empty space on workspace.
+message SettingsContainer {
+}
+
+enum Attribute {
   UNKNOWN = 0;
   DEFAULT_LAYOUT = 1;       // icon automatically placed in workspace, folder, hotseat
   BACKUP_RESTORE = 2;       // icon layout restored from backup
@@ -85,6 +92,8 @@
   ADD_TO_HOMESCREEN = 6;    // play install + launcher home setting
   ALLAPPS_PREDICTION = 7;   // from prediction bar in all apps container
   HOTSEAT_PREDICTION = 8;   // from prediction bar in hotseat container
+  SUGGESTED_LABEL = 9;      // folder icon's label was suggested
+  MANUAL_LABEL = 10;        // folder icon's label was manually edited
 }
 
 // Main app icons
@@ -143,6 +152,14 @@
   optional int32 index = 1;
 }
 
+// Represents hotseat container with prediction feature enabled.
+message PredictedHotseatContainer {
+  optional int32 index = 1;
+
+  // No of hotseat positions filled with predicted items.
+  optional int32 cardinality = 2;
+}
+
 message FolderContainer {
   optional int32 page_index = 1 [default = -1];
   optional int32 grid_x = 2 [default = -1];
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..f881610 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
@@ -23,6 +23,7 @@
 import android.app.prediction.AppTarget;
 import android.content.ComponentName;
 import android.content.Context;
+import android.os.Process;
 
 import androidx.annotation.NonNull;
 
@@ -35,6 +36,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsStore.OnUpdateListener;
+import com.android.launcher3.hybridhotseat.HotseatFileLog;
 import com.android.launcher3.hybridhotseat.HotseatPredictionController;
 import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
 import com.android.launcher3.model.data.ItemInfo;
@@ -310,6 +312,18 @@
      */
     public static void fillInPredictedRank(
             @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) {
+
+        HotseatFileLog hotseatFileLog = HotseatFileLog.INSTANCE.getNoCreate();
+
+        if (hotseatFileLog != null && itemInfo != null && Utilities.IS_DEBUG_DEVICE) {
+            final String pkg = itemInfo.getTargetComponent() != null
+                    ? itemInfo.getTargetComponent().getPackageName() : "unknown";
+            hotseatFileLog.log("UserEvent",
+                    "appLaunch: packageName:" + pkg + ",isWorkApp:" + (itemInfo.user != null
+                            && !Process.myUserHandle().equals(itemInfo.user))
+                            + ",launchLocation:" + itemInfo.container);
+        }
+
         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..5d807d3 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().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..96be5df 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().log(LAUNCHER_HOTSEAT_EDU_ACCEPT);
     }
 
     private void onDismiss(View v) {
         mHotseatEduController.showDimissTip();
         mHotseatEduController.finishOnboarding();
-        logUserAction(false, -1);
+        mLauncher.getStatsLogManager().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().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..6ca07bb 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,7 @@
 
 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 android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -27,6 +28,7 @@
 import android.app.prediction.AppTarget;
 import android.app.prediction.AppTargetEvent;
 import android.content.ComponentName;
+import android.content.Intent;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -50,7 +52,6 @@
 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.model.data.AppInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
@@ -64,6 +65,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 +110,6 @@
     private boolean mIsCacheEmpty;
     private boolean mIsDestroyed = false;
 
-    private HotseatEduController mHotseatEduController;
-
 
     private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
 
@@ -146,11 +147,48 @@
     }
 
     /**
-     * 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
+     * @return
+     */
+    public boolean hasPredictions() {
+        return !mComponentKeyMappers.isEmpty();
     }
 
     @Override
@@ -250,10 +288,6 @@
         if (mAppPredictor != null) {
             mAppPredictor.destroy();
         }
-        if (mHotseatEduController != null) {
-            mHotseatEduController.destroy();
-            mHotseatEduController = null;
-        }
     }
 
     /**
@@ -299,20 +333,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);
@@ -347,12 +381,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);
     }
 
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 ce6bb7d..0ace4cc 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.11f;
+    private static final float RING_EFFECT_RATIO = 0.08f;
 
     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..3b45ec9 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
@@ -88,7 +88,6 @@
      */
     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) {
@@ -169,13 +168,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/QuickstepAtomicAnimationFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 79dc3e2..ba8656d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -156,6 +156,7 @@
         if (toState == NORMAL && fromState == OVERVIEW) {
             config.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL);
             config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
+            config.setInterpolator(ANIM_ALL_APPS_FADE, ACCEL);
             config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
             config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL);
             config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
@@ -210,6 +211,7 @@
                 }
             }
             config.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
+            config.setInterpolator(ANIM_ALL_APPS_FADE, OVERSHOOT_1_2);
             config.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
             config.setInterpolator(ANIM_DEPTH, OVERSHOOT_1_2);
             Interpolator translationInterpolator = ENABLE_OVERVIEW_ACTIONS.get()
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 7b3a787..d55dc0f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
@@ -41,6 +41,7 @@
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.PointF;
@@ -78,9 +79,11 @@
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.LatencyTrackerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.TaskStackChangeListener;
 
 /**
  * Handles the navigation gestures when Launcher is the default home activity.
@@ -900,6 +903,21 @@
 
     protected abstract HomeAnimationFactory createHomeAnimationFactory(long duration);
 
+    private TaskStackChangeListener mActivityRestartListener = new TaskStackChangeListener() {
+        @Override
+        public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
+                boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
+            if (task.taskId == mGestureState.getRunningTaskId()) {
+                // Since this is an edge case, just cancel and relaunch with default activity
+                // options (since we don't know if there's an associated app icon to launch from)
+                endRunningWindowAnim(true /* cancel */);
+                ActivityManagerWrapper.getInstance().unregisterTaskStackListener(
+                        mActivityRestartListener);
+                ActivityManagerWrapper.getInstance().startActivityFromRecents(task.taskId, null);
+            }
+        }
+    };
+
     @UiThread
     private void animateToProgressInternal(float start, float end, long duration,
             Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
@@ -907,6 +925,13 @@
         mGestureState.setEndTarget(target, false /* isAtomic */);
         maybeUpdateRecentsAttachedState();
 
+        // If we are transitioning to launcher, then listen for the activity to be restarted while
+        // the transition is in progress
+        if (mGestureState.getEndTarget().isLauncher) {
+            ActivityManagerWrapper.getInstance().registerTaskStackListener(
+                    mActivityRestartListener);
+        }
+
         if (mGestureState.getEndTarget() == HOME) {
             HomeAnimationFactory homeAnimFactory = createHomeAnimationFactory(duration);
             RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
@@ -1127,6 +1152,7 @@
         }
 
         mActivityInitListener.unregister();
+        ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mActivityRestartListener);
         mTaskSnapshot = null;
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
index 6798cb0..6751723 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
@@ -32,7 +32,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
-import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
@@ -314,8 +313,8 @@
     @Override
     protected float getExtraSpace(Context context, DeviceProfile dp,
             PagedOrientationHandler orientationHandler) {
-        if (dp.isVerticalBarLayout() ||
-                hideShelfInTwoButtonLandscape(context, orientationHandler)) {
+        if ((dp.isVerticalBarLayout() && !showOverviewActions(context))
+                || hideShelfInTwoButtonLandscape(context, orientationHandler)) {
             return 0;
         } else {
             Resources res = context.getResources();
@@ -323,12 +322,14 @@
                 //TODO: this needs to account for the swipe gesture height and accessibility
                 // UI when shown.
                 float actionsBottomMargin = 0;
-                if (getMode(context) == Mode.THREE_BUTTONS) {
-                    actionsBottomMargin = res.getDimensionPixelSize(
+                if (!dp.isVerticalBarLayout()) {
+                    if (getMode(context) == Mode.THREE_BUTTONS) {
+                        actionsBottomMargin = res.getDimensionPixelSize(
                             R.dimen.overview_actions_bottom_margin_three_button);
-                } else {
-                    actionsBottomMargin = res.getDimensionPixelSize(
+                    } else {
+                        actionsBottomMargin = res.getDimensionPixelSize(
                             R.dimen.overview_actions_bottom_margin_gesture);
+                    }
                 }
                 float actionsHeight = actionsBottomMargin
                         + res.getDimensionPixelSize(R.dimen.overview_actions_height);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 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/TaskOverlayFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
index 3cfff7e..a6a08cb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
@@ -16,20 +16,30 @@
 
 package com.android.quickstep;
 
+import static android.view.Surface.ROTATION_0;
+
 import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.graphics.Insets;
 import android.graphics.Matrix;
 import android.graphics.Rect;
+import android.os.Build;
+import android.view.View;
 import android.widget.Toast;
 
+import androidx.annotation.RequiresApi;
+
 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;
@@ -54,6 +64,19 @@
                 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) {
+            for (TaskShortcutFactory actionMenuOption : ACTION_MENU_OPTIONS) {
+                SystemShortcut shortcut = actionMenuOption.getShortcut(activity, taskView);
+                if (shortcut != null) {
+                    shortcuts.add(shortcut);
+                }
+            }
+        }
         return shortcuts;
     }
 
@@ -81,6 +104,11 @@
             TaskShortcutFactory.WELLBEING
     };
 
+    private static final TaskShortcutFactory[] ACTION_MENU_OPTIONS = new TaskShortcutFactory[]{
+        TaskShortcutFactory.SCREENSHOT,
+        TaskShortcutFactory.MODAL
+    };
+
     /**
      * Overlay on each task handling Overview Action Buttons.
      */
@@ -90,10 +118,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() {
@@ -108,32 +140,37 @@
          * 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);
             final boolean isAllowedByPolicy = thumbnail.isRealSnapshot;
-
             getActionsView().setCallbacks(new OverlayUICallbacks() {
                 @Override
                 public void onShare() {
                     if (isAllowedByPolicy) {
-                        imageApi.startShareActivity();
+                        mImageApi.startShareActivity();
                     } else {
                         showBlockedByPolicyMessage();
                     }
                 }
 
+                @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.
@@ -142,6 +179,20 @@
         }
 
         /**
+         * 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.
@@ -159,9 +210,9 @@
          *
          * @return the insets in screen coordinates.
          */
+        @RequiresApi(api = Build.VERSION_CODES.Q)
         public Insets getTaskSnapshotInsets() {
-            // TODO: return the real insets
-            return Insets.of(0, 0, 0, 0);
+            return mThumbnailView.getScaledInsets();
         }
 
         private void showBlockedByPolicyMessage() {
@@ -170,6 +221,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..ea1795c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java
@@ -18,6 +18,8 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static com.android.launcher3.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;
@@ -332,4 +334,19 @@
 
     TaskShortcutFactory WELLBEING = (activity, view) ->
             WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, dummyInfo(view));
+
+    TaskShortcutFactory SCREENSHOT = (activity, tv) -> {
+        if (ENABLE_OVERVIEW_ACTIONS.get()) {
+            return tv.getThumbnail().getTaskOverlay()
+                .getScreenshotShortcut(activity, dummyInfo(tv));
+        }
+        return null;
+    };
+
+    TaskShortcutFactory MODAL = (activity, tv) -> {
+        if (ENABLE_OVERVIEW_ACTIONS.get() && ENABLE_OVERVIEW_SELECTIONS.get()) {
+            return tv.getThumbnail().getTaskOverlay().getModalStateSystemShortcut(dummyInfo(tv));
+        }
+        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 be69a36..37314ea 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -24,7 +24,6 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.quickstep.GestureState.DEFAULT_STATE;
-import static com.android.quickstep.util.RecentsOrientedState.isFixedRotationTransformEnabled;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
@@ -638,9 +637,7 @@
         if (TestProtocol.sDebugTracing) {
             Log.d(TestProtocol.PAUSE_NOT_DETECTED, "handleOrientationSetup.1");
         }
-        if (!isFixedRotationTransformEnabled()) {
-            return;
-        }
+
         baseInputConsumer.notifyOrientationSetup();
     }
 
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 e7b965b..8daa982 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
@@ -20,6 +20,7 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTROLLER;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
@@ -41,6 +42,7 @@
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.anim.SpringAnimationBuilder;
 import com.android.launcher3.graphics.OverviewScrim;
 import com.android.launcher3.statehandlers.DepthController;
@@ -67,7 +69,7 @@
     private final AnimatorSet mAnimators = new AnimatorSet();
 
     public StaggeredWorkspaceAnim(Launcher launcher, float velocity, boolean animateOverviewScrim) {
-        prepareToAnimate(launcher);
+        prepareToAnimate(launcher, animateOverviewScrim);
 
         mVelocity = velocity;
 
@@ -129,8 +131,9 @@
         }
 
         if (animateOverviewScrim) {
-            addScrimAnimationForState(launcher, BACKGROUND_APP, 0);
-            addScrimAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS);
+            PendingAnimation pendingAnimation = new PendingAnimation(ALPHA_DURATION_MS);
+            addScrimAnimationForState(launcher, NORMAL, pendingAnimation);
+            mAnimators.play(pendingAnimation.buildAnim());
         }
 
         addDepthAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS);
@@ -153,7 +156,7 @@
     /**
      * Setup workspace with 0 duration to prepare for our staggered animation.
      */
-    private void prepareToAnimate(Launcher launcher) {
+    private void prepareToAnimate(Launcher launcher, boolean animateOverviewScrim) {
         StateAnimationConfig config = new StateAnimationConfig();
         config.animFlags = ANIM_ALL_COMPONENTS | SKIP_OVERVIEW | SKIP_DEPTH_CONTROLLER;
         config.duration = 0;
@@ -162,6 +165,10 @@
 
         // Stop scrolling so that it doesn't interfere with the translation offscreen.
         launcher.<RecentsView>getOverviewPanel().getScroller().forceFinished(true);
+
+        if (animateOverviewScrim) {
+            addScrimAnimationForState(launcher, BACKGROUND_APP, NO_ANIM_PROPERTY_SETTER);
+        }
     }
 
     public AnimatorSet getAnimators() {
@@ -224,15 +231,14 @@
         mAnimators.play(alpha);
     }
 
-    private void addScrimAnimationForState(Launcher launcher, LauncherState state, long duration) {
-        PendingAnimation builder = new PendingAnimation(duration);
-        launcher.getWorkspace().getStateTransitionAnimation().setScrim(builder, state);
-        builder.setFloat(
+    private void addScrimAnimationForState(Launcher launcher, LauncherState state,
+            PropertySetter setter) {
+        launcher.getWorkspace().getStateTransitionAnimation().setScrim(setter, state);
+        setter.setFloat(
                 launcher.getDragLayer().getOverviewScrim(),
                 OverviewScrim.SCRIM_PROGRESS,
                 state.getOverviewScrimAlpha(launcher),
                 ACCEL_DEACCEL);
-        mAnimators.play(builder.buildAnim());
     }
 
     private void addDepthAnimationForState(Launcher launcher, LauncherState state, long duration) {
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 df89f74..6b99f90 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
@@ -20,6 +20,7 @@
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALL_APPS_EDU_SHOWN;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
 
 import android.animation.Animator;
@@ -231,6 +232,8 @@
                 launcher, parent);
         view.init(launcher);
         launcher.getDragLayer().addView(view);
+        launcher.getStatsLogManager().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 83287c4..95eb10f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
@@ -21,6 +21,8 @@
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 
 import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
@@ -30,9 +32,11 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.Insettable;
 import com.android.launcher3.R;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
+import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.TaskOverlayFactory.OverlayUICallbacks;
 
@@ -43,7 +47,9 @@
  * View for showing action buttons in Overview
  */
 public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayout
-        implements OnClickListener {
+        implements OnClickListener, Insettable {
+
+    private final Rect mInsets = new Rect();
 
     @IntDef(flag = true, value = {
             HIDDEN_UNSUPPORTED_NAVIGATION,
@@ -129,6 +135,18 @@
         updateHiddenFlags(HIDDEN_UNSUPPORTED_NAVIGATION, !removeShelfFromOverview(getContext()));
     }
 
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        updateVerticalMargin(SysUINavigationMode.getMode(getContext()));
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+        mInsets.set(insets);
+        updateVerticalMargin(SysUINavigationMode.getMode(getContext()));
+    }
+
     public void updateHiddenFlags(@ActionsHiddenFlags int visibilityFlags, boolean enable) {
         if (enable) {
             mHiddenFlags |= visibilityFlags;
@@ -152,16 +170,20 @@
         return mMultiValueAlpha.getProperty(INDEX_FULLSCREEN_ALPHA);
     }
 
-    /** Updates vertical margins for different navigation mode. */
-    public void updateVerticalMarginForNavModeChange(Mode mode) {
-        int bottomMargin = 0;
-        if (mode == Mode.THREE_BUTTONS) {
+    /** Updates vertical margins for different navigation mode or configuration changes. */
+    public void updateVerticalMargin(Mode mode) {
+        int bottomMargin;
+        int orientation = getResources().getConfiguration().orientation;
+        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+            bottomMargin = 0;
+        } else if (mode == Mode.THREE_BUTTONS) {
             bottomMargin = getResources()
                     .getDimensionPixelSize(R.dimen.overview_actions_bottom_margin_three_button);
         } else {
             bottomMargin = getResources()
                     .getDimensionPixelSize(R.dimen.overview_actions_bottom_margin_gesture);
         }
+        bottomMargin += mInsets.bottom;
         LayoutParams params = (LayoutParams) getLayoutParams();
         params.setMargins(
                 params.leftMargin, params.topMargin, params.rightMargin, bottomMargin);
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 f27bedb..05f4e91 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
@@ -607,6 +607,17 @@
         }
     }
 
+    /**
+     * 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();
@@ -616,7 +627,7 @@
     @Override
     protected void onPageEndTransition() {
         super.onPageEndTransition();
-        if (getScrollX() == getScrollForPage(getPageNearestToCenterOfScreen())) {
+        if (isClearAllHidden()) {
             LayoutUtils.setViewEnabled(mActionsView, true);
         }
         if (getNextPage() > 0) {
@@ -1627,8 +1638,10 @@
                     : View.LAYOUT_DIRECTION_RTL);
             mClearAllButton.setRotation(mOrientationHandler.getDegreesRotated());
             mActivity.getDragLayer().recreateControllers();
+            boolean isInLandscape = touchRotation != 0
+                    || mOrientationState.getLauncherRotation() != ROTATION_0;
             mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION,
-                    touchRotation != 0 || mOrientationState.getLauncherRotation() != ROTATION_0);
+                    !mOrientationState.canLauncherRotate() && isInLandscape);
             resetPaddingFromTaskSize();
             requestLayout();
         }
@@ -1898,7 +1911,7 @@
             anim.play(depthAnimator);
         }
         anim.play(progressAnim);
-        anim.setDuration(duration).setInterpolator(interpolator);
+        anim.setInterpolator(interpolator);
 
         mPendingAnimation = new PendingAnimation(duration);
         mPendingAnimation.add(anim);
@@ -2180,6 +2193,11 @@
         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);
+        LayoutUtils.setViewEnabled(mActionsView, true);
     }
 
     @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 ead0c8b..26fb563 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
@@ -27,6 +27,7 @@
 import android.graphics.ColorFilter;
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Insets;
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.PorterDuff;
@@ -34,12 +35,15 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Shader;
+import android.os.Build;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.Property;
 import android.view.Surface;
 import android.view.View;
 
+import androidx.annotation.RequiresApi;
+
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
@@ -211,6 +215,38 @@
         return fallback;
     }
 
+    /**
+     * Get the scaled insets that are being used to draw the task view. This is a subsection of
+     * the full snapshot.
+     * @return the insets in snapshot bitmap coordinates.
+     */
+    @RequiresApi(api = Build.VERSION_CODES.Q)
+    public Insets getScaledInsets() {
+        if (mThumbnailData == null) {
+            return Insets.NONE;
+        }
+
+        RectF bitmapRect = new RectF(
+                0, 0,
+                mThumbnailData.thumbnail.getWidth(), mThumbnailData.thumbnail.getHeight());
+        RectF viewRect = new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight());
+
+        // The position helper matrix tells us how to transform the bitmap to fit the view, the
+        // inverse tells us where the view would be in the bitmaps coordinates. The insets are the
+        // difference between the bitmap bounds and the projected view bounds.
+        Matrix boundsToBitmapSpace = new Matrix();
+        mPreviewPositionHelper.getMatrix().invert(boundsToBitmapSpace);
+        RectF boundsInBitmapSpace = new RectF();
+        boundsToBitmapSpace.mapRect(boundsInBitmapSpace, viewRect);
+
+        return Insets.of(
+            Math.round(boundsInBitmapSpace.left),
+            Math.round(boundsInBitmapSpace.top),
+            Math.round(bitmapRect.right - boundsInBitmapSpace.right),
+            Math.round(bitmapRect.bottom - boundsInBitmapSpace.bottom));
+    }
+
+
     public int getSysUiStatusNavFlags() {
         if (mThumbnailData != null) {
             int flags = 0;
@@ -412,6 +448,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..e25c85b 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
@@ -425,13 +425,16 @@
     }
 
     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().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);
+            }
         }
         return mMenuView != null;
     }
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 0968d8e..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) {
@@ -93,7 +95,7 @@
     public void onNavigationModeChanged(Mode newMode) {
         getDragLayer().recreateControllers();
         if (mActionsView != null && isOverviewActionsEnabled()) {
-            mActionsView.updateVerticalMarginForNavModeChange(newMode);
+            mActionsView.updateVerticalMargin(newMode);
         }
     }
 
@@ -175,7 +177,7 @@
             // Overview is above all other launcher elements, including qsb, so move it to the top.
             getOverviewPanel().bringToFront();
             mActionsView.bringToFront();
-            mActionsView.updateVerticalMarginForNavModeChange(SysUINavigationMode.getMode(this));
+            mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this));
         }
     }
 
@@ -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/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
index fc60434..5ad0bca 100644
--- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
@@ -26,7 +26,6 @@
 import android.content.Context;
 import android.os.Build;
 import android.os.Handler;
-import android.util.Log;
 
 import androidx.annotation.BinderThread;
 import androidx.annotation.UiThread;
@@ -154,16 +153,8 @@
 
                 // Because t=0 has the app icon in its original spot, we can skip the
                 // first frame and have the same movement one frame earlier.
-                int singleFrameMs = getSingleFrameMs(context);
-                long playTime = singleFrameMs;
-                // b/153821199 Add logs to debug crash but ensure release builds do not crash.
-                if (Utilities.IS_DEBUG_DEVICE) {
-                    Log.e(TAG, "Total duration=[" + mAnimator.getTotalDuration()
-                            + "], singleFrameMs=[" + singleFrameMs + "], mAnimator=" + mAnimator);
-                } else {
-                    playTime = Math.min(singleFrameMs, mAnimator.getTotalDuration());
-                }
-                mAnimator.setCurrentPlayTime(playTime);
+                mAnimator.setCurrentPlayTime(
+                        Math.min(getSingleFrameMs(context), mAnimator.getTotalDuration()));
             }
         }
     }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index adf2321..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;
@@ -23,7 +24,6 @@
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
 import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
-import static com.android.quickstep.util.RecentsOrientedState.isFixedRotationTransformEnabled;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
@@ -49,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;
@@ -113,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;
@@ -194,13 +241,29 @@
             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() {
-        if (!isFixedRotationTransformEnabled()) {
-            return;
-        }
-
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mFrozenTaskListener);
         mOnDestroyFrozenTaskRunnable = () -> ActivityManagerWrapper.getInstance()
                 .unregisterTaskStackListener(mFrozenTaskListener);
@@ -260,7 +323,7 @@
 
     @Override
     public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) {
-        if (info.id != getDisplayId() || (flags & CHANGE_FRAME_DELAY) == CHANGE_FRAME_DELAY) {
+        if (info.id != getDisplayId() || flags == CHANGE_FRAME_DELAY) {
             // ignore displays that aren't running launcher and frame refresh rate changes
             return;
         }
@@ -273,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);
+        }
     }
 
     /**
@@ -558,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) {
@@ -586,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) {
@@ -599,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
@@ -608,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 04f1169..390330f 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -17,6 +17,7 @@
 package com.android.quickstep.logging;
 
 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.FOLDER;
+import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.PREDICTED_HOTSEAT_CONTAINER;
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__ALLAPPS;
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__BACKGROUND;
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__HOME;
@@ -33,6 +34,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.AllAppsList;
 import com.android.launcher3.model.BaseModelUpdateTask;
@@ -92,6 +94,11 @@
         log(event, DEFAULT_INSTANCE_ID, info);
     }
 
+    @Override
+    public void log(EventEnum event, ItemInfo itemInfo) {
+        logInternal(event, DEFAULT_INSTANCE_ID, itemInfo);
+    }
+
     /**
      * Logs an event and accompanying {@link InstanceId} and {@link LauncherAtom.ItemInfo}.
      */
@@ -104,6 +111,19 @@
     }
 
     /**
+     * Logs a ranking event and accompanying {@link InstanceId} and package name.
+     */
+    @Override
+    public void log(EventEnum rankingEvent, InstanceId instanceId, @Nullable String packageName,
+            int position) {
+        SysUiStatsLog.write(SysUiStatsLog.RANKING_SELECTED,
+                rankingEvent.getId() /* event_id = 1; */,
+                packageName /* package_name = 2; */,
+                instanceId.getId() /* instance_id = 3; */,
+                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.
      */
@@ -120,6 +140,27 @@
         logInternal(event, DEFAULT_INSTANCE_ID, info, srcState, dstState);
     }
 
+    private void logInternal(EventEnum event, InstanceId instanceId, @Nullable ItemInfo info) {
+        LauncherAppState.getInstance(sContext).getModel().enqueueModelUpdateTask(
+                new BaseModelUpdateTask() {
+                    @Override
+                    public void execute(LauncherAppState app, BgDataModel dataModel,
+                            AllAppsList apps) {
+                        LauncherAtom.ItemInfo atomInfo = LauncherAtom.ItemInfo.getDefaultInstance();
+                        if (info != null) {
+                            if (info.container >= 0) {
+                                atomInfo = info.buildProto(dataModel.folders.get(info.container));
+                            } else {
+                                atomInfo = info.buildProto();
+                            }
+                        }
+                        logInternal(event, instanceId, atomInfo,
+                                LAUNCHER_UICHANGED__DST_STATE__HOME,
+                                LAUNCHER_UICHANGED__DST_STATE__BACKGROUND);
+                    }
+                });
+    }
+
     /**
      * Logs an event and accompanying {@link InstanceId} and {@link LauncherAtom.ItemInfo}.
      */
@@ -128,14 +169,14 @@
         info = info == null ? LauncherAtom.ItemInfo.getDefaultInstance() : info;
 
         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));
+                    getStateString(dstState), info)
+                    : String.format("\n%s (State:%s->%s) (InstanceId:%s)\n%s", name,
+                            getStateString(srcState), getStateString(dstState), instanceId, info));
         }
 
         if (!Utilities.ATLEAST_R) {
@@ -167,7 +208,7 @@
                 info.getFolderIcon().getFromLabelState().getNumber() /* fromState */,
                 info.getFolderIcon().getToLabelState().getNumber() /* toState */,
                 info.getFolderIcon().getLabelInfo() /* edittext */,
-                info.getFolderIcon().getCardinality() /* cardinality */);
+                getCardinality(info) /* cardinality */);
     }
 
     /**
@@ -180,33 +221,37 @@
     }
 
     private class SnapshotWorker extends BaseModelUpdateTask {
+        private final InstanceId mInstanceId;
+        SnapshotWorker() {
+            mInstanceId = new InstanceIdSequence(
+                    1 << 20 /*InstanceId.INSTANCE_ID_MAX*/).newInstanceId();
+        }
 
         @Override
         public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
             IntSparseArrayMap<FolderInfo> folders = dataModel.folders.clone();
             ArrayList<ItemInfo> workspaceItems = (ArrayList) dataModel.workspaceItems.clone();
             ArrayList<LauncherAppWidgetInfo> appWidgets = (ArrayList) dataModel.appWidgets.clone();
-
             for (ItemInfo info : workspaceItems) {
                 LauncherAtom.ItemInfo atomInfo = info.buildProto(null);
-                writeSnapshot(atomInfo);
+                writeSnapshot(atomInfo, mInstanceId);
             }
             for (FolderInfo fInfo : folders) {
                 for (ItemInfo info : fInfo.contents) {
                     LauncherAtom.ItemInfo atomInfo = info.buildProto(fInfo);
-                    writeSnapshot(atomInfo);
+                    writeSnapshot(atomInfo, mInstanceId);
                 }
             }
             for (ItemInfo info : appWidgets) {
                 LauncherAtom.ItemInfo atomInfo = info.buildProto(null);
-                writeSnapshot(atomInfo);
+                writeSnapshot(atomInfo, mInstanceId);
             }
         }
     }
 
-    private static void writeSnapshot(LauncherAtom.ItemInfo info) {
+    private static void writeSnapshot(LauncherAtom.ItemInfo info, InstanceId instanceId) {
         if (IS_VERBOSE) {
-            Log.d(TAG, "\nwriteSnapshot:" + info);
+            Log.d(TAG, String.format("\nwriteSnapshot(%d):\n%s", instanceId.getId(), info));
         }
         if (!Utilities.ATLEAST_R) {
             return;
@@ -214,7 +259,7 @@
         SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_SNAPSHOT,
                 0 /* event_id */,
                 info.getItemCase().getNumber() /* target_id */,
-                0 /* instance_id */,
+                instanceId.getId() /* instance_id */,
                 0 /* uid */,
                 getPackageName(info) /* package_name */,
                 getComponentName(info) /* component_name */,
@@ -226,12 +271,18 @@
                 getPageId(info, true) /* page_id_parent */,
                 getHierarchy(info) /* hierarchy */,
                 info.getIsWork() /* is_work_profile */,
-                0 /* origin TODO */,
-                0 /* cardinality */,
+                info.getAttribute().getNumber() /* origin */,
+                getCardinality(info) /* cardinality */,
                 info.getWidget().getSpanX(),
                 info.getWidget().getSpanY());
     }
 
+    private static int getCardinality(LauncherAtom.ItemInfo info) {
+        return info.getContainerInfo().getContainerCase().equals(PREDICTED_HOTSEAT_CONTAINER)
+                ? info.getContainerInfo().getPredictedHotseatContainer().getCardinality()
+                : info.getFolderIcon().getCardinality();
+    }
+
     private static String getPackageName(LauncherAtom.ItemInfo info) {
         switch (info.getItemCase()) {
             case APPLICATION:
@@ -308,7 +359,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/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 922f5ac..5af3d70 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -72,8 +72,6 @@
     private static final String TAG = "RecentsOrientedState";
     private static final boolean DEBUG = true;
 
-    private static final String FIXED_ROTATION_TRANSFORM_SETTING_NAME = "fixed_rotation_transform";
-
     private ContentObserver mSystemAutoRotateObserver = new ContentObserver(new Handler()) {
         @Override
         public void onChange(boolean selfChange) {
@@ -94,25 +92,22 @@
     private static final int FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY = 1 << 0;
     // Multiple orientation is only supported if density is < 600
     private static final int FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY = 1 << 1;
-    // Feature flag controlling the multi-orientation feature
-    private static final int FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_FLAG = 1 << 2;
     // Shared prefs for rotation, only if activity supports it
-    private static final int FLAG_HOME_ROTATION_ALLOWED_IN_PREFS = 1 << 3;
+    private static final int FLAG_HOME_ROTATION_ALLOWED_IN_PREFS = 1 << 2;
     // If the user has enabled system rotation
-    private static final int FLAG_SYSTEM_ROTATION_ALLOWED = 1 << 4;
+    private static final int FLAG_SYSTEM_ROTATION_ALLOWED = 1 << 3;
     // Multiple orientation is not supported in multiwindow mode
-    private static final int FLAG_MULTIWINDOW_ROTATION_ALLOWED = 1 << 5;
+    private static final int FLAG_MULTIWINDOW_ROTATION_ALLOWED = 1 << 4;
     // Whether to rotation sensor is supported on the device
-    private static final int FLAG_ROTATION_WATCHER_SUPPORTED = 1 << 6;
+    private static final int FLAG_ROTATION_WATCHER_SUPPORTED = 1 << 5;
     // Whether to enable rotation watcher when multi-rotation is supported
-    private static final int FLAG_ROTATION_WATCHER_ENABLED = 1 << 7;
+    private static final int FLAG_ROTATION_WATCHER_ENABLED = 1 << 6;
     // Enable home rotation for UI tests, ignoring home rotation value from prefs
-    private static final int FLAG_HOME_ROTATION_FORCE_ENABLED_FOR_TESTING = 1 << 8;
+    private static final int FLAG_HOME_ROTATION_FORCE_ENABLED_FOR_TESTING = 1 << 7;
 
     private static final int MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE =
             FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY
-            | FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY
-            | FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_FLAG;
+            | FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY;
 
     // State for which rotation watcher will be enabled. We skip it when home rotation or
     // multi-window is enabled as in that case, activity itself rotates.
@@ -123,9 +118,6 @@
     private SysUINavigationMode.NavigationModeChangeListener mNavModeChangeListener =
             newMode -> setFlag(FLAG_ROTATION_WATCHER_SUPPORTED, newMode != TWO_BUTTONS);
 
-    /** TODO: Remove once R ships. This is unlikely to change across different swipe gestures. */
-    private static boolean sFixedRotationEnabled;
-
     private final Context mContext;
     private final ContentResolver mContentResolver;
     private final SharedPreferences mSharedPrefs;
@@ -168,11 +160,6 @@
         if (originalSmallestWidth < 600) {
             mFlags |= FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY;
         }
-        sFixedRotationEnabled = Settings.Global.getInt(
-                context.getContentResolver(), FIXED_ROTATION_TRANSFORM_SETTING_NAME, 1) == 1;
-        if (sFixedRotationEnabled) {
-            mFlags |= FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_FLAG;
-        }
         initFlags();
     }
 
@@ -210,7 +197,7 @@
         mPreviousRotation = touchRotation;
 
         if (mLauncherRotation == mTouchRotation || canLauncherRotate()) {
-            mOrientationHandler = PagedOrientationHandler.HOME_ROTATED;
+            mOrientationHandler = PagedOrientationHandler.PORTRAIT;
             if (DEBUG) {
                 Log.d(TAG, "current RecentsOrientedState: " + this);
             }
@@ -458,7 +445,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) {
@@ -481,7 +469,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
@@ -521,14 +510,6 @@
         }
     }
 
-    /**
-     * Returns true if system can keep Launcher fixed to portrait layout even if the
-     * foreground app is rotated
-     */
-    public static boolean isFixedRotationTransformEnabled() {
-        return sFixedRotationEnabled;
-    }
-
     @NonNull
     @Override
     public String toString() {
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index 3d048a6..f20a0ba 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -25,7 +25,6 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.launcher3.Launcher;
-import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.util.RaceConditionReproducer;
 import com.android.quickstep.NavigationModeSwitchRule.Mode;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
@@ -99,5 +98,7 @@
             // 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/layout/work_apps_paused.xml b/res/layout/work_apps_paused.xml
index 08e1c98..7f1107f 100644
--- a/res/layout/work_apps_paused.xml
+++ b/res/layout/work_apps_paused.xml
@@ -15,6 +15,7 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:background="?attr/allAppsScrimColor"
     android:padding="48dp"
     android:orientation="vertical"
     android:gravity="center">
@@ -34,6 +35,7 @@
     <TextView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:id="@+id/work_apps_paused_content"
         android:textColor="?attr/workProfileOverlayTextColor"
         android:text="@string/work_apps_paused_body"
         android:textAlignment="center"
diff --git a/res/layout/work_mode_switch.xml b/res/layout/work_mode_switch.xml
index 9cb7ce8..b5237db 100644
--- a/res/layout/work_mode_switch.xml
+++ b/res/layout/work_mode_switch.xml
@@ -28,11 +28,11 @@
     android:gravity="start"
     android:lines="1"
     android:showText="false"
-    android:textSize="16sp"
+    android:textSize="@dimen/work_profile_footer_text_size"
     android:background="?attr/allAppsScrimColor"
     android:text="@string/work_profile_toggle_label"
-    android:paddingBottom="@dimen/all_apps_work_profile_tab_footer_padding"
-    android:paddingLeft="@dimen/all_apps_work_profile_tab_footer_padding"
-    android:paddingRight="@dimen/all_apps_work_profile_tab_footer_padding"
-    android:paddingTop="@dimen/all_apps_work_profile_tab_footer_padding"
+    android:paddingBottom="@dimen/work_profile_footer_padding"
+    android:paddingLeft="@dimen/work_profile_footer_padding"
+    android:paddingRight="@dimen/work_profile_footer_padding"
+    android:paddingTop="@dimen/work_profile_footer_padding"
 />
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 31b12ad..947e635 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -96,7 +96,6 @@
     <!-- The size of corner radius of the arrow in the arrow toast. -->
     <dimen name="arrow_toast_corner_radius">2dp</dimen>
 
-    <dimen name="all_apps_work_profile_tab_footer_padding">20dp</dimen>
 
 <!-- Search bar in All Apps -->
     <dimen name="all_apps_header_max_elevation">3dp</dimen>
@@ -105,6 +104,9 @@
 
     <dimen name="all_apps_divider_margin_vertical">8dp</dimen>
 
+    <dimen name="work_profile_footer_padding">20dp</dimen>
+    <dimen name="work_profile_footer_text_size">16sp</dimen>
+
 <!-- Widget tray -->
     <dimen name="widget_preview_label_vertical_padding">8dp</dimen>
     <dimen name="widget_preview_label_horizontal_padding">16dp</dimen>
diff --git a/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java b/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java
index 2584f65..3a252dc 100644
--- a/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -92,7 +92,8 @@
                 SCREEN, CELLX, CELLY, RESTORED, INTENT
         });
 
-        mLoaderCursor = new LoaderCursor(mCursor, LauncherSettings.Favorites.CONTENT_URI, mApp);
+        mLoaderCursor = new LoaderCursor(mCursor, LauncherSettings.Favorites.CONTENT_URI, mApp,
+                new UserManagerState());
         mLoaderCursor.allUsers.put(0, Process.myUserHandle());
     }
 
diff --git a/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java b/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
index 4b0ae7e..87fe3c0 100644
--- a/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
@@ -199,7 +199,7 @@
         }
 
         @Override
-        public void bindAllApplications(AppInfo[] apps) {
+        public void bindAllApplications(AppInfo[] apps, int flags) {
             mAppInfos = apps;
         }
 
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 572615f..cd27a2d 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -174,7 +174,8 @@
                 targetInfo.first, TYPE_WINDOW_STATE_CHANGED, targetInfo.second);
 
         if (mIsOpen) {
-            performAccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+            getAccessibilityInitialFocusView().performAccessibilityAction(
+                    AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
         }
         ActivityContext.lookupContext(getContext()).getDragLayer()
                 .sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED);
@@ -184,6 +185,11 @@
         return null;
     }
 
+    /** Returns the View that Accessibility services should focus on first. */
+    protected View getAccessibilityInitialFocusView() {
+        return this;
+    }
+
     /**
      * Returns a view matching FloatingViewType
      */
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index d1d5e26..268b910 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -187,8 +187,9 @@
                         sourceContainer);
             }
             getUserEventDispatcher().logAppLaunch(v, intent, user);
-            getStatsLogManager().log(LAUNCHER_APP_LAUNCH_TAP, item == null ? null
-                    : item.buildProto());
+            if (item != null) {
+                getStatsLogManager().log(LAUNCHER_APP_LAUNCH_TAP, item.buildProto());
+            }
             return true;
         } catch (NullPointerException|ActivityNotFoundException|SecurityException e) {
             Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 1f84c42..ec32e62 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -2455,8 +2455,9 @@
      *
      * Implementation of the method from LauncherModel.Callbacks.
      */
-    public void bindAllApplications(AppInfo[] apps) {
-        mAppsView.getAppsStore().setApps(apps);
+    @Override
+    public void bindAllApplications(AppInfo[] apps, int flags) {
+        mAppsView.getAppsStore().setApps(apps, flags);
     }
 
     /**
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 3b1c7bb..f434c91 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -117,7 +117,7 @@
         @Override
         public void run() {
             if (mModelLoaded && hasShortcutsPermission(mApp.getContext())
-                    != mBgDataModel.hasShortcutHostPermission) {
+                    != mBgAllAppsList.hasShortcutHostPermission()) {
                 forceReload();
             }
         }
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 2a5ee00..208d565 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -162,6 +162,7 @@
         // Represents search results view.
         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 String containerToString(int container) {
             switch (container) {
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 32685b0..f6c392b 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1342,7 +1342,6 @@
 
         ValueAnimator stepAnimator = ValueAnimator.ofFloat(0, 1);
         stepAnimator.addUpdateListener(listener);
-        stepAnimator.setDuration(config.duration);
         stepAnimator.addListener(listener);
         animation.add(stepAnimator);
     }
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 1d6bb62..c989e7b 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -16,9 +16,14 @@
 package com.android.launcher3.allapps;
 
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
 
 import android.animation.ValueAnimator;
 import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Point;
@@ -37,7 +42,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
-import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.recyclerview.widget.DefaultItemAnimator;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
@@ -54,7 +58,6 @@
 import com.android.launcher3.keyboard.FocusedItemDecorator;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.ItemInfoMatcher;
@@ -195,7 +198,7 @@
     }
 
     private void resetWorkProfile() {
-        mWorkModeSwitch.refresh();
+        mWorkModeSwitch.update(!mAllAppsStore.hasModelFlag(FLAG_QUIET_MODE_ENABLED));
         mAH[AdapterHolder.WORK].setupOverlay();
         mAH[AdapterHolder.WORK].applyPadding();
     }
@@ -431,11 +434,22 @@
     }
 
     private void setupWorkToggle() {
-        mWorkModeSwitch = (WorkModeSwitch) mLauncher.getLayoutInflater().inflate(
-                R.layout.work_mode_switch, this, false);
-        this.addView(mWorkModeSwitch);
-        mWorkModeSwitch.setInsets(mInsets);
-        mWorkModeSwitch.post(() -> mAH[AdapterHolder.WORK].applyPadding());
+        if (Utilities.ATLEAST_P) {
+            mWorkModeSwitch = (WorkModeSwitch) mLauncher.getLayoutInflater().inflate(
+                    R.layout.work_mode_switch, this, false);
+            this.addView(mWorkModeSwitch);
+            mWorkModeSwitch.setInsets(mInsets);
+            mWorkModeSwitch.post(() -> mAH[AdapterHolder.WORK].applyPadding());
+        }
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        View overlay = mAH[AdapterHolder.WORK].getOverlayView();
+        int v = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE ? GONE : VISIBLE;
+        overlay.findViewById(R.id.work_apps_paused_title).setVisibility(v);
+        overlay.findViewById(R.id.work_apps_paused_content).setVisibility(v);
     }
 
     private void replaceRVContainer(boolean showTabs) {
@@ -470,7 +484,9 @@
         }
         reset(true /* animate */);
         if (mWorkModeSwitch != null) {
-            mWorkModeSwitch.setWorkTabVisible(pos == AdapterHolder.WORK);
+            mWorkModeSwitch.setWorkTabVisible(pos == AdapterHolder.WORK
+                    && mAllAppsStore.hasModelFlag(
+                            FLAG_HAS_SHORTCUT_PERMISSION | FLAG_QUIET_MODE_CHANGE_PERMISSION));
         }
     }
 
@@ -580,15 +596,8 @@
                         && valueAnimator.getAnimatedFraction() >= FLING_ANIMATION_THRESHOLD) {
                     int searchViewId = getSearchView().getId();
                     addSpringView(searchViewId);
-
                     finishWithShiftAndVelocity(1, velocity * FLING_VELOCITY_MULTIPLIER,
-                            new DynamicAnimation.OnAnimationEndListener() {
-                                @Override
-                                public void onAnimationEnd(DynamicAnimation animation,
-                                        boolean canceled, float value, float velocity) {
-                                    removeSpringView(searchViewId);
-                                }
-                            });
+                            (anim, canceled, value, velocity) -> removeSpringView(searchViewId));
 
                     shouldSpring = false;
                 }
@@ -647,7 +656,7 @@
 
         void setupOverlay() {
             if (!mIsWork || recyclerView == null) return;
-            boolean workDisabled = UserCache.INSTANCE.get(mLauncher).isAnyProfileQuietModeEnabled();
+            boolean workDisabled = mAllAppsStore.hasModelFlag(FLAG_QUIET_MODE_ENABLED);
             if (mWorkDisabled == workDisabled) return;
             recyclerView.setContentDescription(workDisabled ? mLauncher.getString(
                     R.string.work_apps_paused_content_description) : null);
@@ -655,10 +664,12 @@
             recyclerView.setItemAnimator(new DefaultItemAnimator());
             if (workDisabled) {
                 overlayView.setAlpha(0);
-                appsList.updateItemFilter((info, cn) -> false);
                 recyclerView.addAutoSizedOverlay(overlayView);
                 overlayView.animate().alpha(1).withEndAction(
-                        () -> recyclerView.setItemAnimator(null)).start();
+                        () -> {
+                            appsList.updateItemFilter((info, cn) -> false);
+                            recyclerView.setItemAnimator(null);
+                        }).start();
             } else if (mInfoMatcher != null) {
                 appsList.updateItemFilter(mInfoMatcher);
                 overlayView.animate().alpha(0).withEndAction(() -> {
@@ -671,8 +682,12 @@
 
         void applyPadding() {
             if (recyclerView != null) {
-                int bottomOffset =
-                        mWorkModeSwitch != null && mIsWork ? mWorkModeSwitch.getHeight() : 0;
+                Resources res = getResources();
+                int switchH = res.getDimensionPixelSize(R.dimen.work_profile_footer_padding) * 2
+                        + mInsets.bottom + Utilities.calculateTextHeight(
+                        res.getDimension(R.dimen.work_profile_footer_text_size));
+
+                int bottomOffset = mWorkModeSwitch != null && mIsWork ? switchH : 0;
                 recyclerView.setPadding(padding.left, padding.top, padding.right,
                         padding.bottom + bottomOffset);
             }
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index b11312c..3ae0a18 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -52,6 +52,7 @@
 
     private final List<OnUpdateListener> mUpdateListeners = new CopyOnWriteArrayList<>();
     private final ArrayList<ViewGroup> mIconContainers = new ArrayList<>();
+    private int mModelFlags;
 
     private int mDeferUpdatesFlags = 0;
     private boolean mUpdatePending = false;
@@ -63,11 +64,21 @@
     /**
      * Sets the current set of apps.
      */
-    public void setApps(AppInfo[] apps) {
+    public void setApps(AppInfo[] apps, int flags) {
         mApps = apps;
+        mModelFlags = flags;
         notifyUpdate();
     }
 
+    /**
+     * @see com.android.launcher3.model.BgDataModel.Callbacks#FLAG_QUIET_MODE_ENABLED
+     * @see com.android.launcher3.model.BgDataModel.Callbacks#FLAG_HAS_SHORTCUT_PERMISSION
+     * @see com.android.launcher3.model.BgDataModel.Callbacks#FLAG_QUIET_MODE_CHANGE_PERMISSION
+     */
+    public boolean hasModelFlag(int mask) {
+        return (mModelFlags & mask) != 0;
+    }
+
     public AppInfo getApp(ComponentKey key) {
         mTempInfo.componentName = key.componentName;
         mTempInfo.user = key.user;
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 99ed0ad..8260336 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -189,7 +189,6 @@
                 : FAST_OUT_SLOW_IN;
 
         Animator anim = createSpringAnimation(mProgress, targetProgress);
-        anim.setDuration(config.duration);
         anim.setInterpolator(config.getInterpolator(ANIM_VERTICAL_PROGRESS, interpolator));
         anim.addListener(getProgressAnimatorListener());
         builder.add(anim);
diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java
index 6692af5..4567ee6 100644
--- a/src/com/android/launcher3/allapps/WorkModeSwitch.java
+++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java
@@ -15,11 +15,8 @@
  */
 package com.android.launcher3.allapps;
 
-import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
-
 import android.content.Context;
 import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
 import android.graphics.Rect;
 import android.os.AsyncTask;
 import android.os.Process;
@@ -59,7 +56,6 @@
     public WorkModeSwitch(Context context, AttributeSet attrs) {
         super(context, attrs);
         init();
-
     }
 
     public WorkModeSwitch(Context context, AttributeSet attrs, int defStyleAttr) {
@@ -73,9 +69,7 @@
     }
 
     @Override
-    public void setChecked(boolean checked) {
-
-    }
+    public void setChecked(boolean checked) { }
 
     @Override
     public void toggle() {
@@ -84,20 +78,17 @@
         trySetQuietModeEnabledToAllProfilesAsync(isChecked());
     }
 
-    private void setCheckedInternal(boolean checked) {
-        super.setChecked(checked);
+    /**
+     * Sets the enabled or disabled state of the button
+     * @param isChecked
+     */
+    public void update(boolean isChecked) {
+        super.setChecked(isChecked);
         setCompoundDrawablesRelativeWithIntrinsicBounds(
-                checked ? R.drawable.ic_corp : R.drawable.ic_corp_off, 0, 0, 0);
-    }
-
-    public void refresh() {
-        if (!shouldShowWorkSwitch()) return;
-        UserCache userManager = UserCache.INSTANCE.get(getContext());
-        setCheckedInternal(!userManager.isAnyProfileQuietModeEnabled());
+                isChecked ? R.drawable.ic_corp : R.drawable.ic_corp_off, 0, 0, 0);
         setEnabled(true);
     }
 
-
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
         if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
@@ -116,12 +107,6 @@
         return super.onTouchEvent(ev);
     }
 
-    @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        super.onLayout(changed, l, t, r, b);
-        this.setVisibility(shouldShowWorkSwitch() ? VISIBLE : GONE);
-    }
-
     private void trySetQuietModeEnabledToAllProfilesAsync(boolean enabled) {
         new SetQuietModeEnabledAsyncTask(enabled, new WeakReference<>(this)).execute();
     }
@@ -138,13 +123,12 @@
      * Animates in/out work profile toggle panel based on the tab user is on
      */
     public void setWorkTabVisible(boolean workTabVisible) {
-        if (!shouldShowWorkSwitch()) return;
         clearAnimation();
         if (workTabVisible) {
             setVisibility(VISIBLE);
             setAlpha(0);
             animate().alpha(1).start();
-            showTipifNeeded();
+            showTipIfNeeded();
         } else {
             animate().alpha(0).withEndAction(() -> this.setVisibility(GONE)).start();
         }
@@ -201,16 +185,10 @@
         }
     }
 
-    private boolean shouldShowWorkSwitch() {
-        return Utilities.ATLEAST_P && (hasShortcutsPermission(getContext())
-                || getContext().checkSelfPermission("android.permission.MODIFY_QUIET_MODE")
-                == PackageManager.PERMISSION_GRANTED);
-    }
-
     /**
      * Shows a work tip on the Nth work tab open
      */
-    public void showTipifNeeded() {
+    public void showTipIfNeeded() {
         Context context = getContext();
         SharedPreferences prefs = Utilities.getPrefs(context);
         int tipCounter = prefs.getInt(KEY_WORK_TIP_COUNTER, WORK_TIP_THRESHOLD);
diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java
index afeb341..4195933 100644
--- a/src/com/android/launcher3/anim/PendingAnimation.java
+++ b/src/com/android/launcher3/anim/PendingAnimation.java
@@ -69,7 +69,7 @@
     }
 
     public void add(Animator a, SpringProperty springProperty) {
-        mAnim.play(a);
+        mAnim.play(a.setDuration(mDuration));
         addAnimationHoldersRecur(a, mDuration, springProperty, mAnimHolders);
     }
 
@@ -87,7 +87,7 @@
         }
         ObjectAnimator anim = ObjectAnimator.ofFloat(view, View.ALPHA, alpha);
         anim.addListener(new AlphaUpdateListener(view));
-        anim.setDuration(mDuration).setInterpolator(interpolator);
+        anim.setInterpolator(interpolator);
         add(anim);
     }
 
@@ -105,7 +105,7 @@
     public <T> void addFloat(T target, FloatProperty<T> property, float from, float to,
             TimeInterpolator interpolator) {
         Animator anim = ObjectAnimator.ofFloat(target, property, from, to);
-        anim.setDuration(mDuration).setInterpolator(interpolator);
+        anim.setInterpolator(interpolator);
         add(anim);
     }
 
@@ -116,7 +116,7 @@
             return;
         }
         Animator anim = ObjectAnimator.ofInt(target, property, value);
-        anim.setDuration(mDuration).setInterpolator(interpolator);
+        anim.setInterpolator(interpolator);
         add(anim);
     }
 
@@ -125,7 +125,7 @@
      */
     public void addOnFrameCallback(Runnable runnable) {
         if (mProgressAnimator == null) {
-            mProgressAnimator = ValueAnimator.ofFloat(0, 1).setDuration(mDuration);
+            mProgressAnimator = ValueAnimator.ofFloat(0, 1);
         }
 
         mProgressAnimator.addUpdateListener(anim -> runnable.run());
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 118ce0c..8df6f90 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -98,6 +98,10 @@
             "FOLDER_NAME_SUGGEST", true,
             "Suggests folder names instead of blank text.");
 
+    public static final BooleanFlag FOLDER_NAME_MAJORITY_RANKING = getDebugFlag(
+            "FOLDER_NAME_MAJORITY_RANKING", true,
+            "Suggests folder names based on majority based ranking.");
+
     public static final BooleanFlag APP_SEARCH_IMPROVEMENTS = new DeviceFlag(
             "APP_SEARCH_IMPROVEMENTS", true,
             "Adds localized title and keyword search and ranking");
@@ -173,6 +177,9 @@
             "SEPARATE_RECENTS_ACTIVITY", false,
             "Uses a separate recents activity instead of using the integrated recents+Launcher UI");
 
+    public static final BooleanFlag USER_EVENT_DISPATCHER = new DeviceFlag(
+            "USER_EVENT_DISPATCHER", true, "User event dispatcher collects logs.");
+
     public static void initialize(Context context) {
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index fdf0ea4..c7487cb 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -745,6 +745,11 @@
                 : getContext().getString(R.string.folder_closed));
     }
 
+    @Override
+    protected View getAccessibilityInitialFocusView() {
+        return mContent.getFirstItem();
+    }
+
     private void closeComplete(boolean wasAnimated) {
         // TODO: Clear all active animations.
         DragLayer parent = (DragLayer) getParent();
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index c4ef381..ce70a32 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -38,6 +38,9 @@
     }
 
     public enum LauncherEvent implements EventEnum {
+        /* Used to prevent double logging. */
+        IGNORE(-1),
+
         @UiEvent(doc = "App launched from workspace, hotseat or folder in launcher")
         LAUNCHER_APP_LAUNCH_TAP(338),
 
@@ -121,12 +124,48 @@
         LAUNCHER_SYSTEM_SHORTCUT_PAUSE_TAP(521),
 
         @UiEvent(doc = "User tapped on pin system shortcut.")
-        LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP(522);
+        LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP(522),
+
+        @UiEvent(doc = "User is shown All Apps education view.")
+        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);
+        // ADD MORE
+        private final int mId;
+
+        LauncherEvent(int id) {
+            mId = id;
+        }
+
+        public int getId() {
+            return mId;
+        }
+    }
+
+    /**
+     * Launcher specific ranking related events.
+     */
+    public enum LauncherRankingEvent implements EventEnum {
+
+        UNKNOWN(0);
         // ADD MORE
 
         private final int mId;
 
-        LauncherEvent(int id) {
+        LauncherRankingEvent(int id) {
             mId = id;
         }
 
@@ -170,12 +209,32 @@
     }
 
     /**
+     * Logs an event and accompanying {@link com.android.launcher3.model.data.ItemInfo}.
+     */
+    public void log(EventEnum event,
+            com.android.launcher3.model.data.ItemInfo itemInfo) {
+    }
+
+    /**
      * Logs an event and accompanying {@link InstanceId} and {@link ItemInfo}.
      */
     public void log(EventEnum event, InstanceId instanceId, @Nullable ItemInfo itemInfo) {
     }
 
     /**
+     * Log an event with ranked-choice information along with package. Does nothing if event.getId()
+     * <= 0.
+     *
+     * @param rankingEvent an enum implementing UiEventEnum 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.
+     */
+    public void log(EventEnum rankingEvent, InstanceId instanceId, @Nullable String packageName,
+            int position) {
+    }
+
+    /**
      * 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.
      */
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/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index 4f349ca..eb5d106 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -35,6 +35,7 @@
 import com.android.launcher3.AppFilter;
 import com.android.launcher3.compat.AlphabeticIndexCompat;
 import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.PromiseAppInfo;
 import com.android.launcher3.pm.PackageInstallInfo;
@@ -73,6 +74,13 @@
     private AlphabeticIndexCompat mIndex;
 
     /**
+     * @see Callbacks#FLAG_HAS_SHORTCUT_PERMISSION
+     * @see Callbacks#FLAG_QUIET_MODE_ENABLED
+     * @see Callbacks#FLAG_QUIET_MODE_CHANGE_PERMISSION
+     */
+    private int mFlags;
+
+    /**
      * Boring constructor.
      */
     public AllAppsList(IconCache iconCache, AppFilter appFilter) {
@@ -91,6 +99,33 @@
     }
 
     /**
+     * Helper to checking {@link Callbacks#FLAG_HAS_SHORTCUT_PERMISSION}
+     */
+    public boolean hasShortcutHostPermission() {
+        return (mFlags & Callbacks.FLAG_HAS_SHORTCUT_PERMISSION) != 0;
+    }
+
+    /**
+     * Sets or clears the provided flag
+     */
+    public void setFlags(int flagMask, boolean enabled) {
+        if (enabled) {
+            mFlags |= flagMask;
+        } else {
+            mFlags &= ~flagMask;
+        }
+        mDataChanged = true;
+    }
+
+    /**
+     * Returns the model flags
+     */
+    public int getFlags() {
+        return mFlags;
+    }
+
+
+    /**
      * Add the supplied ApplicationInfo objects to the list, and enqueue it into the
      * list to broadcast when notify() is called.
      *
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index ab921ea..8b0ef7b 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -96,7 +96,8 @@
     public void bindAllApps() {
         // shallow copy
         AppInfo[] apps = mBgAllAppsList.copyData();
-        executeCallbacksTask(c -> c.bindAllApplications(apps), mUiExecutor);
+        int flags = mBgAllAppsList.getFlags();
+        executeCallbacksTask(c -> c.bindAllApplications(apps, flags), mUiExecutor);
     }
 
     public abstract void bindWidgets();
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
index 7ce970d..9013cba 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -117,7 +117,8 @@
     public void bindApplicationsIfNeeded() {
         if (mAllAppsList.getAndResetChangeFlag()) {
             AppInfo[] apps = mAllAppsList.copyData();
-            scheduleCallbackTask(c -> c.bindAllApplications(apps));
+            int flags = mAllAppsList.getFlags();
+            scheduleCallbackTask(c -> c.bindAllApplications(apps, flags));
         }
     }
 }
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 2522a49..9bef847 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -98,9 +98,11 @@
     public final ArrayList<AppInfo> cachedPredictedItems = new ArrayList<>();
 
     /**
-     * True if the launcher has permission to access deep shortcuts.
+     * @see Callbacks#FLAG_HAS_SHORTCUT_PERMISSION
+     * @see Callbacks#FLAG_QUIET_MODE_ENABLED
+     * @see Callbacks#FLAG_QUIET_MODE_CHANGE_PERMISSION
      */
-    public boolean hasShortcutHostPermission;
+    public int flags;
 
     /**
      * Maps all launcher activities to counts of their shortcuts.
@@ -347,6 +349,13 @@
     }
 
     public interface Callbacks {
+        // If the launcher has permission to access deep shortcuts.
+        int FLAG_HAS_SHORTCUT_PERMISSION = 1 << 0;
+        // If quiet mode is enabled for any user
+        int FLAG_QUIET_MODE_ENABLED = 1 << 1;
+        // If launcher can change quiet mode
+        int FLAG_QUIET_MODE_CHANGE_PERMISSION = 1 << 2;
+
         /**
          * Returns the page number to bind first, synchronously if possible or -1
          */
@@ -370,7 +379,7 @@
         void executeOnNextDraw(ViewOnDrawExecutor executor);
         void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap);
 
-        void bindAllApplications(AppInfo[] apps);
+        void bindAllApplications(AppInfo[] apps, int flags);
 
         /**
          * Binds predicted appInfos at at available prediction slots.
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
index 4bfabb0..25a2c69 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -32,7 +32,6 @@
 import android.database.DatabaseUtils;
 import android.database.sqlite.SQLiteDatabase;
 import android.graphics.Point;
-import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
 
@@ -53,13 +52,13 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 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,75 +247,78 @@
 
     /** 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;
     }
 
-    private static void insertEntryInDb(SQLiteDatabase db, Context context,
-            ArrayList<DbEntry> entriesFromSrcDb, DbEntry entry, String srcTableName,
-            String destTableName) {
-        int id = -1;
-        switch (entry.itemType) {
-            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
-            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
-            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: {
-                for (DbEntry e : entriesFromSrcDb) {
-                    if (TextUtils.equals(e.mIntent, entry.mIntent)) {
-                        id = e.id;
-                    }
-                }
+    private static void insertEntryInDb(SQLiteDatabase db, Context context, DbEntry entry,
+            String srcTableName, String destTableName) {
+        int id = copyEntryAndUpdate(db, context, entry, srcTableName, destTableName);
 
-                break;
+        if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
+            for (int itemId : entry.mFolderItems.values()) {
+                copyEntryAndUpdate(db, context, itemId, id, srcTableName, destTableName);
             }
-            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: {
-                for (DbEntry e : entriesFromSrcDb) {
-                    if (e.mFolderItems.size() == entry.mFolderItems.size()
-                            && e.mFolderItems.containsAll(entry.mFolderItems)) {
-                        id = e.id;
-                    }
-                }
-                break;
-            }
-            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
-            case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: {
-                for (DbEntry e : entriesFromSrcDb) {
-                    if (TextUtils.equals(e.mProvider, entry.mProvider)) {
-                        id = e.id;
-                        break;
-                    }
-                }
-                break;
-            }
-            default:
-                return;
         }
+    }
 
-        Cursor c = db.query(srcTableName, null, LauncherSettings.Favorites._ID + " = '" + id + "'",
+    private static int copyEntryAndUpdate(SQLiteDatabase db, Context context,
+            DbEntry entry, String srcTableName, String destTableName) {
+        return copyEntryAndUpdate(db, context, entry, -1, -1, srcTableName, destTableName);
+    }
+
+    private static int copyEntryAndUpdate(SQLiteDatabase db, Context context,
+            int id, int folderId, String srcTableName, String destTableName) {
+        return copyEntryAndUpdate(db, context, null, id, folderId, srcTableName, destTableName);
+    }
+
+    private static int copyEntryAndUpdate(SQLiteDatabase db, Context context,
+            DbEntry entry, int id, int folderId, String srcTableName, String destTableName) {
+        int newId = -1;
+        Cursor c = db.query(srcTableName, null,
+                LauncherSettings.Favorites._ID + " = '" + (entry != null ? entry.id : id) + "'",
                 null, null, null, null);
-
         while (c.moveToNext()) {
             ContentValues values = new ContentValues();
             DatabaseUtils.cursorRowToContentValues(c, values);
-            entry.updateContentValues(values);
-            values.put(LauncherSettings.Favorites._ID,
-                    LauncherSettings.Settings.call(context.getContentResolver(),
-                            LauncherSettings.Settings.METHOD_NEW_ITEM_ID).getInt(
-                            LauncherSettings.Settings.EXTRA_VALUE));
+            if (entry != null) {
+                entry.updateContentValues(values);
+            } else {
+                values.put(LauncherSettings.Favorites.CONTAINER, folderId);
+            }
+            newId = LauncherSettings.Settings.call(context.getContentResolver(),
+                    LauncherSettings.Settings.METHOD_NEW_ITEM_ID).getInt(
+                    LauncherSettings.Settings.EXTRA_VALUE);
+            values.put(LauncherSettings.Favorites._ID, newId);
             db.insert(destTableName, null, values);
         }
         c.close();
+        return newId;
     }
 
-    private static void removeEntryFromDb(SQLiteDatabase db, String tableName, IntArray entryId) {
+    private static void removeEntryFromDb(SQLiteDatabase db, String tableName, IntArray entryIds) {
         db.delete(tableName,
-                Utilities.createDbSelectionQuery(LauncherSettings.Favorites._ID, entryId), null);
+                Utilities.createDbSelectionQuery(LauncherSettings.Favorites._ID, entryIds), null);
     }
 
     private static HashSet<String> getValidPackages(Context context) {
@@ -381,8 +383,8 @@
                     continue;
                 }
                 if (findPlacement(entry)) {
-                    insertEntryInDb(mDb, mContext, mSrcReader.mWorkspaceEntries, entry,
-                            mSrcReader.mTableName, mDestReader.mTableName);
+                    insertEntryInDb(mDb, mContext, entry, mSrcReader.mTableName,
+                            mDestReader.mTableName);
                     iterator.remove();
                 }
             }
@@ -394,7 +396,7 @@
          * to speed up the search.
          */
         private boolean findPlacement(DbEntry entry) {
-            for (int y = mNextStartY; y >= 0; y--) {
+            for (int y = mNextStartY; y > 0; y--) {
                 for (int x = mNextStartX; x < mTrgX; x++) {
                     boolean fits = mOccupied.isRegionVacant(x, y, entry.spanX, entry.spanY);
                     boolean minFits = mOccupied.isRegionVacant(x, y, entry.minSpanX,
@@ -451,8 +453,8 @@
                     // to something other than -1.
                     entry.cellX = i;
                     entry.cellY = 0;
-                    insertEntryInDb(mDb, mContext, mSrcReader.mHotseatEntries, entry,
-                            mSrcReader.mTableName, mDestReader.mTableName);
+                    insertEntryInDb(mDb, mContext, entry, mSrcReader.mTableName,
+                            mDestReader.mTableName);
                     mOccupied.markCells(entry, true);
                 }
             }
@@ -669,10 +671,11 @@
             int total = 0;
             while (c.moveToNext()) {
                 try {
+                    int id = c.getInt(0);
                     String intent = c.getString(1);
                     verifyIntent(intent);
                     total++;
-                    entry.mFolderItems.add(intent);
+                    entry.mFolderItems.put(intent, id);
                 } catch (Exception e) {
                     removeEntryFromDb(mDb, mTableName, IntArray.wrap(c.getInt(0)));
                 }
@@ -711,7 +714,7 @@
 
         private String mIntent;
         private String mProvider;
-        private Set<String> mFolderItems = new HashSet<>();
+        private Map<String, Integer> mFolderItems = new HashMap<>();
 
         /** Comparator according to the reading order */
         @Override
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 244de96..165d1ea 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -65,7 +65,7 @@
 
     private static final String TAG = "LoaderCursor";
 
-    public final LongSparseArray<UserHandle> allUsers = new LongSparseArray<>();
+    public final LongSparseArray<UserHandle> allUsers;
 
     private final Uri mContentUri;
     private final Context mContext;
@@ -100,9 +100,11 @@
     public int itemType;
     public int restoreFlag;
 
-    public LoaderCursor(Cursor cursor, Uri contentUri, LauncherAppState app) {
+    public LoaderCursor(Cursor cursor, Uri contentUri, LauncherAppState app,
+            UserManagerState userManagerState) {
         super(cursor);
 
+        allUsers = userManagerState.allUsers;
         mContentUri = contentUri;
         mContext = app.getContext();
         mIconCache = app.getIconCache();
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index d05d70b..f2073ef 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -17,6 +17,9 @@
 package com.android.launcher3.model;
 
 import static com.android.launcher3.config.FeatureFlags.MULTI_DB_GRID_MIRATION_ALGO;
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
 import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
@@ -35,6 +38,7 @@
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.ShortcutInfo;
 import android.net.Uri;
 import android.os.UserHandle;
@@ -120,6 +124,8 @@
     private final InstallSessionHelper mSessionHelper;
     private final IconCache mIconCache;
 
+    private final UserManagerState mUserManagerState = new UserManagerState();
+
     private boolean mStopped;
 
     public LoaderTask(LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel dataModel,
@@ -333,7 +339,8 @@
 
             Map<ShortcutKey, ShortcutInfo> shortcutKeyToPinnedShortcuts = new HashMap<>();
             final LoaderCursor c = new LoaderCursor(
-                    contentResolver.query(contentUri, null, null, null, null), contentUri, mApp);
+                    contentResolver.query(contentUri, null, null, null, null), contentUri, mApp,
+                    mUserManagerState);
 
             Map<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null;
 
@@ -352,12 +359,13 @@
                         LauncherSettings.Favorites.OPTIONS);
 
                 final LongSparseArray<UserHandle> allUsers = c.allUsers;
-                final LongSparseArray<Boolean> quietMode = new LongSparseArray<>();
                 final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>();
+
+                mUserManagerState.init(mUserCache, mUserManager);
+
                 for (UserHandle user : mUserCache.getUserProfiles()) {
                     long serialNo = mUserCache.getSerialNumberForUser(user);
                     allUsers.put(serialNo, user);
-                    quietMode.put(serialNo, mUserManager.isQuietModeEnabled(user));
 
                     boolean userUnlocked = mUserManager.isUserUnlocked(user);
 
@@ -404,8 +412,8 @@
                                 continue;
                             }
 
-                            int disabledState = quietMode.get(c.serialNumber) ?
-                                    WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER : 0;
+                            int disabledState = mUserManagerState.isUserQuiet(c.serialNumber)
+                                    ? WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER : 0;
                             ComponentName cn = intent.getComponent();
                             targetPkg = cn == null ? intent.getPackage() : cn.getPackageName();
 
@@ -862,8 +870,8 @@
             for (ComponentKey key : componentKeys) {
                 l = mLauncherApps.getActivityList(key.componentName.getPackageName(), key.user);
                 if (l.size() == 0) continue;
-                boolean quietMode = mUserManager.isQuietModeEnabled(key.user);
-                AppInfo info = new AppInfo(l.get(0), key.user, quietMode);
+                AppInfo info = new AppInfo(l.get(0), key.user,
+                        mUserManagerState.isUserQuiet(key.user));
                 mBgDataModel.cachedPredictedItems.add(info);
                 mIconCache.getTitleAndIcon(info, false);
             }
@@ -883,7 +891,7 @@
             if (apps == null || apps.isEmpty()) {
                 return allActivityList;
             }
-            boolean quietMode = mUserManager.isQuietModeEnabled(user);
+            boolean quietMode = mUserManagerState.isUserQuiet(user);
             // Create the ApplicationInfos
             for (int i = 0; i < apps.size(); i++) {
                 LauncherActivityInfo app = apps.get(i);
@@ -905,11 +913,19 @@
             List<LauncherActivityInfo> l = mLauncherApps.getActivityList(
                     item.componentName.getPackageName(), item.user);
             for (LauncherActivityInfo info : l) {
-                boolean quietMode = mUserManager.isQuietModeEnabled(item.user);
+                boolean quietMode = mUserManagerState.isUserQuiet(item.user);
                 mBgAllAppsList.add(new AppInfo(info, item.user, quietMode), info);
             }
         }
 
+        mBgAllAppsList.setFlags(FLAG_QUIET_MODE_ENABLED,
+                mUserManagerState.isAnyProfileQuietModeEnabled());
+        mBgAllAppsList.setFlags(FLAG_HAS_SHORTCUT_PERMISSION,
+                hasShortcutsPermission(mApp.getContext()));
+        mBgAllAppsList.setFlags(FLAG_QUIET_MODE_CHANGE_PERMISSION,
+                mApp.getContext().checkSelfPermission("android.permission.MODIFY_QUIET_MODE")
+                        == PackageManager.PERMISSION_GRANTED);
+
         mBgAllAppsList.getAndResetChangeFlag();
         return allActivityList;
     }
@@ -917,8 +933,8 @@
     private List<ShortcutInfo> loadDeepShortcuts() {
         List<ShortcutInfo> allShortcuts = new ArrayList<>();
         mBgDataModel.deepShortcutMap.clear();
-        mBgDataModel.hasShortcutHostPermission = hasShortcutsPermission(mApp.getContext());
-        if (mBgDataModel.hasShortcutHostPermission) {
+
+        if (mBgAllAppsList.hasShortcutHostPermission()) {
             for (UserHandle user : mUserCache.getUserProfiles()) {
                 if (mUserManager.isUserUnlocked(user)) {
                     List<ShortcutInfo> shortcuts = new ShortcutRequest(mApp.getContext(), user)
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 2fa6051..7cd467e 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.model;
 
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
 import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
 import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_RESTORED_ICON;
 
@@ -41,6 +42,7 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.IntSparseArrayMap;
@@ -149,14 +151,21 @@
                 if (DEBUG) Log.d(TAG, "mAllAppsList.(un)suspend " + N);
                 appsList.updateDisabledFlags(matcher, flagOp);
                 break;
-            case OP_USER_AVAILABILITY_CHANGE:
-                flagOp = context.getSystemService(UserManager.class).isQuietModeEnabled(mUser)
+            case OP_USER_AVAILABILITY_CHANGE: {
+                UserManagerState ums = new UserManagerState();
+                ums.init(UserCache.INSTANCE.get(context),
+                        context.getSystemService(UserManager.class));
+                flagOp = ums.isUserQuiet(mUser)
                         ? FlagOp.addFlag(WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER)
                         : FlagOp.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER);
                 // We want to update all packages for this user.
                 matcher = ItemInfoMatcher.ofUser(mUser);
                 appsList.updateDisabledFlags(matcher, flagOp);
+
+                // We are not synchronizing here, as int operations are atomic
+                appsList.setFlags(FLAG_QUIET_MODE_ENABLED, ums.isAnyProfileQuietModeEnabled());
                 break;
+            }
         }
 
         bindApplicationsIfNeeded();
diff --git a/src/com/android/launcher3/model/UserManagerState.java b/src/com/android/launcher3/model/UserManagerState.java
new file mode 100644
index 0000000..3a4206c
--- /dev/null
+++ b/src/com/android/launcher3/model/UserManagerState.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.model;
+
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.LongSparseArray;
+import android.util.SparseBooleanArray;
+
+import com.android.launcher3.pm.UserCache;
+
+/**
+ * Utility class to manager store and user manager state at any particular time
+ */
+public class UserManagerState {
+
+    public final LongSparseArray<UserHandle> allUsers = new LongSparseArray<>();
+
+    private final LongSparseArray<Boolean> mQuietUsersSerialNoMap = new LongSparseArray<>();
+    private final SparseBooleanArray mQuietUsersHashCodeMap = new SparseBooleanArray();
+
+    /**
+     * Initialises the state values for all users
+     */
+    public void init(UserCache userCache, UserManager userManager) {
+        for (UserHandle user : userCache.getUserProfiles()) {
+            long serialNo = userCache.getSerialNumberForUser(user);
+            boolean isUserQuiet = userManager.isQuietModeEnabled(user);
+            allUsers.put(serialNo, user);
+            mQuietUsersHashCodeMap.put(user.hashCode(), isUserQuiet);
+            mQuietUsersSerialNoMap.put(serialNo, isUserQuiet);
+        }
+    }
+
+    /**
+     * Returns true if quiet mode is enabled for the provided user
+     */
+    public boolean isUserQuiet(long serialNo) {
+        return mQuietUsersSerialNoMap.get(serialNo);
+    }
+
+    /**
+     * Returns true if quiet mode is enabled for the provided user
+     */
+    public boolean isUserQuiet(UserHandle user) {
+        return mQuietUsersHashCodeMap.get(user.hashCode());
+    }
+
+    /**
+     * Returns true if any user profile has quiet mode enabled.
+     */
+    public boolean isAnyProfileQuietModeEnabled() {
+        for (int i = mQuietUsersHashCodeMap.size() - 1; i >= 0; i--) {
+            if (mQuietUsersHashCodeMap.valueAt(i)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java
index 096743a..8f577b5 100644
--- a/src/com/android/launcher3/model/data/FolderInfo.java
+++ b/src/com/android/launcher3/model/data/FolderInfo.java
@@ -22,6 +22,8 @@
 
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.logger.LauncherAtom.Attribute.MANUAL_LABEL;
+import static com.android.launcher3.logger.LauncherAtom.Attribute.SUGGESTED_LABEL;
 import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_CUSTOM;
 import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_EMPTY;
 import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_FOLDER_LABEL_STATE_UNSPECIFIED;
@@ -205,6 +207,7 @@
         return getDefaultItemInfoBuilder()
                 .setFolderIcon(LauncherAtom.FolderIcon.newBuilder().setCardinality(contents.size()))
                 .setRank(rank)
+                .setAttribute(fromCustom ? MANUAL_LABEL : SUGGESTED_LABEL)
                 .setContainerInfo(getContainerInfo())
                 .build();
     }
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 0c815d1..d52b7eb 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SEARCH_RESULTS;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SETTINGS;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
@@ -46,6 +47,7 @@
 import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
 import com.android.launcher3.logger.LauncherAtom.PredictionContainer;
 import com.android.launcher3.logger.LauncherAtom.SearchResultContainer;
+import com.android.launcher3.logger.LauncherAtom.SettingsContainer;
 import com.android.launcher3.logger.LauncherAtom.ShortcutsContainer;
 import com.android.launcher3.util.ContentWriter;
 
@@ -335,10 +337,13 @@
     ContainerInfo getContainerInfo() {
         switch (container) {
             case CONTAINER_HOTSEAT:
-            case CONTAINER_HOTSEAT_PREDICTION:
                 return ContainerInfo.newBuilder()
                         .setHotseat(LauncherAtom.HotseatContainer.newBuilder().setIndex(screenId))
                         .build();
+            case CONTAINER_HOTSEAT_PREDICTION:
+                return ContainerInfo.newBuilder().setPredictedHotseatContainer(
+                        LauncherAtom.PredictedHotseatContainer.newBuilder().setIndex(screenId))
+                        .build();
             case CONTAINER_DESKTOP:
                 return ContainerInfo.newBuilder()
                         .setWorkspace(
@@ -369,6 +374,10 @@
                 return ContainerInfo.newBuilder()
                         .setShortcutsContainer(ShortcutsContainer.getDefaultInstance())
                         .build();
+            case CONTAINER_SETTINGS:
+                return ContainerInfo.newBuilder()
+                        .setSettingsContainer(SettingsContainer.getDefaultInstance())
+                        .build();
         }
         return ContainerInfo.getDefaultInstance();
     }
diff --git a/src/com/android/launcher3/pm/UserCache.java b/src/com/android/launcher3/pm/UserCache.java
index f723256..5aab41a 100644
--- a/src/com/android/launcher3/pm/UserCache.java
+++ b/src/com/android/launcher3/pm/UserCache.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.content.Intent;
-import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.ArrayMap;
@@ -107,22 +106,6 @@
     }
 
     /**
-     * Returns true if any user profile has quiet mode enabled.
-     */
-    public boolean isAnyProfileQuietModeEnabled() {
-        List<UserHandle> userProfiles = getUserProfiles();
-        for (UserHandle userProfile : userProfiles) {
-            if (Process.myUserHandle().equals(userProfile)) {
-                continue;
-            }
-            if (mUserManager.isQuietModeEnabled(userProfile)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
      * @see UserManager#getSerialNumberForUser(UserHandle)
      */
     public long getSerialNumberForUser(UserHandle user) {
@@ -160,16 +143,4 @@
         List<UserHandle> users = mUserManager.getUserProfiles();
         return users == null ? Collections.emptyList() : users;
     }
-
-    /**
-     * Returns true is there is at least one user profile enabled
-     */
-    public boolean hasWorkProfile() {
-        synchronized (this) {
-            if (mUsers != null) {
-                return mUsers.size() > 1;
-            }
-        }
-        return getUserProfiles().size() > 1;
-    }
 }
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 5b0c388..d5b32fc 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -389,6 +389,11 @@
         return Pair.create(this, "");
     }
 
+    @Override
+    protected View getAccessibilityInitialFocusView() {
+        return getChildCount() > 0 ? getChildAt(0) : this;
+    }
+
     private void animateOpen() {
         setVisibility(View.VISIBLE);
 
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index 4a15af1..21ad275 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -296,8 +296,8 @@
     }
 
     @Override
-    public void bindAllApplications(AppInfo[] apps) {
-        mAppsView.getAppsStore().setApps(apps);
+    public void bindAllApplications(AppInfo[] apps, int flags) {
+        mAppsView.getAppsStore().setApps(apps, flags);
     }
 
     public PopupDataProvider getPopupDataProvider() {
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 2c21609..171c5ee 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -310,6 +310,9 @@
     }
 
     protected void updateProgress(float fraction) {
+        if (mCurrentAnimation == null) {
+            return;
+        }
         mCurrentAnimation.setPlayFraction(fraction);
         if (mAtomicComponentsController != null) {
             // Make sure we don't divide by 0, and have at least a small runway.
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..de16941 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,7 @@
         if (!folder.isOpen() && !folder.isDestroyed()) {
             // Open the requested folder
             folder.animateOpen();
+            StatsLogManager.newInstance(v.getContext()).log(LAUNCHER_FOLDER_OPEN, folder.mInfo);
         }
     }
 
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/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/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index ddde6d3..86f3431 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3.util;
 
-import static android.content.pm.PackageInstaller.SessionInfo;
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 
 import android.app.AppOpsManager;
@@ -45,8 +44,6 @@
 import android.util.Pair;
 import android.widget.Toast;
 
-import androidx.annotation.NonNull;
-
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -348,15 +345,4 @@
         }
         return false;
     }
-
-    /**
-     * Returns the created time in millis of given session info. Returns 0 if not available.
-     */
-    public static long getSessionCreatedTimeInMillis(@NonNull final SessionInfo info) {
-        try {
-            return (long) SessionInfo.class.getDeclaredMethod("getCreatedMillis").invoke(info);
-        } catch (Exception e) {
-            return 0;
-        }
-    }
 }
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/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 7467186..5431ba1 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -17,8 +17,8 @@
 
 import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_FLAVOR;
 import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_OFFSET;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WALLPAPER_BUTTON_TAP_OR_LONGPRESS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS;
 
 import android.content.Context;
@@ -38,10 +38,12 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.logging.StatsLogManager.EventEnum;
 import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.ArrowPopup;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.testing.TestLogging;
@@ -157,7 +159,7 @@
         int resDrawable = Utilities.existsStyleWallpapers(launcher) ?
                 R.drawable.ic_palette : R.drawable.ic_wallpaper;
         options.add(new OptionItem(resString, resDrawable,
-                LAUNCHER_WALLPAPER_BUTTON_TAP_OR_LONGPRESS,
+                IGNORE,
                 OptionsPopupView::startWallpaperPicker));
         if (!WidgetsModel.GO_DISABLE_WIDGETS) {
             options.add(new OptionItem(R.string.widget_button_text, R.drawable.ic_widget,
@@ -218,7 +220,15 @@
         if (!TextUtils.isEmpty(pickerPackage)) {
             intent.setPackage(pickerPackage);
         }
-        return launcher.startActivitySafely(v, intent, null, null);
+        return launcher.startActivitySafely(v, intent, dummyInfo(intent), null);
+    }
+
+    static WorkspaceItemInfo dummyInfo(Intent intent) {
+        WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo();
+        dummyInfo.intent = intent;
+        dummyInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+        dummyInfo.container = LauncherSettings.Favorites.CONTAINER_SETTINGS;
+        return dummyInfo;
     }
 
     public static class OptionItem {