Merge "Change work_app_edu to be like T spec: https://screenshot.googleplex.com/43ThiCnqrAqAQgE" into tm-dev
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index a61e430..4127650 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -121,6 +121,7 @@
   optional int32 cardinality = 2;
 }
 
+// Next value 39
 enum Attribute {
   UNKNOWN = 0;
   DEFAULT_LAYOUT = 1;       // icon automatically placed in workspace, folder, hotseat
@@ -166,11 +167,13 @@
   ALL_APPS_SEARCH_RESULT_ASSISTANT_MEMORY = 31;
 
   // Suggestion Type provided by AGA
-  ONE_SEARCH_WEB_QUERY = 32;
-  ONE_SEARCH_WEB_TRENDING = 33;
-  ONE_SEARCH_WEB_ENTITY = 34;
-  ONE_SEARCH_WEB_ANSWER = 35;
-  ONE_SEARCH_WEB_PERSONAL = 36;
+  WEB_SEARCH_RESULT_QUERY = 32;
+  WEB_SEARCH_RESULT_TRENDING = 33;
+  WEB_SEARCH_RESULT_ENTITY = 34;
+  WEB_SEARCH_RESULT_ANSWER = 35;
+  WEB_SEARCH_RESULT_PERSONAL = 36;
+  WEB_SEARCH_RESULT_CALCULATOR = 37;
+  WEB_SEARCH_RESULT_URL = 38;
 
   WIDGETS_BOTTOM_TRAY = 28;
   WIDGETS_TRAY_PREDICTION = 29;
diff --git a/quickstep/res/layout/activity_allset.xml b/quickstep/res/layout/activity_allset.xml
index 06dfa37..0cae733 100644
--- a/quickstep/res/layout/activity_allset.xml
+++ b/quickstep/res/layout/activity_allset.xml
@@ -72,12 +72,11 @@
             <TextView
                 android:id="@+id/subtitle"
                 style="@style/TextAppearance.GestureTutorial.Feedback.Subtitle.AllSet"
-                android:layout_width="0dp"
+                android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_marginTop="@dimen/allset_subtitle_margin_top"
                 app:layout_constraintTop_toBottomOf="@id/title"
                 app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintWidth_max="@dimen/allset_subtitle_width_max"
                 android:gravity="start"/>
 
             <androidx.constraintlayout.widget.Guideline
diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
index 8f439a2..185c815 100644
--- a/quickstep/res/values/colors.xml
+++ b/quickstep/res/values/colors.xml
@@ -24,7 +24,6 @@
     <color name="all_apps_prediction_row_separator_dark">#3cffffff</color>
 
     <!-- Taskbar -->
-    <color name="taskbar_background">@color/overview_scrim_dark</color>
     <color name="taskbar_nav_icon_selection_ripple">#E0E0E0</color>
     <color name="taskbar_nav_icon_light_color">#ffffff</color>
     <!-- The dark navigation button color is only used in the rare cases that taskbar isn't drawing
diff --git a/quickstep/src/com/android/launcher3/model/AppEventProducer.java b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
index 3f29e43..5c66944 100644
--- a/quickstep/src/com/android/launcher3/model/AppEventProducer.java
+++ b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
@@ -18,6 +18,7 @@
 import static android.app.prediction.AppTargetEvent.ACTION_DISMISS;
 import static android.app.prediction.AppTargetEvent.ACTION_LAUNCH;
 import static android.app.prediction.AppTargetEvent.ACTION_PIN;
+import static android.app.prediction.AppTargetEvent.ACTION_UNDISMISS;
 import static android.app.prediction.AppTargetEvent.ACTION_UNPIN;
 
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
@@ -25,6 +26,7 @@
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
 import static com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers.ContainerCase.DEVICE_SEARCH_RESULT_CONTAINER;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DISMISS_PREDICTION_UNDO;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_CONVERTED_TO_ICON;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_PREDICTION_PINNED;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED;
@@ -176,6 +178,8 @@
                     mContext.getPackageName(), Process.myUserHandle())
                     .build();
             sendEvent(target, atomInfo, ACTION_LAUNCH, CONTAINER_PREDICTION);
+        } else if (event == LAUNCHER_DISMISS_PREDICTION_UNDO) {
+            sendEvent(atomInfo, ACTION_UNDISMISS, CONTAINER_HOTSEAT_PREDICTION);
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/search/SearchSessionManager.java b/quickstep/src/com/android/launcher3/search/SearchSessionManager.java
new file mode 100644
index 0000000..da85793
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/search/SearchSessionManager.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 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.search;
+
+import android.app.search.SearchSession;
+import android.content.Context;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.R;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.util.ResourceBasedOverride;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/** Manages an all apps search session. */
+public class SearchSessionManager implements ResourceBasedOverride {
+
+    /** Entry state for the search session (e.g. from all apps). */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {ZERO_ALLAPPS, ZERO_QSB})
+    public @interface ZeroEntryState {}
+    public static final int ZERO_ALLAPPS = 1;
+    public static final int ZERO_QSB = 2;
+
+    /** Creates a {@link SearchSessionManager} instance. */
+    public static SearchSessionManager newInstance(Context context) {
+        return Overrides.getObject(
+                SearchSessionManager.class, context, R.string.search_session_manager_class);
+    }
+
+    /** The current {@link SearchSession}. */
+    @UiThread
+    public void setSearchSession(SearchSession session) {}
+
+    /** {@code true} if IME is shown. */
+    public void setIsImeShown(boolean value) {}
+
+    /** Returns {@code true} if IME is enabled. */
+    public boolean getIsImeEnabled() {
+        return false;
+    }
+
+    /** The current entry state for search. */
+    public @ZeroEntryState int getEntryState() {
+        return ZERO_ALLAPPS;
+    }
+
+    /**
+     * When user enters all apps surface via tap on home widget, set the state to
+     * {@code #ZERO_QSB}. When user exits, reset to {@code #ZERO_ALLAPPS}
+     */
+    public void setEntryState(@ZeroEntryState int state) {}
+
+    /** This will be called before opening all apps, to prepare zero state suggestions. */
+    public void prepareZeroState() {}
+
+    /** Apply predicted items for the search zero state. */
+    public void setZeroStatePredictedItems(List<ItemInfo> items) {}
+
+    /** Returns {@code true} if the session is valid and should be enabled. */
+    public boolean isValidSession() {
+        return false;
+    }
+
+    /** Called when the search session is destroyed. */
+    public void onDestroy() {}
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index ca0767b..175a1d9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
 import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_KEYGUARD;
+import static com.android.launcher3.taskbar.Utilities.appendFlag;
 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_BACK_DISABLED;
@@ -693,20 +694,20 @@
 
     private static String getStateString(int flags) {
         StringJoiner str = new StringJoiner("|");
-        str.add((flags & FLAG_SWITCHER_SUPPORTED) != 0 ? "FLAG_SWITCHER_SUPPORTED" : "");
-        str.add((flags & FLAG_IME_VISIBLE) != 0 ? "FLAG_IME_VISIBLE" : "");
-        str.add((flags & FLAG_ROTATION_BUTTON_VISIBLE) != 0 ? "FLAG_ROTATION_BUTTON_VISIBLE" : "");
-        str.add((flags & FLAG_A11Y_VISIBLE) != 0 ? "FLAG_A11Y_VISIBLE" : "");
-        str.add((flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0
-                ? "FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE" : "");
-        str.add((flags & FLAG_KEYGUARD_VISIBLE) != 0 ? "FLAG_KEYGUARD_VISIBLE" : "");
-        str.add((flags & FLAG_KEYGUARD_OCCLUDED) != 0 ? "FLAG_KEYGUARD_OCCLUDED" : "");
-        str.add((flags & FLAG_DISABLE_HOME) != 0 ? "FLAG_DISABLE_HOME" : "");
-        str.add((flags & FLAG_DISABLE_RECENTS) != 0 ? "FLAG_DISABLE_RECENTS" : "");
-        str.add((flags & FLAG_DISABLE_BACK) != 0 ? "FLAG_DISABLE_BACK" : "");
-        str.add((flags & FLAG_NOTIFICATION_SHADE_EXPANDED) != 0
-                ? "FLAG_NOTIFICATION_SHADE_EXPANDED" : "");
-        str.add((flags & FLAG_SCREEN_PINNING_ACTIVE) != 0 ? "FLAG_SCREEN_PINNING_ACTIVE" : "");
+        appendFlag(str, flags, FLAG_SWITCHER_SUPPORTED, "FLAG_SWITCHER_SUPPORTED");
+        appendFlag(str, flags, FLAG_IME_VISIBLE, "FLAG_IME_VISIBLE");
+        appendFlag(str, flags, FLAG_ROTATION_BUTTON_VISIBLE, "FLAG_ROTATION_BUTTON_VISIBLE");
+        appendFlag(str, flags, FLAG_A11Y_VISIBLE, "FLAG_A11Y_VISIBLE");
+        appendFlag(str, flags, FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE,
+                "FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE");
+        appendFlag(str, flags, FLAG_KEYGUARD_VISIBLE, "FLAG_KEYGUARD_VISIBLE");
+        appendFlag(str, flags, FLAG_KEYGUARD_OCCLUDED, "FLAG_KEYGUARD_OCCLUDED");
+        appendFlag(str, flags, FLAG_DISABLE_HOME, "FLAG_DISABLE_HOME");
+        appendFlag(str, flags, FLAG_DISABLE_RECENTS, "FLAG_DISABLE_RECENTS");
+        appendFlag(str, flags, FLAG_DISABLE_BACK, "FLAG_DISABLE_BACK");
+        appendFlag(str, flags, FLAG_NOTIFICATION_SHADE_EXPANDED,
+                "FLAG_NOTIFICATION_SHADE_EXPANDED");
+        appendFlag(str, flags, FLAG_SCREEN_PINNING_ACTIVE, "FLAG_SCREEN_PINNING_ACTIVE");
         return str.toString();
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 5479664..1c6d4a2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -56,6 +56,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BubbleTextView;
@@ -66,6 +67,7 @@
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.logger.LauncherAtom;
+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;
@@ -244,6 +246,13 @@
         mDeviceProfile.updateAllAppsIconSize(1, resources); // Leave all apps unscaled.
     }
 
+    @VisibleForTesting
+    @Override
+    public StatsLogManager getStatsLogManager() {
+        // Used to mock, can't mock a default interface method directly
+        return super.getStatsLogManager();
+    }
+
     /** Creates LayoutParams for adding a view directly to WindowManager as a new window */
     public WindowManager.LayoutParams createDefaultWindowLayoutParams() {
         WindowManager.LayoutParams windowLayoutParams = new WindowManager.LayoutParams(
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java
index 31a6aa6..c5615c7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.taskbar;
 
+import static com.android.launcher3.taskbar.Utilities.appendFlag;
+
 import androidx.annotation.IntDef;
 
 import com.android.quickstep.SystemUiProxy;
@@ -75,10 +77,9 @@
 
     private static String getStateString(int flags) {
         StringJoiner str = new StringJoiner("|");
-        str.add((flags & FLAG_AUTOHIDE_SUSPEND_FULLSCREEN) != 0
-                ? "FLAG_AUTOHIDE_SUSPEND_FULLSCREEN" : "");
-        str.add((flags & FLAG_AUTOHIDE_SUSPEND_DRAGGING) != 0
-                ? "FLAG_AUTOHIDE_SUSPEND_DRAGGING" : "");
+        appendFlag(str, flags, FLAG_AUTOHIDE_SUSPEND_FULLSCREEN,
+                "FLAG_AUTOHIDE_SUSPEND_FULLSCREEN");
+        appendFlag(str, flags, FLAG_AUTOHIDE_SUSPEND_DRAGGING, "FLAG_AUTOHIDE_SUSPEND_DRAGGING");
         return str.toString();
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index a5999cc..a3586396 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -19,6 +19,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.taskbar.allapps.TaskbarAllAppsController;
 import com.android.systemui.shared.rotation.RotationButtonController;
@@ -117,6 +118,7 @@
         taskbarPopupController.init(this);
         taskbarForceVisibleImmersiveController.init(this);
         taskbarAllAppsController.init(this, sharedState);
+        navButtonController.init(this);
 
         mControllersToLog = new LoggableTaskbarController[] {
                 taskbarDragController, navButtonController, navbarButtonsViewController,
@@ -153,6 +155,7 @@
         taskbarPopupController.onDestroy();
         taskbarForceVisibleImmersiveController.onDestroy();
         taskbarAllAppsController.onDestroy();
+        navButtonController.onDestroy();
 
         mControllersToLog = null;
     }
@@ -185,6 +188,12 @@
         rotationButtonController.dumpLogs(prefix + "\t", pw);
     }
 
+    @VisibleForTesting
+    TaskbarActivityContext getTaskbarActivityContext() {
+        // Used to mock
+        return taskbarActivityContext;
+    }
+
     protected interface LoggableTaskbarController {
         void dumpLogs(String prefix, PrintWriter pw);
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index 958598d..4ff0649 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -19,15 +19,27 @@
 import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS;
 import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_KEY;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_A11Y_BUTTON_LONGPRESS;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_A11Y_BUTTON_TAP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_BACK_BUTTON_LONGPRESS;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_BACK_BUTTON_TAP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_HOME_BUTTON_TAP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
 
 import android.os.Bundle;
 import android.os.Handler;
+import android.util.Log;
 
 import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
 
 import com.android.launcher3.R;
+import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.quickstep.OverviewCommandHelper;
@@ -49,6 +61,7 @@
     /** Allow some time in between the long press for back and recents. */
     static final int SCREEN_PIN_LONG_PRESS_THRESHOLD = 200;
     static final int SCREEN_PIN_LONG_PRESS_RESET = SCREEN_PIN_LONG_PRESS_THRESHOLD + 100;
+    private static final String TAG = TaskbarNavButtonController.class.getSimpleName();
 
     private long mLastScreenPinLongPress;
     private boolean mScreenPinned;
@@ -89,6 +102,7 @@
     private final TouchInteractionService mService;
     private final SystemUiProxy mSystemUiProxy;
     private final Handler mHandler;
+    @Nullable private StatsLogManager mStatsLogManager;
 
     private final Runnable mResetLongPress = this::resetScreenUnpin;
 
@@ -102,18 +116,23 @@
     public void onButtonClick(@TaskbarButton int buttonType) {
         switch (buttonType) {
             case BUTTON_BACK:
+                logEvent(LAUNCHER_TASKBAR_BACK_BUTTON_TAP);
                 executeBack();
                 break;
             case BUTTON_HOME:
+                logEvent(LAUNCHER_TASKBAR_HOME_BUTTON_TAP);
                 navigateHome();
                 break;
             case BUTTON_RECENTS:
+                logEvent(LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP);
                 navigateToOverview();
                 break;
             case BUTTON_IME_SWITCH:
+                logEvent(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP);
                 showIMESwitcher();
                 break;
             case BUTTON_A11Y:
+                logEvent(LAUNCHER_TASKBAR_A11Y_BUTTON_TAP);
                 notifyA11yClick(false /* longClick */);
                 break;
             case BUTTON_QUICK_SETTINGS:
@@ -128,15 +147,19 @@
     public boolean onButtonLongClick(@TaskbarButton int buttonType) {
         switch (buttonType) {
             case BUTTON_HOME:
+                logEvent(LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS);
                 startAssistant();
                 return true;
             case BUTTON_A11Y:
+                logEvent(LAUNCHER_TASKBAR_A11Y_BUTTON_LONGPRESS);
                 notifyA11yClick(true /* longClick */);
                 return true;
             case BUTTON_BACK:
+                logEvent(LAUNCHER_TASKBAR_BACK_BUTTON_LONGPRESS);
+                return backRecentsLongpress(buttonType);
             case BUTTON_RECENTS:
-                mLongPressedButtons |= buttonType;
-                return determineScreenUnpin();
+                logEvent(LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS);
+                return backRecentsLongpress(buttonType);
             case BUTTON_IME_SWITCH:
             default:
                 return false;
@@ -164,6 +187,11 @@
         }
     }
 
+    private boolean backRecentsLongpress(@TaskbarButton int buttonType) {
+        mLongPressedButtons |= buttonType;
+        return determineScreenUnpin();
+    }
+
     /**
      * Checks if the user has long pressed back and recents buttons
      * "together" (within {@link #SCREEN_PIN_LONG_PRESS_THRESHOLD})ms
@@ -210,6 +238,22 @@
         mScreenPinned = (sysuiFlags & SYSUI_STATE_SCREEN_PINNING) != 0;
     }
 
+    public void init(TaskbarControllers taskbarControllers) {
+        mStatsLogManager = taskbarControllers.getTaskbarActivityContext().getStatsLogManager();
+    }
+
+    public void onDestroy() {
+        mStatsLogManager = null;
+    }
+
+    private void logEvent(StatsLogManager.LauncherEvent event) {
+        if (mStatsLogManager == null) {
+            Log.w(TAG, "No stats log manager to log taskbar button event");
+            return;
+        }
+        mStatsLogManager.logger().log(event);
+    }
+
     private void navigateHome() {
         mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_HOME);
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 54dd0b2..7abe6d4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -19,6 +19,7 @@
 
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_HIDE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_SHOW;
+import static com.android.launcher3.taskbar.Utilities.appendFlag;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
 
@@ -64,6 +65,9 @@
             | FLAG_STASHED_IN_APP_PINNED | FLAG_STASHED_IN_APP_EMPTY | FLAG_STASHED_IN_APP_SETUP
             | FLAG_STASHED_IN_APP_IME | FLAG_STASHED_IN_APP_ALL_APPS;
 
+    private static final int FLAGS_STASHED_IN_APP_IGNORING_IME =
+            FLAGS_STASHED_IN_APP & ~FLAG_STASHED_IN_APP_IME;
+
     // If any of these flags are enabled, inset apps by our stashed height instead of our unstashed
     // height. This way the reported insets are consistent even during transitions out of the app.
     // Currently any flag that causes us to stash in an app is included, except for IME or All Apps
@@ -238,6 +242,13 @@
     }
 
     /**
+     * Returns whether the taskbar should be stashed in apps regardless of the IME visibility.
+     */
+    public boolean isStashedInAppIgnoringIme() {
+        return hasAnyFlag(FLAGS_STASHED_IN_APP_IGNORING_IME);
+    }
+
+    /**
      * Returns whether the taskbar should be stashed in the current LauncherState.
      */
     public boolean isInStashedLauncherState() {
@@ -585,7 +596,11 @@
 
     private void notifyStashChange(boolean visible, boolean stashed) {
         mSystemUiProxy.notifyTaskbarStatus(visible, stashed);
-        mControllers.taskbarActivityContext.updateInsetRoundedCornerFrame(visible && !stashed);
+        // If stashing taskbar is caused by IME visibility, we could just skip updating rounded
+        // corner insets since the rounded corners will be covered by IME during IME is showing and
+        // taskbar will be restored back to unstashed when IME is hidden.
+        mControllers.taskbarActivityContext.updateInsetRoundedCornerFrame(
+                    visible && !isStashedInAppIgnoringIme());
         mControllers.rotationButtonController.onTaskbarStateChange(visible, stashed);
     }
 
@@ -606,16 +621,15 @@
 
     private static String getStateString(int flags) {
         StringJoiner str = new StringJoiner("|");
-        str.add((flags & FLAG_IN_APP) != 0 ? "FLAG_IN_APP" : "");
-        str.add((flags & FLAG_STASHED_IN_APP_MANUAL) != 0 ? "FLAG_STASHED_IN_APP_MANUAL" : "");
-        str.add((flags & FLAG_STASHED_IN_APP_PINNED) != 0 ? "FLAG_STASHED_IN_APP_PINNED" : "");
-        str.add((flags & FLAG_STASHED_IN_APP_EMPTY) != 0 ? "FLAG_STASHED_IN_APP_EMPTY" : "");
-        str.add((flags & FLAG_STASHED_IN_APP_SETUP) != 0 ? "FLAG_STASHED_IN_APP_SETUP" : "");
-        str.add((flags & FLAG_STASHED_IN_APP_IME) != 0 ? "FLAG_STASHED_IN_APP_IME" : "");
-        str.add((flags & FLAG_IN_STASHED_LAUNCHER_STATE) != 0
-                ? "FLAG_IN_STASHED_LAUNCHER_STATE" : "");
-        str.add((flags & FLAG_STASHED_IN_APP_ALL_APPS) != 0 ? "FLAG_STASHED_IN_APP_ALL_APPS" : "");
-        str.add((flags & FLAG_IN_SETUP) != 0 ? "FLAG_IN_SETUP" : "");
+        appendFlag(str, flags, FLAGS_IN_APP, "FLAG_IN_APP");
+        appendFlag(str, flags, FLAG_STASHED_IN_APP_MANUAL, "FLAG_STASHED_IN_APP_MANUAL");
+        appendFlag(str, flags, FLAG_STASHED_IN_APP_PINNED, "FLAG_STASHED_IN_APP_PINNED");
+        appendFlag(str, flags, FLAG_STASHED_IN_APP_EMPTY, "FLAG_STASHED_IN_APP_EMPTY");
+        appendFlag(str, flags, FLAG_STASHED_IN_APP_SETUP, "FLAG_STASHED_IN_APP_SETUP");
+        appendFlag(str, flags, FLAG_STASHED_IN_APP_IME, "FLAG_STASHED_IN_APP_IME");
+        appendFlag(str, flags, FLAG_IN_STASHED_LAUNCHER_STATE, "FLAG_IN_STASHED_LAUNCHER_STATE");
+        appendFlag(str, flags, FLAG_STASHED_IN_APP_ALL_APPS, "FLAG_STASHED_IN_APP_ALL_APPS");
+        appendFlag(str, flags, FLAG_IN_SETUP, "FLAG_IN_SETUP");
         return str.toString();
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/Utilities.java b/quickstep/src/com/android/launcher3/taskbar/Utilities.java
new file mode 100644
index 0000000..fda6453
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/Utilities.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 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.taskbar;
+
+import java.util.StringJoiner;
+
+/**
+ * Various utilities shared amongst the Taskbar's classes.
+ */
+public final class Utilities {
+
+    private Utilities() {}
+
+    static void appendFlag(StringJoiner str, int flags, int flag, String flagName) {
+        if ((flags & flag) != 0) {
+            str.add(flagName);
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 6646e1b..9f35507 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -1413,6 +1413,7 @@
                 .setStartBounds(startRect)
                 .setDestinationBounds(destinationBounds)
                 .setCornerRadius(mRecentsView.getPipCornerRadius())
+                .setShadowRadius(mRecentsView.getPipShadowRadius())
                 .setAttachedView(mRecentsView);
         // We would assume home and app window always in the same rotation While homeRotation
         // is not ROTATION_0 (which implies the rotation is turned on in launcher settings).
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index ff67b09..53296b5 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -71,6 +71,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.provider.RestoreDbTask;
 import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.taskbar.TaskbarActivityContext;
 import com.android.launcher3.taskbar.TaskbarManager;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
@@ -174,7 +175,7 @@
                 SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy, pip,
                         splitscreen, onehanded, shellTransitions, startingWindow, recentTasks,
                         launcherUnlockAnimationController, backAnimation);
-                TouchInteractionService.this.initInputMonitor();
+                TouchInteractionService.this.initInputMonitor("TISBinder#onInitialize()");
                 preloadOverview(true /* fromInit */);
             });
             sIsInitialized = true;
@@ -372,7 +373,8 @@
         sConnected = true;
     }
 
-    private void disposeEventHandlers() {
+    private void disposeEventHandlers(String reason) {
+        Log.d(TAG, "disposeEventHandlers: Reason: " + reason);
         if (mInputEventReceiver != null) {
             mInputEventReceiver.dispose();
             mInputEventReceiver = null;
@@ -383,8 +385,8 @@
         }
     }
 
-    private void initInputMonitor() {
-        disposeEventHandlers();
+    private void initInputMonitor(String reason) {
+        disposeEventHandlers("Initializing input monitor due to: " + reason);
 
         if (mDeviceState.isButtonNavMode()) {
             return;
@@ -401,7 +403,7 @@
      * Called when the navigation mode changes, guaranteed to be after the device state has updated.
      */
     private void onNavigationModeChanged() {
-        initInputMonitor();
+        initInputMonitor("onNavigationModeChanged()");
         resetHomeBounceSeenOnQuickstepEnabledFirstTime();
     }
 
@@ -520,7 +522,7 @@
             mInputConsumer.unregisterInputConsumer();
             mOverviewComponentObserver.onDestroy();
         }
-        disposeEventHandlers();
+        disposeEventHandlers("TouchInteractionService onDestroy()");
         mDeviceState.destroy();
         SystemUiProxy.INSTANCE.get(this).clearProxy();
         ProtoTracer.INSTANCE.get(this).stop();
@@ -687,11 +689,9 @@
             }
 
             // If Taskbar is present, we listen for long press to unstash it.
-            BaseActivityInterface activityInterface = newGestureState.getActivityInterface();
-            StatefulActivity activity = activityInterface.getCreatedActivity();
-            if (activity != null && activity.getDeviceProfile().isTaskbarPresent) {
-                base = new TaskbarStashInputConsumer(this, base, mInputMonitorCompat,
-                        mTaskbarManager.getCurrentActivityContext());
+            TaskbarActivityContext tac = mTaskbarManager.getCurrentActivityContext();
+            if (tac != null) {
+                base = new TaskbarStashInputConsumer(this, base, mInputMonitorCompat, tac);
             }
 
             // If Bubbles is expanded, use the overlay input consumer, which will close Bubbles
diff --git a/quickstep/src/com/android/quickstep/ViewUtils.java b/quickstep/src/com/android/quickstep/ViewUtils.java
index ab26d15..1fef544 100644
--- a/quickstep/src/com/android/quickstep/ViewUtils.java
+++ b/quickstep/src/com/android/quickstep/ViewUtils.java
@@ -83,7 +83,7 @@
         }
 
         private boolean schedule() {
-            if (mViewRoot.getView() != null) {
+            if (mViewRoot != null && mViewRoot.getView() != null) {
                 mViewRoot.registerRtFrameCallback(this);
                 mViewRoot.getView().invalidate();
                 return true;
diff --git a/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialFragment.java
index b3f2354..f440638 100644
--- a/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialFragment.java
@@ -15,20 +15,18 @@
  */
 package com.android.quickstep.interaction;
 
-import android.content.SharedPreferences;
 import android.view.MotionEvent;
 import android.view.View;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.quickstep.interaction.TutorialController.TutorialType;
 
 /** Shows the Home gesture interactive tutorial. */
 public class AssistantGestureTutorialFragment extends TutorialFragment {
 
-    protected AssistantGestureTutorialFragment(
-            SharedPreferences sharedPrefs, StatsLogManager statsLogManager) {
-        super(sharedPrefs, statsLogManager);
-    }
+    public AssistantGestureTutorialFragment() {}
 
     @Override
     TutorialController createController(TutorialType type) {
@@ -49,12 +47,12 @@
     }
 
     @Override
-    void logTutorialStepShown() {
+    void logTutorialStepShown(@NonNull StatsLogManager statsLogManager) {
         // No-Op: tutorial step not currently shown to users
     }
 
     @Override
-    void logTutorialStepCompleted() {
+    void logTutorialStepCompleted(@NonNull StatsLogManager statsLogManager) {
         // No-Op: tutorial step not currently shown to users
     }
 }
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
index e7ed0b4..37253e2 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
@@ -19,10 +19,10 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
-import android.content.SharedPreferences;
 import android.view.MotionEvent;
 import android.view.View;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.R;
@@ -34,10 +34,7 @@
 /** Shows the Back gesture interactive tutorial. */
 public class BackGestureTutorialFragment extends TutorialFragment {
 
-    protected BackGestureTutorialFragment(
-            SharedPreferences sharedPrefs, StatsLogManager statsLogManager) {
-        super(sharedPrefs, statsLogManager);
-    }
+    public BackGestureTutorialFragment() {}
 
     @Nullable
     @Override
@@ -126,14 +123,14 @@
     }
 
     @Override
-    void logTutorialStepShown() {
-        mStatsLogManager.logger().log(
+    void logTutorialStepShown(@NonNull StatsLogManager statsLogManager) {
+        statsLogManager.logger().log(
                 StatsLogManager.LauncherEvent.LAUNCHER_GESTURE_TUTORIAL_BACK_STEP_SHOWN);
     }
 
     @Override
-    void logTutorialStepCompleted() {
-        mStatsLogManager.logger().log(
+    void logTutorialStepCompleted(@NonNull StatsLogManager statsLogManager) {
+        statsLogManager.logger().log(
                 StatsLogManager.LauncherEvent.LAUNCHER_GESTURE_TUTORIAL_BACK_STEP_COMPLETED);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
index c532f8e..d059d82 100644
--- a/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
@@ -31,6 +31,7 @@
 
 import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.util.DisplayController;
 
 /**
  * Utility class to handle edge swipes for back gestures.
@@ -115,10 +116,9 @@
             // Add a nav bar panel window.
             mEdgeBackPanel = new EdgeBackGesturePanel(mContext, parent, createLayoutParams());
             mEdgeBackPanel.setBackCallback(mBackCallback);
-            if (mContext.getDisplay() != null) {
-                mContext.getDisplay().getRealSize(mDisplaySize);
-                mEdgeBackPanel.setDisplaySize(mDisplaySize);
-            }
+            Point currentSize = DisplayController.INSTANCE.get(mContext).getInfo().currentSize;
+            mDisplaySize.set(currentSize.x, currentSize.y);
+            mEdgeBackPanel.setDisplaySize(mDisplaySize);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
index 002e8b7..7daac81 100644
--- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
@@ -65,10 +65,7 @@
         mTutorialSteps = getTutorialSteps(args);
         mCurrentTutorialStep = mTutorialSteps[mCurrentStep - 1];
         mFragment = TutorialFragment.newInstance(
-                mCurrentTutorialStep,
-                args.getBoolean(KEY_GESTURE_COMPLETE, false),
-                mSharedPrefs,
-                mStatsLogManager);
+                mCurrentTutorialStep, args.getBoolean(KEY_GESTURE_COMPLETE, false));
         getSupportFragmentManager().beginTransaction()
                 .add(R.id.gesture_tutorial_fragment_container, mFragment)
                 .commit();
@@ -103,6 +100,14 @@
         super.onSaveInstanceState(savedInstanceState);
     }
 
+    protected SharedPreferences getSharedPrefs() {
+        return mSharedPrefs;
+    }
+
+    protected StatsLogManager getStatsLogManager() {
+        return mStatsLogManager;
+    }
+
     /** Returns true iff there aren't anymore tutorial types to display to the user. */
     public boolean isTutorialComplete() {
         return mCurrentStep >= mNumSteps;
@@ -128,7 +133,7 @@
         }
         mCurrentTutorialStep = mTutorialSteps[mCurrentStep];
         mFragment = TutorialFragment.newInstance(
-                mCurrentTutorialStep, /* gestureComplete= */ false, mSharedPrefs, mStatsLogManager);
+                mCurrentTutorialStep, /* gestureComplete= */ false);
         getSupportFragmentManager().beginTransaction()
                 .replace(R.id.gesture_tutorial_fragment_container, mFragment)
                 .runOnCommit(() -> mFragment.onAttachedToWindow())
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java
index e987d5a..95eafda 100644
--- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java
@@ -18,10 +18,10 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
-import android.content.SharedPreferences;
 import android.view.MotionEvent;
 import android.view.View;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.R;
@@ -33,10 +33,7 @@
 /** Shows the Home gesture interactive tutorial. */
 public class HomeGestureTutorialFragment extends TutorialFragment {
 
-    protected HomeGestureTutorialFragment(
-            SharedPreferences sharedPrefs, StatsLogManager statsLogManager) {
-        super(sharedPrefs, statsLogManager);
-    }
+    public HomeGestureTutorialFragment() {}
 
     @Nullable
     @Override
@@ -108,14 +105,14 @@
     }
 
     @Override
-    void logTutorialStepShown() {
-        mStatsLogManager.logger().log(
+    void logTutorialStepShown(@NonNull StatsLogManager statsLogManager) {
+        statsLogManager.logger().log(
                 StatsLogManager.LauncherEvent.LAUNCHER_GESTURE_TUTORIAL_HOME_STEP_SHOWN);
     }
 
     @Override
-    void logTutorialStepCompleted() {
-        mStatsLogManager.logger().log(
+    void logTutorialStepCompleted(@NonNull StatsLogManager statsLogManager) {
+        statsLogManager.logger().log(
                 StatsLogManager.LauncherEvent.LAUNCHER_GESTURE_TUTORIAL_HOME_STEP_COMPLETED);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
index a8163af..f981860 100644
--- a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
@@ -37,7 +37,6 @@
 import android.view.Display;
 import android.view.GestureDetector;
 import android.view.MotionEvent;
-import android.view.Surface;
 import android.view.View;
 import android.view.View.OnTouchListener;
 import android.view.ViewConfiguration;
@@ -47,6 +46,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.NavigationMode;
 import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.util.NavBarPosition;
@@ -92,13 +92,10 @@
     NavBarGestureHandler(Context context) {
         mContext = context;
         final Display display = mContext.getDisplay();
-        final int displayRotation;
-        if (display == null) {
-            displayRotation = Surface.ROTATION_0;
-        } else {
-            displayRotation = display.getRotation();
-            display.getRealSize(mDisplaySize);
-        }
+        DisplayController.Info displayInfo = DisplayController.INSTANCE.get(mContext).getInfo();
+        final int displayRotation = displayInfo.rotation;
+        Point currentSize = displayInfo.currentSize;
+        mDisplaySize.set(currentSize.x, currentSize.y);
         mSwipeUpTouchTracker =
                 new TriggerSwipeUpTouchTracker(context, true /*disableHorizontalSwipe*/,
                         new NavBarPosition(NavigationMode.NO_BUTTON, displayRotation),
diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java
index c7e24db..4e1521f 100644
--- a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java
@@ -18,10 +18,10 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
-import android.content.SharedPreferences;
 import android.view.MotionEvent;
 import android.view.View;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.R;
@@ -33,10 +33,7 @@
 /** Shows the Overview gesture interactive tutorial. */
 public class OverviewGestureTutorialFragment extends TutorialFragment {
 
-    protected OverviewGestureTutorialFragment(
-            SharedPreferences sharedPrefs, StatsLogManager statsLogManager) {
-        super(sharedPrefs, statsLogManager);
-    }
+    public OverviewGestureTutorialFragment() {}
 
     @Nullable
     @Override
@@ -120,14 +117,14 @@
     }
 
     @Override
-    void logTutorialStepShown() {
-        mStatsLogManager.logger().log(
+    void logTutorialStepShown(@NonNull StatsLogManager statsLogManager) {
+        statsLogManager.logger().log(
                 StatsLogManager.LauncherEvent.LAUNCHER_GESTURE_TUTORIAL_OVERVIEW_STEP_SHOWN);
     }
 
     @Override
-    void logTutorialStepCompleted() {
-        mStatsLogManager.logger().log(
+    void logTutorialStepCompleted(@NonNull StatsLogManager statsLogManager) {
+        statsLogManager.logger().log(
                 StatsLogManager.LauncherEvent.LAUNCHER_GESTURE_TUTORIAL_OVERVIEW_STEP_COMPLETED);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/interaction/SandboxModeTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/SandboxModeTutorialFragment.java
index 92a2731..5183e2c 100644
--- a/quickstep/src/com/android/quickstep/interaction/SandboxModeTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/SandboxModeTutorialFragment.java
@@ -15,20 +15,18 @@
  */
 package com.android.quickstep.interaction;
 
-import android.content.SharedPreferences;
 import android.view.MotionEvent;
 import android.view.View;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.quickstep.interaction.TutorialController.TutorialType;
 
 /** Shows the general navigation gesture sandbox environment. */
 public class SandboxModeTutorialFragment extends TutorialFragment {
 
-    protected SandboxModeTutorialFragment(
-            SharedPreferences sharedPrefs, StatsLogManager statsLogManager) {
-        super(sharedPrefs, statsLogManager);
-    }
+    public SandboxModeTutorialFragment() {}
 
     @Override
     TutorialController createController(TutorialType type) {
@@ -49,12 +47,12 @@
     }
 
     @Override
-    void logTutorialStepShown() {
+    void logTutorialStepShown(@NonNull StatsLogManager statsLogManager) {
         // No-Op: tutorial step not currently shown to users
     }
 
     @Override
-    void logTutorialStepCompleted() {
+    void logTutorialStepCompleted(@NonNull StatsLogManager statsLogManager) {
         // No-Op: tutorial step not currently shown to users
     }
 }
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
index 7556bf4..33e800d 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
@@ -60,9 +60,6 @@
     private static final String COMPLETED_TUTORIAL_STEPS_PREFERENCE_KEY =
             "pref_completedTutorialSteps";
 
-    private final SharedPreferences mSharedPrefs;
-    protected final StatsLogManager mStatsLogManager;
-
     TutorialType mTutorialType;
     boolean mGestureComplete = false;
     @Nullable TutorialController mTutorialController = null;
@@ -84,14 +81,10 @@
     private boolean mIsFoldable;
 
     public static TutorialFragment newInstance(
-            TutorialType tutorialType,
-            boolean gestureComplete,
-            SharedPreferences sharedPrefs,
-            StatsLogManager statsLogManager) {
-        TutorialFragment fragment =
-                getFragmentForTutorialType(tutorialType, sharedPrefs, statsLogManager);
+            TutorialType tutorialType, boolean gestureComplete) {
+        TutorialFragment fragment = getFragmentForTutorialType(tutorialType);
         if (fragment == null) {
-            fragment = new BackGestureTutorialFragment(sharedPrefs, statsLogManager);
+            fragment = new BackGestureTutorialFragment();
             tutorialType = TutorialType.BACK_NAVIGATION;
         }
 
@@ -103,36 +96,28 @@
     }
 
     @Nullable
-    private static TutorialFragment getFragmentForTutorialType(
-            TutorialType tutorialType,
-            SharedPreferences sharedPrefs,
-            StatsLogManager statsLogManager) {
+    private static TutorialFragment getFragmentForTutorialType(TutorialType tutorialType) {
         switch (tutorialType) {
             case BACK_NAVIGATION:
             case BACK_NAVIGATION_COMPLETE:
-                return new BackGestureTutorialFragment(sharedPrefs, statsLogManager);
+                return new BackGestureTutorialFragment();
             case HOME_NAVIGATION:
             case HOME_NAVIGATION_COMPLETE:
-                return new HomeGestureTutorialFragment(sharedPrefs, statsLogManager);
+                return new HomeGestureTutorialFragment();
             case OVERVIEW_NAVIGATION:
             case OVERVIEW_NAVIGATION_COMPLETE:
-                return new OverviewGestureTutorialFragment(sharedPrefs, statsLogManager);
+                return new OverviewGestureTutorialFragment();
             case ASSISTANT:
             case ASSISTANT_COMPLETE:
-                return new AssistantGestureTutorialFragment(sharedPrefs, statsLogManager);
+                return new AssistantGestureTutorialFragment();
             case SANDBOX_MODE:
-                return new SandboxModeTutorialFragment(sharedPrefs, statsLogManager);
+                return new SandboxModeTutorialFragment();
             default:
                 Log.e(LOG_TAG, "Failed to find an appropriate fragment for " + tutorialType.name());
         }
         return null;
     }
 
-    protected TutorialFragment(SharedPreferences sharedPrefs, StatsLogManager statsLogManager) {
-        mSharedPrefs = sharedPrefs;
-        mStatsLogManager = statsLogManager;
-    }
-
     @Nullable Integer getEdgeAnimationResId() {
         return null;
     }
@@ -340,7 +325,10 @@
     }
 
     void onAttachedToWindow() {
-        logTutorialStepShown();
+        StatsLogManager statsLogManager = getStatsLogManager();
+        if (statsLogManager != null) {
+            logTutorialStepShown(statsLogManager);
+        }
         mEdgeBackGestureHandler.setViewGroupParent(getRootView());
     }
 
@@ -374,14 +362,20 @@
     }
 
     void continueTutorial() {
-        Set<String> updatedCompletedSteps = new ArraySet<>(mSharedPrefs.getStringSet(
-                COMPLETED_TUTORIAL_STEPS_PREFERENCE_KEY, new ArraySet<>()));
+        SharedPreferences sharedPrefs = getSharedPreferences();
+        if (sharedPrefs != null) {
+            Set<String> updatedCompletedSteps = new ArraySet<>(sharedPrefs.getStringSet(
+                    COMPLETED_TUTORIAL_STEPS_PREFERENCE_KEY, new ArraySet<>()));
 
-        updatedCompletedSteps.add(mTutorialType.toString());
+            updatedCompletedSteps.add(mTutorialType.toString());
 
-        mSharedPrefs.edit().putStringSet(
-                COMPLETED_TUTORIAL_STEPS_PREFERENCE_KEY, updatedCompletedSteps).apply();
-        logTutorialStepCompleted();
+            sharedPrefs.edit().putStringSet(
+                    COMPLETED_TUTORIAL_STEPS_PREFERENCE_KEY, updatedCompletedSteps).apply();
+        }
+        StatsLogManager statsLogManager = getStatsLogManager();
+        if (statsLogManager != null) {
+            logTutorialStepCompleted(statsLogManager);
+        }
 
         GestureSandboxActivity gestureSandboxActivity = getGestureSandboxActivity();
         if (gestureSandboxActivity == null) {
@@ -397,9 +391,15 @@
 
     void closeTutorial(boolean tutorialSkipped) {
         if (tutorialSkipped) {
-            mSharedPrefs.edit().putBoolean(TUTORIAL_SKIPPED_PREFERENCE_KEY, true).apply();
-            mStatsLogManager.logger().log(
-                    StatsLogManager.LauncherEvent.LAUNCHER_GESTURE_TUTORIAL_SKIPPED);
+            SharedPreferences sharedPrefs = getSharedPreferences();
+            if (sharedPrefs != null) {
+                sharedPrefs.edit().putBoolean(TUTORIAL_SKIPPED_PREFERENCE_KEY, true).apply();
+            }
+            StatsLogManager statsLogManager = getStatsLogManager();
+            if (statsLogManager != null) {
+                statsLogManager.logger().log(
+                        StatsLogManager.LauncherEvent.LAUNCHER_GESTURE_TUTORIAL_SKIPPED);
+            }
         }
         FragmentActivity activity = getActivity();
         if (activity != null) {
@@ -433,9 +433,9 @@
                 || (mTutorialController != null && mTutorialController.isGestureCompleted());
     }
 
-    abstract void logTutorialStepShown();
+    abstract void logTutorialStepShown(@NonNull StatsLogManager statsLogManager);
 
-    abstract void logTutorialStepCompleted();
+    abstract void logTutorialStepCompleted(@NonNull StatsLogManager statsLogManager);
 
     @Nullable
     private GestureSandboxActivity getGestureSandboxActivity() {
@@ -443,4 +443,18 @@
 
         return context instanceof GestureSandboxActivity ? (GestureSandboxActivity) context : null;
     }
+
+    @Nullable
+    private StatsLogManager getStatsLogManager() {
+        GestureSandboxActivity activity = getGestureSandboxActivity();
+
+        return activity != null ? activity.getStatsLogManager() : null;
+    }
+
+    @Nullable
+    private SharedPreferences getSharedPreferences() {
+        GestureSandboxActivity activity = getGestureSandboxActivity();
+
+        return activity != null ? activity.getSharedPrefs() : null;
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index f6002ec..13007ea 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -403,7 +403,7 @@
                             String.format("(State:%s->%s)", getStateString(srcState),
                                     getStateString(dstState)));
                 }
-                if (mItemInfo != DEFAULT_ITEM_INFO) {
+                if (atomInfo.hasContainerInfo()) {
                     logStringBuilder.append("\n").append(atomInfo);
                 }
                 Log.d(TAG, logStringBuilder.toString());
@@ -492,9 +492,7 @@
                 String name = (event instanceof Enum) ? ((Enum) event).name() :
                         event.getId() + "";
                 StringBuilder logStringBuilder = new StringBuilder("\n");
-                if (mInstanceId != DEFAULT_INSTANCE_ID) {
-                    logStringBuilder.append(String.format("InstanceId:%s ", mInstanceId));
-                }
+                logStringBuilder.append(String.format("InstanceId:%s ", mInstanceId));
                 logStringBuilder.append(String.format("%s=%sms", name, mLatencyInMillis));
                 Log.d(LATENCY_TAG, logStringBuilder.toString());
             }
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index ee35adc..70fde1d 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -101,6 +101,7 @@
      * @param fromRotation From rotation if different from final rotation, ROTATION_0 otherwise
      * @param destinationBoundsTransformed Destination bounds in window space
      * @param cornerRadius Corner radius in pixel value for PiP window
+     * @param shadowRadius Shadow radius in pixel value for PiP window
      * @param view Attached view for logging purpose
      */
     private SwipePipToHomeAnimator(@NonNull Context context,
@@ -115,6 +116,7 @@
             @RecentsOrientedState.SurfaceRotation int fromRotation,
             @NonNull Rect destinationBoundsTransformed,
             int cornerRadius,
+            int shadowRadius,
             @NonNull View view) {
         super(startBounds, new RectF(destinationBoundsTransformed), context, null);
         mTaskId = taskId;
@@ -126,7 +128,7 @@
         mDestinationBounds.set(destinationBounds);
         mFromRotation = fromRotation;
         mDestinationBoundsTransformed.set(destinationBoundsTransformed);
-        mSurfaceTransactionHelper = new PipSurfaceTransactionHelper(cornerRadius);
+        mSurfaceTransactionHelper = new PipSurfaceTransactionHelper(cornerRadius, shadowRadius);
 
         if (sourceRectHint != null && (sourceRectHint.width() < destinationBounds.width()
                 || sourceRectHint.height() < destinationBounds.height())) {
@@ -324,6 +326,7 @@
         private RectF mStartBounds;
         private Rect mDestinationBounds;
         private int mCornerRadius;
+        private int mShadowRadius;
         private View mAttachedView;
         private @RecentsOrientedState.SurfaceRotation int mFromRotation = Surface.ROTATION_0;
         private final Rect mDestinationBoundsTransformed = new Rect();
@@ -378,6 +381,11 @@
             return this;
         }
 
+        public Builder setShadowRadius(int shadowRadius) {
+            mShadowRadius = shadowRadius;
+            return this;
+        }
+
         public Builder setAttachedView(View attachedView) {
             mAttachedView = attachedView;
             return this;
@@ -422,7 +430,7 @@
                     mSourceRectHint, mAppBounds,
                     mHomeToWindowPositionMap, mStartBounds, mDestinationBounds,
                     mFromRotation, mDestinationBoundsTransformed,
-                    mCornerRadius, mAttachedView);
+                    mCornerRadius, mShadowRadius, mAttachedView);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index b9615ab..955fffc 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -237,7 +237,7 @@
         }
         getPagedOrientationHandler().measureGroupedTaskViewThumbnailBounds(mSnapshotView,
                 mSnapshotView2, widthSize, heightSize, mSplitBoundsConfig,
-                mActivity.getDeviceProfile());
+                mActivity.getDeviceProfile(), getLayoutDirection() == LAYOUT_DIRECTION_RTL);
         updateIconPlacement();
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 6b15807..8e5839c 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -58,7 +58,6 @@
             HIDDEN_NON_ZERO_ROTATION,
             HIDDEN_NO_TASKS,
             HIDDEN_NO_RECENTS,
-            HIDDEN_FOCUSED_SCROLL,
             HIDDEN_SPLIT_SCREEN})
     @Retention(RetentionPolicy.SOURCE)
     public @interface ActionsHiddenFlags { }
@@ -66,8 +65,7 @@
     public static final int HIDDEN_NON_ZERO_ROTATION = 1 << 0;
     public static final int HIDDEN_NO_TASKS = 1 << 1;
     public static final int HIDDEN_NO_RECENTS = 1 << 2;
-    public static final int HIDDEN_FOCUSED_SCROLL = 1 << 3;
-    public static final int HIDDEN_SPLIT_SCREEN = 1 << 4;
+    public static final int HIDDEN_SPLIT_SCREEN = 1 << 3;
 
     @IntDef(flag = true, value = {
             DISABLED_SCROLLING,
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 22491bc..25f8f01 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -375,6 +375,8 @@
     // OverScroll constants
     private static final int OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION = 270;
 
+    private static final int DEFAULT_ACTIONS_VIEW_ALPHA_ANIMATION_DURATION = 300;
+
     private static final int DISMISS_TASK_DURATION = 300;
     private static final int ADDITION_TASK_DURATION = 200;
     private static final float INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.55f;
@@ -539,6 +541,7 @@
     private final PinnedStackAnimationListener mIPipAnimationListener =
             new PinnedStackAnimationListener();
     private int mPipCornerRadius;
+    private int mPipShadowRadius;
 
     // Used to keep track of the last requested task list id, so that we do not request to load the
     // tasks again if we have already requested it and the task list has not changed
@@ -647,6 +650,8 @@
     private TaskView mMovingTaskView;
 
     private OverviewActionsView mActionsView;
+    private ObjectAnimator mActionsViewAlphaAnimator;
+    private float mActionsViewAlphaAnimatorFinalValue;
 
     private MultiWindowModeChangedListener mMultiWindowModeChangedListener =
             new MultiWindowModeChangedListener() {
@@ -1132,6 +1137,11 @@
         return getScrollForPage(taskIndex) == getPagedOrientationHandler().getPrimaryScroll(this);
     }
 
+    private boolean isFocusedTaskInExpectedScrollPosition() {
+        TaskView focusedTask = getFocusedTaskView();
+        return focusedTask != null && isTaskInExpectedScrollPosition(indexOfChild(focusedTask));
+    }
+
     /**
      * Returns a {@link TaskView} that has taskId matching {@code taskId} or null if no match.
      */
@@ -1178,13 +1188,15 @@
     @Override
     protected void onPageBeginTransition() {
         super.onPageBeginTransition();
-        mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, true);
+        if (!mActivity.getDeviceProfile().isTablet) {
+            mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, true);
+        }
     }
 
     @Override
     protected void onPageEndTransition() {
         super.onPageEndTransition();
-        if (isClearAllHidden()) {
+        if (isClearAllHidden() && !mActivity.getDeviceProfile().isTablet) {
             mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false);
         }
         if (getNextPage() > 0) {
@@ -1785,16 +1797,24 @@
     }
 
     private void updateActionsViewFocusedScroll() {
-        boolean hiddenFocusedScroll;
         if (showAsGrid()) {
-            TaskView focusedTaskView = getFocusedTaskView();
-            hiddenFocusedScroll = focusedTaskView == null
-                    || !isTaskInExpectedScrollPosition(indexOfChild(focusedTaskView));
-        } else {
-            hiddenFocusedScroll = false;
+            float actionsViewAlphaValue = isFocusedTaskInExpectedScrollPosition() ? 1 : 0;
+            // If animation is already in progress towards the same end value, do not restart.
+            if (mActionsViewAlphaAnimator == null || !mActionsViewAlphaAnimator.isStarted()
+                    || (mActionsViewAlphaAnimator.isStarted()
+                    && mActionsViewAlphaAnimatorFinalValue != actionsViewAlphaValue)) {
+                animateActionsViewAlpha(actionsViewAlphaValue,
+                        DEFAULT_ACTIONS_VIEW_ALPHA_ANIMATION_DURATION);
+            }
         }
-        mActionsView.updateHiddenFlags(OverviewActionsView.HIDDEN_FOCUSED_SCROLL,
-                hiddenFocusedScroll);
+    }
+
+    private void animateActionsViewAlpha(float alphaValue, long duration) {
+        mActionsViewAlphaAnimator = ObjectAnimator.ofFloat(
+                mActionsView.getVisibilityAlpha(), MultiValueAlpha.VALUE, alphaValue);
+        mActionsViewAlphaAnimatorFinalValue = alphaValue;
+        mActionsViewAlphaAnimator.setDuration(duration);
+        mActionsViewAlphaAnimator.start();
     }
 
     /**
@@ -2323,10 +2343,9 @@
     }
 
     private void animateActionsViewIn() {
-        ObjectAnimator anim = ObjectAnimator.ofFloat(
-                mActionsView.getVisibilityAlpha(), MultiValueAlpha.VALUE, 0, 1);
-        anim.setDuration(TaskView.SCALE_ICON_DURATION);
-        anim.start();
+        if (!showAsGrid() || isFocusedTaskInExpectedScrollPosition()) {
+            animateActionsViewAlpha(1, TaskView.SCALE_ICON_DURATION);
+        }
     }
 
     public void animateUpTaskIconScale() {
@@ -3262,7 +3281,7 @@
                         // Update various scroll-dependent UI.
                         dispatchScrollChanged();
                         updateActionsViewFocusedScroll();
-                        if (isClearAllHidden()) {
+                        if (isClearAllHidden() && !mActivity.getDeviceProfile().isTablet) {
                             mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING,
                                     false);
                         }
@@ -5004,6 +5023,14 @@
         return mPipCornerRadius;
     }
 
+    /**
+     * @return Shadow radius in pixel value for PiP window, which is updated via
+     *         {@link #mIPipAnimationListener}
+     */
+    public int getPipShadowRadius() {
+        return mPipShadowRadius;
+    }
+
     @Override
     public boolean scrollLeft() {
         if (!showAsGrid()) {
@@ -5099,9 +5126,10 @@
         }
 
         @Override
-        public void onPipCornerRadiusChanged(int cornerRadius) {
+        public void onPipResourceDimensionsChanged(int cornerRadius, int shadowRadius) {
             if (mRecentsView != null) {
                 mRecentsView.mPipCornerRadius = cornerRadius;
+                mRecentsView.mPipShadowRadius = shadowRadius;
             }
         }
 
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
index ba1a60d..d8be307 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
@@ -1,5 +1,11 @@
 package com.android.launcher3.taskbar;
 
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_BACK_BUTTON_LONGPRESS;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_BACK_BUTTON_TAP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_HOME_BUTTON_TAP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
@@ -11,6 +17,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -19,6 +26,7 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.launcher3.logging.StatsLogManager;
 import com.android.quickstep.OverviewCommandHelper;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TouchInteractionService;
@@ -42,6 +50,14 @@
     OverviewCommandHelper mockCommandHelper;
     @Mock
     Handler mockHandler;
+    @Mock
+    StatsLogManager mockStatsLogManager;
+    @Mock
+    StatsLogManager.StatsLogger mockStatsLogger;
+    @Mock
+    TaskbarControllers mockTaskbarControllers;
+    @Mock
+    TaskbarActivityContext mockTaskbarActivityContext;
 
     private TaskbarNavButtonController mNavButtonController;
 
@@ -50,6 +66,10 @@
         MockitoAnnotations.initMocks(this);
         when(mockService.getDisplayId()).thenReturn(DISPLAY_ID);
         when(mockService.getOverviewCommandHelper()).thenReturn(mockCommandHelper);
+        when(mockStatsLogManager.logger()).thenReturn(mockStatsLogger);
+        when(mockTaskbarControllers.getTaskbarActivityContext())
+                .thenReturn(mockTaskbarActivityContext);
+        doReturn(mockStatsLogManager).when(mockTaskbarActivityContext).getStatsLogManager();
         mNavButtonController = new TaskbarNavButtonController(mockService,
                 mockSystemUiProxy, mockHandler);
     }
@@ -156,4 +176,49 @@
         mNavButtonController.onButtonLongClick(BUTTON_HOME);
         verify(mockSystemUiProxy, times(0)).startAssistant(any());
     }
+
+    @Test
+    public void testNoCallsToNullLogger() {
+        mNavButtonController.onButtonClick(BUTTON_HOME);
+        verify(mockStatsLogManager, times(0)).logger();
+        verify(mockStatsLogger, times(0)).log(any());
+    }
+
+    @Test
+    public void testNoCallsAfterNullingOut() {
+        mNavButtonController.init(mockTaskbarControllers);
+        mNavButtonController.onButtonClick(BUTTON_HOME);
+        mNavButtonController.onDestroy();
+        mNavButtonController.onButtonClick(BUTTON_HOME);
+        verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_HOME_BUTTON_TAP);
+        verify(mockStatsLogger, times(0)).log(LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS);
+    }
+
+    @Test
+    public void testLogOnTap() {
+        mNavButtonController.init(mockTaskbarControllers);
+        mNavButtonController.onButtonClick(BUTTON_HOME);
+        verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_HOME_BUTTON_TAP);
+        verify(mockStatsLogger, times(0)).log(LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS);
+    }
+
+    @Test
+    public void testLogOnLongpress() {
+        mNavButtonController.init(mockTaskbarControllers);
+        mNavButtonController.onButtonLongClick(BUTTON_HOME);
+        verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS);
+        verify(mockStatsLogger, times(0)).log(LAUNCHER_TASKBAR_HOME_BUTTON_TAP);
+    }
+
+    @Test
+    public void testBackOverviewLogOnLongpress() {
+        mNavButtonController.init(mockTaskbarControllers);
+        mNavButtonController.onButtonLongClick(BUTTON_RECENTS);
+        verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS);
+        verify(mockStatsLogger, times(0)).log(LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP);
+
+        mNavButtonController.onButtonLongClick(BUTTON_BACK);
+        verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_BACK_BUTTON_LONGPRESS);
+        verify(mockStatsLogger, times(0)).log(LAUNCHER_TASKBAR_BACK_BUTTON_TAP);
+    }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 9b38853..8a62c14 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -57,6 +57,7 @@
 import com.android.launcher3.testcomponent.TestCommandReceiver;
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.rule.FailureWatcher;
+import com.android.launcher3.util.rule.ScreenRecordRule;
 import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
 import com.android.quickstep.views.RecentsView;
 
@@ -78,6 +79,8 @@
 @RunWith(AndroidJUnit4.class)
 public class FallbackRecentsTest {
 
+    private static final String FALLBACK_LAUNCHER_TITLE = "Test launcher";
+
     private final UiDevice mDevice;
     private final LauncherInstrumentation mLauncher;
     private final ActivityInfo mOtherLauncherActivity;
@@ -91,6 +94,9 @@
     @Rule
     public final TestRule mOrderSensitiveRules;
 
+    @Rule
+    public ScreenRecordRule mScreenRecordRule = new ScreenRecordRule();
+
     public FallbackRecentsTest() throws RemoteException {
         Instrumentation instrumentation = getInstrumentation();
         Context context = instrumentation.getContext();
@@ -165,7 +171,7 @@
     public void goToOverviewFromHome() {
         mDevice.pressHome();
         assertTrue("Fallback Launcher not visible", mDevice.wait(Until.hasObject(By.pkg(
-                mOtherLauncherActivity.packageName)), WAIT_TIME_MS));
+                mOtherLauncherActivity.packageName).text(FALLBACK_LAUNCHER_TITLE)), WAIT_TIME_MS));
 
         mLauncher.getLaunchedAppState().switchToOverview();
     }
@@ -254,7 +260,7 @@
         // Test dismissing all tasks.
         pressHomeAndGoToOverview().dismissAllTasks();
         assertTrue("Fallback Launcher not visible", TestHelpers.wait(Until.hasObject(By.pkg(
-                mOtherLauncherActivity.packageName)), WAIT_TIME_MS));
+                mOtherLauncherActivity.packageName).text(FALLBACK_LAUNCHER_TITLE)), WAIT_TIME_MS));
     }
 
     private int getCurrentOverviewPage(RecentsActivity recents) {
diff --git a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
index 6b3f699..9e5d958 100644
--- a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
+++ b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
@@ -17,8 +17,6 @@
 
 package com.android.quickstep;
 
-import static android.view.Display.DEFAULT_DISPLAY;
-
 import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
 
 import static com.android.launcher3.util.DisplayController.NavigationMode.NO_BUTTON;
@@ -27,17 +25,16 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
-import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Point;
-import android.hardware.display.DisplayManager;
+import android.graphics.Rect;
+import android.util.ArrayMap;
 import android.util.DisplayMetrics;
+import android.util.Size;
 import android.view.Display;
 import android.view.MotionEvent;
 import android.view.Surface;
@@ -47,6 +44,10 @@
 
 import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.RotationUtils;
+import com.android.launcher3.util.WindowBounds;
+import com.android.launcher3.util.window.CachedDisplayInfo;
+import com.android.launcher3.util.window.WindowManagerProxy;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -56,18 +57,9 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class OrientationTouchTransformerTest {
-    static class ScreenSize {
-        int mHeight;
-        int mWidth;
 
-        ScreenSize(int height, int width) {
-            mHeight = height;
-            mWidth = width;
-        }
-    }
-
-    private static final ScreenSize NORMAL_SCREEN_SIZE = new ScreenSize(2280, 1080);
-    private static final ScreenSize LARGE_SCREEN_SIZE = new ScreenSize(3280, 1080);
+    private static final Size NORMAL_SCREEN_SIZE = new Size(1080, 2280);
+    private static final Size LARGE_SCREEN_SIZE = new Size(1080, 3280);
     private static final float DENSITY_DISPLAY_METRICS = 3.0f;
 
     private OrientationTouchTransformer mTouchTransformer;
@@ -75,7 +67,6 @@
     Resources mResources;
     private DisplayController.Info mInfo;
 
-
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -296,33 +287,24 @@
         assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion2.getX(), inRegion2.getY()));
     }
 
-    private DisplayController.Info createDisplayInfo(ScreenSize screenSize, int rotation) {
-        Context context = getApplicationContext();
-        Display display = spy(context.getSystemService(DisplayManager.class)
-                .getDisplay(DEFAULT_DISPLAY));
-
-        Point p = new Point(screenSize.mWidth, screenSize.mHeight);
-        if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
-            p.set(screenSize.mHeight, screenSize.mWidth);
-        }
-
-        doReturn(rotation).when(display).getRotation();
-        doAnswer(i -> {
-            ((Point) i.getArgument(0)).set(p.x, p.y);
-            return null;
-        }).when(display).getRealSize(any(Point.class));
-        doAnswer(i -> {
-            ((Point) i.getArgument(0)).set(p.x, p.y);
-            ((Point) i.getArgument(1)).set(p.x, p.y);
-            return null;
-        }).when(display).getCurrentSizeRange(any(Point.class), any(Point.class));
-        return new DisplayController.Info(context, display);
+    private DisplayController.Info createDisplayInfo(Size screenSize, int rotation) {
+        Point displaySize = new Point(screenSize.getWidth(), screenSize.getHeight());
+        RotationUtils.rotateSize(displaySize, rotation);
+        CachedDisplayInfo cdi = new CachedDisplayInfo(displaySize, rotation);
+        WindowBounds wm = new WindowBounds(
+                new Rect(0, 0, displaySize.x, displaySize.y),
+                new Rect());
+        WindowManagerProxy wmProxy = mock(WindowManagerProxy.class);
+        doReturn(cdi).when(wmProxy).getDisplayInfo(any(), any());
+        doReturn(wm).when(wmProxy).getRealBounds(any(), any(), any());
+        return new DisplayController.Info(
+                getApplicationContext(), mock(Display.class), wmProxy, new ArrayMap<>());
     }
 
-    private float generateTouchRegionHeight(ScreenSize screenSize, int rotation) {
-        float height = screenSize.mHeight;
+    private float generateTouchRegionHeight(Size screenSize, int rotation) {
+        float height = screenSize.getHeight();
         if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
-            height = screenSize.mWidth;
+            height = screenSize.getWidth();
         }
         return height - ResourceUtils.DEFAULT_NAVBAR_VALUE * DENSITY_DISPLAY_METRICS;
     }
diff --git a/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
index c6cdafc..7d414f4 100644
--- a/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
+++ b/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -164,7 +164,7 @@
                 }
 
                 WindowManagerProxy wmProxy = mock(WindowManagerProxy.class);
-                doReturn(cdi).when(wmProxy).getDisplayInfo(any());
+                doReturn(cdi).when(wmProxy).getDisplayInfo(any(), any());
                 doReturn(wm).when(wmProxy).getRealBounds(any(), any(), any());
 
                 ArrayMap<String, Pair<CachedDisplayInfo, WindowBounds[]>> perDisplayBoundsCache =
diff --git a/res/color-v31/overview_scrim.xml b/res/color-v31/overview_scrim.xml
index 8079995..212518f 100644
--- a/res/color-v31/overview_scrim.xml
+++ b/res/color-v31/overview_scrim.xml
@@ -14,5 +14,5 @@
      limitations under the License.
 -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-  <item android:color="@android:color/system_neutral2_500" android:lStar="87" />
+  <item android:color="@android:color/system_neutral2_200" />
 </selector>
diff --git a/res/color-v31/overview_scrim_dark.xml b/res/color-v31/overview_scrim_dark.xml
index 487b9f8..2ab8ecd 100644
--- a/res/color-v31/overview_scrim_dark.xml
+++ b/res/color-v31/overview_scrim_dark.xml
@@ -14,5 +14,5 @@
      limitations under the License.
 -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-  <item android:color="@android:color/system_neutral1_500" android:lStar="15" />
+  <item android:color="@android:color/system_neutral1_500" android:lStar="35" />
 </selector>
diff --git a/res/color-v31/taskbar_background.xml b/res/color-v31/taskbar_background.xml
new file mode 100644
index 0000000..eaf676f
--- /dev/null
+++ b/res/color-v31/taskbar_background.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:color="@android:color/system_neutral1_500" android:lStar="15" />
+</selector>
diff --git a/res/layout/widgets_full_sheet.xml b/res/layout/widgets_full_sheet.xml
index 505ecb1..e867405 100644
--- a/res/layout/widgets_full_sheet.xml
+++ b/res/layout/widgets_full_sheet.xml
@@ -43,7 +43,6 @@
             android:layout_height="match_parent"
             android:gravity="center"
             android:visibility="gone"
-            android:fontFamily="sans-serif-medium"
             android:textSize="20sp"
             android:layout_below="@id/search_and_recommendations_container"
             tools:text="No widgets available" />
diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml
index 422240c..652a61c 100644
--- a/res/values-land/dimens.xml
+++ b/res/values-land/dimens.xml
@@ -37,7 +37,4 @@
     <dimen name="drop_target_button_drawable_vertical_padding">2dp</dimen>
     <dimen name="drop_target_top_margin">6dp</dimen>
     <dimen name="drop_target_bottom_margin">6dp</dimen>
-
-    <!-- Workspace grid visualization parameters -->
-    <dimen name="grid_visualization_horizontal_cell_spacing">24dp</dimen>
 </resources>
diff --git a/res/values-sw600dp-land/dimens.xml b/res/values-sw600dp-land/dimens.xml
index daca048..dce09e3 100644
--- a/res/values-sw600dp-land/dimens.xml
+++ b/res/values-sw600dp-land/dimens.xml
@@ -19,14 +19,14 @@
 <!-- Hotseat -->
     <dimen name="spring_loaded_hotseat_top_margin">44dp</dimen>
 
-<!-- Dynamic grid -->
-    <dimen name="dynamic_grid_edge_margin">11.33dp</dimen>
-    <dimen name="cell_layout_padding">11.33dp</dimen>
-
 <!-- Dragging -->
     <dimen name="drop_target_top_margin">0dp</dimen>
     <dimen name="drop_target_bottom_margin">16dp</dimen>
 
+<!-- Dynamic grid -->
+    <dimen name="dynamic_grid_edge_margin">11.33dp</dimen>
+    <dimen name="cell_layout_padding">11.33dp</dimen>
+
 <!-- AllApps -->
     <dimen name="all_apps_bottom_sheet_horizontal_padding">52dp</dimen>
 </resources>
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
index 73edc6f..27b13d7 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -36,7 +36,6 @@
     <dimen name="dynamic_grid_icon_drawable_padding">7dp</dimen>
     <dimen name="cell_layout_padding">9dp</dimen>
 
-
 <!-- Hotseat -->
     <dimen name="dynamic_grid_hotseat_side_padding">0dp</dimen>
     <dimen name="spring_loaded_hotseat_top_margin">97dp</dimen>
@@ -47,7 +46,4 @@
     <dimen name="drop_target_button_drawable_horizontal_padding">16dp</dimen>
     <dimen name="drop_target_button_drawable_vertical_padding">16dp</dimen>
     <dimen name="dynamic_grid_drop_target_size">56dp</dimen>
-
-<!-- Workspace grid visualization parameters -->
-    <dimen name="grid_visualization_horizontal_cell_spacing">6dp</dimen>
 </resources>
diff --git a/res/values-sw720dp-land/dimens.xml b/res/values-sw720dp-land/dimens.xml
index eb5c751..439ea93 100644
--- a/res/values-sw720dp-land/dimens.xml
+++ b/res/values-sw720dp-land/dimens.xml
@@ -15,20 +15,20 @@
 -->
 
 <resources>
-<!-- Dragging-->
-    <dimen name="drop_target_top_margin">0dp</dimen>
-    <dimen name="drop_target_bottom_margin">32dp</dimen>
-
 <!-- Dynamic grid -->
     <dimen name="dynamic_grid_edge_margin">21.93dp</dimen>
     <dimen name="cell_layout_padding">29.33dp</dimen>
 
-<!-- Hotseat -->
-    <dimen name="spring_loaded_hotseat_top_margin">64dp</dimen>
-
 <!-- AllApps -->
     <dimen name="all_apps_bottom_sheet_horizontal_padding">32dp</dimen>
 
+<!-- Dragging-->
+    <dimen name="drop_target_top_margin">0dp</dimen>
+    <dimen name="drop_target_bottom_margin">32dp</dimen>
+
+<!-- Hotseat -->
+    <dimen name="spring_loaded_hotseat_top_margin">64dp</dimen>
+
 <!-- Widget picker-->
     <dimen name="widget_list_horizontal_margin">49dp</dimen>
 
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index c96a228..dd3e08b 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -258,14 +258,6 @@
         <attr name="allAppsCellHeightTwoPanelLandscape" format="float" />
         <!-- defaults to allAppsCellWidth, if not specified -->
         <attr name="allAppsCellWidthTwoPanelLandscape" format="float" />
-        <!-- defaults to borderSpace, if not specified -->
-        <attr name="allAppsBorderSpace" format="float" />
-        <!-- defaults to allAppsBorderSpace, if not specified -->
-        <attr name="allAppsBorderSpaceLandscape" format="float" />
-        <!-- defaults to allAppsBorderSpace, if not specified -->
-        <attr name="allAppsBorderSpaceTwoPanelPortrait" format="float" />
-        <!-- defaults to allAppsBorderSpace, if not specified -->
-        <attr name="allAppsBorderSpaceTwoPanelLandscape" format="float" />
         <!-- The following values are only enabled if grid is supported. -->
         <!-- defaults to iconImageSize, if not specified -->
         <attr name="allAppsIconSize" format="float" />
@@ -280,6 +272,40 @@
         <!-- defaults to allAppsIconTextSize, if not specified -->
         <attr name="allAppsIconTextSizeTwoPanelLandscape" format="float" />
 
+        <!-- defaults to borderSpace, if not specified -->
+        <!-- space to be used horizontally and vertically -->
+        <attr name="allAppsBorderSpace" format="float" />
+        <!-- space to the right of the cell, defaults to allAppsBorderSpace if not specified -->
+        <attr name="allAppsBorderSpaceHorizontal" format="float" />
+        <!-- space below the cell, defaults to allAppsBorderSpace if not specified -->
+        <attr name="allAppsBorderSpaceVertical" format="float" />
+        <!-- space to be used horizontally and vertically,
+        defaults to allAppsBorderSpace if not specified -->
+        <attr name="allAppsBorderSpaceLandscape" format="float" />
+        <!-- space to the right of the cell, defaults to allAppsBorderSpaceLandscape
+        if not specified -->
+        <attr name="allAppsBorderSpaceLandscapeHorizontal" format="float" />
+        <!-- space below the cell, defaults to allAppsBorderSpaceLandscape if not specified -->
+        <attr name="allAppsBorderSpaceLandscapeVertical" format="float" />
+        <!-- space to be used horizontally and vertically in two panels,
+        defaults to allAppsBorderSpace if not specified -->
+        <attr name="allAppsBorderSpaceTwoPanelPortrait" format="float" />
+        <!-- space to the right of the cell in two panels, defaults to
+        allAppsBorderSpaceTwoPanelPortrait if not specified -->
+        <attr name="allAppsBorderSpaceTwoPanelPortraitHorizontal" format="float" />
+        <!-- space below the cell in two panels, defaults to allAppsBorderSpaceTwoPanelPortrait
+        if not specified -->
+        <attr name="allAppsBorderSpaceTwoPanelPortraitVertical" format="float" />
+        <!-- space to be used horizontally and vertically in two panels,
+        defaults to allAppsBorderSpace if not specified -->
+        <attr name="allAppsBorderSpaceTwoPanelLandscape" format="float" />
+        <!-- space to the right of the cell in two panels, defaults to
+        allAppsBorderSpaceTwoPanelLandscape if not specified -->
+        <attr name="allAppsBorderSpaceTwoPanelLandscapeHorizontal" format="float" />
+        <!-- space below the cell in two panels, defaults to allAppsBorderSpaceTwoPanelLandscape
+        if not specified -->
+        <attr name="allAppsBorderSpaceTwoPanelLandscapeVertical" format="float" />
+
         <!-- defaults to borderSpaceDps, if not specified -->
         <attr name="hotseatBorderSpace" format="float" />
         <!-- defaults to hotseatBorderSpace, if not specified -->
@@ -316,6 +342,18 @@
         <attr name="horizontalMarginTwoPanelLandscape" format="float"/>
         <!-- defaults to horizontalMargin if not specified -->
         <attr name="horizontalMarginTwoPanelPortrait" format="float"/>
+
+        <!-- By default all are false -->
+        <attr name="inlineQsb" format="integer" >
+            <!-- Enable on landscape only -->
+            <flag name="portrait" value="1" />
+            <!-- Enable on portrait only -->
+            <flag name="landscape" value="2" />
+            <!-- Enable on two panel portrait only -->
+            <flag name="twoPanelPortrait" value="4" />
+            <!-- Enable on two panel landscape only -->
+            <flag name="twoPanelLandscape" value="8" />
+        </attr>
     </declare-styleable>
 
     <declare-styleable name="CellLayout">
diff --git a/res/values/config.xml b/res/values/config.xml
index 5e90bea..0ed2d85 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -88,6 +88,7 @@
     <string name="custom_activity_picker" translatable="false">
         com.android.customization.picker.CustomizationPickerActivity</string>
     <string name="local_colors_extraction_class" translatable="false"></string>
+    <string name="search_session_manager_class" translatable="false"></string>
 
     <!-- Accessibility actions -->
     <item type="id" name="action_remove" />
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index c1b9f01..b320bb2 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -109,6 +109,7 @@
     <dimen name="all_apps_header_pill_corner_radius">12dp</dimen>
     <dimen name="all_apps_header_tab_height">48dp</dimen>
     <dimen name="all_apps_tabs_indicator_height">2dp</dimen>
+    <dimen name="all_apps_header_top_margin">33dp</dimen>
     <dimen name="all_apps_header_top_padding">36dp</dimen>
     <dimen name="all_apps_header_bottom_padding">6dp</dimen>
     <dimen name="all_apps_work_profile_tab_footer_top_padding">16dp</dimen>
@@ -373,9 +374,8 @@
 
 
 <!-- Workspace grid visualization parameters -->
-    <dimen name="grid_visualization_rounding_radius">28dp</dimen>
-    <dimen name="grid_visualization_horizontal_cell_spacing">6dp</dimen>
-    <dimen name="grid_visualization_vertical_cell_spacing">6dp</dimen>
+    <dimen name="grid_visualization_rounding_radius">22dp</dimen>
+    <dimen name="grid_visualization_cell_spacing">6dp</dimen>
 
 <!-- Search results related parameters -->
     <dimen name="search_row_icon_size">48dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 267f9c3..f699fca 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -217,7 +217,7 @@
     <string name="disabled_app_label">Disabled <xliff:g id="app_name" example="Messenger">%1$s</xliff:g></string>
     <!-- The format string for when an app has a notification dot (meaning it has associated notifications). [ICU_FORMAT]-->
     <string name="dotted_app_label">
-        {count, plural, offset:1
+        {count, plural,
             =1      {{app_name} has # notification}
             other   {{app_name} has # notifications}
         }
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index f7133c4..d235180 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -148,8 +148,7 @@
     private boolean mVisualizeDropLocation = true;
     private RectF mVisualizeGridRect = new RectF();
     private Paint mVisualizeGridPaint = new Paint();
-    private int mGridVisualizationPaddingX;
-    private int mGridVisualizationPaddingY;
+    private int mGridVisualizationPadding;
     private int mGridVisualizationRoundingRadius;
     private float mGridAlpha = 0f;
     private int mGridColor = 0;
@@ -261,10 +260,8 @@
         mBackground.setAlpha(0);
 
         mGridColor = Themes.getAttrColor(getContext(), R.attr.workspaceAccentColor);
-        mGridVisualizationPaddingX = res.getDimensionPixelSize(
-                R.dimen.grid_visualization_horizontal_cell_spacing);
-        mGridVisualizationPaddingY = res.getDimensionPixelSize(
-                R.dimen.grid_visualization_vertical_cell_spacing);
+        mGridVisualizationPadding =
+                res.getDimensionPixelSize(R.dimen.grid_visualization_cell_spacing);
         mGridVisualizationRoundingRadius =
                 res.getDimensionPixelSize(R.dimen.grid_visualization_rounding_radius);
         mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * deviceProfile.iconSizePx);
@@ -594,8 +591,8 @@
 
     protected void visualizeGrid(Canvas canvas) {
         DeviceProfile dp = mActivity.getDeviceProfile();
-        int paddingX = Math.min((mCellWidth - dp.iconSizePx) / 2, mGridVisualizationPaddingX);
-        int paddingY = Math.min((mCellHeight - dp.iconSizePx) / 2, mGridVisualizationPaddingY);
+        int paddingX = (int) Math.min((mCellWidth - dp.iconSizePx) / 2, mGridVisualizationPadding);
+        int paddingY = (int) Math.min((mCellHeight - dp.iconSizePx) / 2, mGridVisualizationPadding);
         mVisualizeGridRect.set(paddingX, paddingY,
                 mCellWidth - paddingX,
                 mCellHeight - paddingY);
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 0c33bce..500244c 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -16,6 +16,10 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.InvariantDeviceProfile.INDEX_DEFAULT;
+import static com.android.launcher3.InvariantDeviceProfile.INDEX_LANDSCAPE;
+import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_LANDSCAPE;
+import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_PORTRAIT;
 import static com.android.launcher3.ResourceUtils.pxFromDp;
 import static com.android.launcher3.Utilities.dpiFromPx;
 import static com.android.launcher3.Utilities.pxFromSp;
@@ -58,7 +62,6 @@
 
     // Device properties
     public final boolean isTablet;
-    public final boolean isLargeTablet;
     public final boolean isPhone;
     public final boolean transposeLayoutWithOrientation;
     public final boolean isTwoPanels;
@@ -253,7 +256,6 @@
         // Determine device posture.
         mInfo = info;
         isTablet = info.isTablet(windowBounds);
-        isLargeTablet = info.isLargeTablet(windowBounds);
         isPhone = !isTablet;
         isTwoPanels = isTablet && useTwoPanels;
         isTaskbarPresent = isTablet && ApiWrapper.TASKBAR_DRAWN_IN_PROCESS;
@@ -278,15 +280,15 @@
 
         if (isTwoPanels) {
             if (isLandscape) {
-                mTypeIndex = InvariantDeviceProfile.INDEX_TWO_PANEL_LANDSCAPE;
+                mTypeIndex = INDEX_TWO_PANEL_LANDSCAPE;
             } else {
-                mTypeIndex = InvariantDeviceProfile.INDEX_TWO_PANEL_PORTRAIT;
+                mTypeIndex = INDEX_TWO_PANEL_PORTRAIT;
             }
         } else {
             if (isLandscape) {
-                mTypeIndex = InvariantDeviceProfile.INDEX_LANDSCAPE;
+                mTypeIndex = INDEX_LANDSCAPE;
             } else {
-                mTypeIndex = InvariantDeviceProfile.INDEX_DEFAULT;
+                mTypeIndex = INDEX_DEFAULT;
             }
         }
 
@@ -319,7 +321,7 @@
                 pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].x, mMetrics),
                 pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].y, mMetrics));
         cellLayoutBorderSpaceOriginalPx = new Point(cellLayoutBorderSpacePx);
-        folderCellLayoutBorderSpaceOriginalPx = pxFromDp(inv.folderBorderSpace, mMetrics, 1f);
+        folderCellLayoutBorderSpaceOriginalPx = pxFromDp(inv.folderBorderSpace, mMetrics);
         folderCellLayoutBorderSpacePx = new Point(folderCellLayoutBorderSpaceOriginalPx,
                 folderCellLayoutBorderSpaceOriginalPx);
 
@@ -348,9 +350,12 @@
         workspaceCellPaddingXPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_x);
 
         hotseatQsbHeight = res.getDimensionPixelSize(R.dimen.qsb_widget_height);
-        // Whether QSB might be inline in appropriate orientation (landscape).
-        boolean canQsbInline = isLargeTablet && hotseatQsbHeight > 0;
-        isQsbInline = canQsbInline && isLandscape;
+        // Whether QSB might be inline in appropriate orientation (e.g. landscape).
+        boolean canQsbInline = (isTwoPanels ? inv.inlineQsb[INDEX_TWO_PANEL_PORTRAIT]
+                || inv.inlineQsb[INDEX_TWO_PANEL_LANDSCAPE]
+                : inv.inlineQsb[INDEX_DEFAULT] || inv.inlineQsb[INDEX_LANDSCAPE])
+                && hotseatQsbHeight > 0;
+        isQsbInline = inv.inlineQsb[mTypeIndex] && canQsbInline;
 
         // We shrink hotseat sizes regardless of orientation, if nav buttons are inline and QSB
         // might be inline in either orientations, to keep hotseat size consistent across rotation.
@@ -388,7 +393,7 @@
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size);
         hotseatBorderSpace = pxFromDp(inv.hotseatBorderSpaces[mTypeIndex], mMetrics);
         updateHotseatIconSize(
-                pxFromDp(inv.iconSize[InvariantDeviceProfile.INDEX_DEFAULT], mMetrics));
+                pxFromDp(inv.iconSize[INDEX_DEFAULT], mMetrics));
 
         qsbBottomMarginOriginalPx = isScalableGrid
                 ? res.getDimensionPixelSize(R.dimen.scalable_grid_qsb_bottom_margin)
@@ -525,21 +530,21 @@
     }
 
     private Point getCellLayoutBorderSpace(InvariantDeviceProfile idp) {
+        return getCellLayoutBorderSpace(idp, 1f);
+
+    }
+
+    private Point getCellLayoutBorderSpace(InvariantDeviceProfile idp, float scale) {
         if (!isScalableGrid) {
             return new Point(0, 0);
         }
 
-        int horizontalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].x, mMetrics);
-        int verticalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].y, mMetrics);
+        int horizontalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].x, mMetrics, scale);
+        int verticalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].y, mMetrics, scale);
 
         return new Point(horizontalSpacePx, verticalSpacePx);
     }
 
-    private Point getCellLayoutBorderSpaceScaled(InvariantDeviceProfile idp, float scale) {
-        Point original = getCellLayoutBorderSpace(idp);
-        return new Point((int) (original.x * scale), (int) (original.y * scale));
-    }
-
     public Info getDisplayInfo() {
         return mInfo;
     }
@@ -708,7 +713,7 @@
         iconTextSizePx = (int) (pxFromSp(invIconTextSizeSp, mMetrics) * iconScale);
         iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * iconScale);
 
-        cellLayoutBorderSpacePx = getCellLayoutBorderSpaceScaled(inv, scale);
+        cellLayoutBorderSpacePx = getCellLayoutBorderSpace(inv, scale);
 
         if (isScalableGrid) {
             cellWidthPx = pxFromDp(inv.minCellSize[mTypeIndex].x, mMetrics, scale);
@@ -756,15 +761,22 @@
      * Updates the iconSize for allApps* variants.
      */
     public void updateAllAppsIconSize(float scale, Resources res) {
-        //TODO(b/218638090): remove the tablet condition once we have phone specs
-        if (isScalableGrid && isTablet) {
+        allAppsBorderSpacePx = new Point(
+                pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].x, mMetrics, scale),
+                pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].y, mMetrics, scale));
+        if (isScalableGrid) {
             allAppsIconSizePx =
                     pxFromDp(inv.allAppsIconSize[mTypeIndex], mMetrics);
             allAppsIconTextSizePx =
                     pxFromSp(inv.allAppsIconTextSize[mTypeIndex], mMetrics);
             allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx;
+            // AllApps cells don't have real space between cells,
+            // so we add the border space to the cell height
+            allAppsCellHeightPx = pxFromDp(inv.allAppsCellSize[mTypeIndex].y, mMetrics, scale)
+                    + allAppsBorderSpacePx.y;
+            // but width is just the cell,
+            // the border is added in #updateAllAppsContainerWidth
             allAppsCellWidthPx = pxFromDp(inv.allAppsCellSize[mTypeIndex].x, mMetrics, scale);
-            allAppsCellHeightPx = pxFromDp(inv.allAppsCellSize[mTypeIndex].y, mMetrics, scale);
         } else {
             float invIconSizeDp = inv.iconSize[mTypeIndex];
             float invIconTextSizeSp = inv.iconTextSize[mTypeIndex];
@@ -812,11 +824,11 @@
 
     private void updateFolderCellSize(float scale, Resources res) {
         float invIconSizeDp = isVerticalBarLayout()
-                ? inv.iconSize[InvariantDeviceProfile.INDEX_LANDSCAPE]
-                : inv.iconSize[InvariantDeviceProfile.INDEX_DEFAULT];
+                ? inv.iconSize[INDEX_LANDSCAPE]
+                : inv.iconSize[INDEX_DEFAULT];
         folderChildIconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mMetrics, scale));
         folderChildTextSizePx =
-                pxFromSp(inv.iconTextSize[InvariantDeviceProfile.INDEX_DEFAULT], mMetrics, scale);
+                pxFromSp(inv.iconTextSize[INDEX_DEFAULT], mMetrics, scale);
         folderLabelTextSizePx = (int) (folderChildTextSizePx * folderLabelTextScale);
 
         int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx);
@@ -1167,7 +1179,6 @@
         writer.println(prefix + "\t1 dp = " + mMetrics.density + " px");
 
         writer.println(prefix + "\tisTablet:" + isTablet);
-        writer.println(prefix + "\tisLargeTablet:" + isLargeTablet);
         writer.println(prefix + "\tisPhone:" + isPhone);
         writer.println(prefix + "\ttransposeLayoutWithOrientation:"
                 + transposeLayoutWithOrientation);
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 219ed9e..36c1797 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -167,6 +167,7 @@
     public String dbFile;
     public int defaultLayoutId;
     int demoModeLayoutId;
+    boolean[] inlineQsb = new boolean[COUNT_SIZES];
 
     /**
      * An immutable list of supported profiles.
@@ -250,6 +251,8 @@
                 COUNT_SIZES);
         System.arraycopy(defaultDisplayOption.borderSpaces, 0, result.borderSpaces, 0,
                 COUNT_SIZES);
+        System.arraycopy(defaultDisplayOption.inlineQsb, 0, result.inlineQsb, 0,
+                COUNT_SIZES);
 
         initGrid(context, myInfo, result, deviceType);
     }
@@ -371,6 +374,8 @@
             devicePaddings = new DevicePaddings(context, devicePaddingId);
         }
 
+        inlineQsb = displayOption.inlineQsb;
+
         // If the partner customization apk contains any grid overrides, apply them
         // Supported overrides: numRows, numColumns, iconSize
         applyPartnerDeviceProfileOverrides(context, metrics);
@@ -783,12 +788,18 @@
 
     @VisibleForTesting
     static final class DisplayOption {
+        private static final int INLINE_QSB_FOR_PORTRAIT = 1 << 0;
+        private static final int INLINE_QSB_FOR_LANDSCAPE = 1 << 1;
+        private static final int INLINE_QSB_FOR_TWO_PANEL_PORTRAIT = 1 << 2;
+        private static final int INLINE_QSB_FOR_TWO_PANEL_LANDSCAPE = 1 << 3;
+        private static final int DONT_INLINE_QSB = 0;
 
         public final GridOption grid;
 
         private final float minWidthDps;
         private final float minHeightDps;
         private final boolean canBeDefault;
+        private final boolean[] inlineQsb = new boolean[COUNT_SIZES];
 
         private final PointF[] minCellSize = new PointF[COUNT_SIZES];
 
@@ -815,6 +826,19 @@
 
             canBeDefault = a.getBoolean(R.styleable.ProfileDisplayOption_canBeDefault, false);
 
+            int inlineForRotation = a.getInt(R.styleable.ProfileDisplayOption_inlineQsb,
+                    DONT_INLINE_QSB);
+            inlineQsb[INDEX_DEFAULT] =
+                    (inlineForRotation & INLINE_QSB_FOR_PORTRAIT) == INLINE_QSB_FOR_PORTRAIT;
+            inlineQsb[INDEX_LANDSCAPE] =
+                    (inlineForRotation & INLINE_QSB_FOR_LANDSCAPE) == INLINE_QSB_FOR_LANDSCAPE;
+            inlineQsb[INDEX_TWO_PANEL_PORTRAIT] =
+                    (inlineForRotation & INLINE_QSB_FOR_TWO_PANEL_PORTRAIT)
+                            == INLINE_QSB_FOR_TWO_PANEL_PORTRAIT;
+            inlineQsb[INDEX_TWO_PANEL_LANDSCAPE] =
+                    (inlineForRotation & INLINE_QSB_FOR_TWO_PANEL_LANDSCAPE)
+                            == INLINE_QSB_FOR_TWO_PANEL_LANDSCAPE;
+
             float x;
             float y;
 
@@ -900,19 +924,44 @@
                     allAppsCellSize[INDEX_DEFAULT].y);
             allAppsCellSize[INDEX_TWO_PANEL_LANDSCAPE] = new PointF(x, y);
 
-            x = y = a.getFloat(R.styleable.ProfileDisplayOption_allAppsBorderSpace,
-                    borderSpace);
-            allAppsBorderSpaces[INDEX_DEFAULT] = new PointF(x, y);
-            x = y = a.getFloat(R.styleable.ProfileDisplayOption_allAppsBorderSpaceLandscape,
-                    allAppsBorderSpaces[INDEX_DEFAULT].x);
-            allAppsBorderSpaces[INDEX_LANDSCAPE] = new PointF(x, y);
-            x = y = a.getFloat(
+            float allAppsBorderSpace = a.getFloat(
+                    R.styleable.ProfileDisplayOption_allAppsBorderSpace, borderSpace);
+            float allAppsBorderSpaceLandscape = a.getFloat(
+                    R.styleable.ProfileDisplayOption_allAppsBorderSpaceLandscape,
+                    allAppsBorderSpace);
+            float allAppsBorderSpaceTwoPanelPortrait = a.getFloat(
                     R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelPortrait,
-                    allAppsBorderSpaces[INDEX_DEFAULT].x);
-            allAppsBorderSpaces[INDEX_TWO_PANEL_PORTRAIT] = new PointF(x, y);
-            x = y = a.getFloat(
+                    allAppsBorderSpace);
+            float allAppsBorderSpaceTwoPanelLandscape = a.getFloat(
                     R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelLandscape,
-                    allAppsBorderSpaces[INDEX_DEFAULT].x);
+                    allAppsBorderSpace);
+
+            x = a.getFloat(R.styleable.ProfileDisplayOption_allAppsBorderSpaceHorizontal,
+                    allAppsBorderSpace);
+            y = a.getFloat(R.styleable.ProfileDisplayOption_allAppsBorderSpaceVertical,
+                    allAppsBorderSpace);
+            allAppsBorderSpaces[INDEX_DEFAULT] = new PointF(x, y);
+
+            x = a.getFloat(R.styleable.ProfileDisplayOption_allAppsBorderSpaceLandscapeHorizontal,
+                    allAppsBorderSpaceLandscape);
+            y = a.getFloat(R.styleable.ProfileDisplayOption_allAppsBorderSpaceLandscapeVertical,
+                    allAppsBorderSpaceLandscape);
+            allAppsBorderSpaces[INDEX_LANDSCAPE] = new PointF(x, y);
+
+            x = a.getFloat(
+                    R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelPortraitHorizontal,
+                    allAppsBorderSpaceTwoPanelPortrait);
+            y = a.getFloat(
+                    R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelPortraitVertical,
+                    allAppsBorderSpaceTwoPanelPortrait);
+            allAppsBorderSpaces[INDEX_TWO_PANEL_PORTRAIT] = new PointF(x, y);
+
+            x = a.getFloat(
+                    R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelLandscapeHorizontal,
+                    allAppsBorderSpaceTwoPanelLandscape);
+            y = a.getFloat(
+                    R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelLandscapeVertical,
+                    allAppsBorderSpaceTwoPanelLandscape);
             allAppsBorderSpaces[INDEX_TWO_PANEL_LANDSCAPE] = new PointF(x, y);
 
             iconSizes[INDEX_DEFAULT] =
@@ -1004,6 +1053,7 @@
                 allAppsIconSizes[i] = 0;
                 allAppsIconTextSizes[i] = 0;
                 allAppsBorderSpaces[i] = new PointF();
+                inlineQsb[i] = false;
             }
         }
 
@@ -1046,6 +1096,7 @@
                 allAppsIconTextSizes[i] += p.allAppsIconTextSizes[i];
                 allAppsBorderSpaces[i].x += p.allAppsBorderSpaces[i].x;
                 allAppsBorderSpaces[i].y += p.allAppsBorderSpaces[i].y;
+                inlineQsb[i] |= p.inlineQsb[i];
             }
 
             folderBorderSpace += p.folderBorderSpace;
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index cd06414..5b037e4 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -8,6 +8,7 @@
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.DISMISS_PREDICTION;
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.RECONFIGURE;
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.UNINSTALL;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DISMISS_PREDICTION_UNDO;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_UNINSTALL;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_UNINSTALL_CANCELLED;
@@ -46,6 +47,7 @@
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.PendingRequestArgs;
+import com.android.launcher3.views.Snackbar;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 
 import java.net.URISyntaxException;
@@ -220,7 +222,8 @@
 
     @Override
     public void completeDrop(final DragObject d) {
-        ComponentName target = performDropAction(getViewUnderDrag(d.dragInfo), d.dragInfo);
+        ComponentName target = performDropAction(getViewUnderDrag(d.dragInfo), d.dragInfo,
+                d.logInstanceId);
         if (d.dragSource instanceof DeferredOnComplete) {
             DeferredOnComplete deferred = (DeferredOnComplete) d.dragSource;
             if (target != null) {
@@ -264,7 +267,7 @@
      * Performs the drop action and returns the target component for the dragObject or null if
      * the action was not performed.
      */
-    protected ComponentName performDropAction(View view, ItemInfo info) {
+    protected ComponentName performDropAction(View view, ItemInfo info, InstanceId instanceId) {
         if (mCurrentAccessibilityAction == RECONFIGURE) {
             int widgetId = getReconfigurableWidgetId(view);
             if (widgetId != INVALID_APPWIDGET_ID) {
@@ -276,7 +279,16 @@
             return null;
         }
         if (mCurrentAccessibilityAction == DISMISS_PREDICTION) {
-            // We sent the log event, nothing else left to do
+            if (FeatureFlags.ENABLE_DISMISS_PREDICTION_UNDO.get()) {
+                mLauncher.getDragLayer()
+                        .announceForAccessibility(getContext().getString(R.string.item_removed));
+                Snackbar.show(mLauncher, R.string.item_removed, R.string.undo, () -> { }, () -> {
+                    mStatsLogManager.logger()
+                            .withInstanceId(instanceId)
+                            .withItemInfo(info)
+                            .log(LAUNCHER_DISMISS_PREDICTION_UNDO);
+                });
+            }
             return null;
         }
         // else: mCurrentAccessibilityAction == UNINSTALL
@@ -303,8 +315,9 @@
 
     @Override
     public void onAccessibilityDrop(View view, ItemInfo item) {
-        doLog(new InstanceIdSequence().newInstanceId(), item);
-        performDropAction(view, item);
+        InstanceId instanceId = new InstanceIdSequence().newInstanceId();
+        doLog(instanceId, item);
+        performDropAction(view, item, instanceId);
     }
 
     /**
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 9bc3d15..972a2e4 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -608,6 +608,10 @@
                 LauncherFiles.DEVICE_PREFERENCES_KEY, Context.MODE_PRIVATE);
     }
 
+    public static boolean isWallpaperSupported(Context context) {
+        return context.getSystemService(WallpaperManager.class).isWallpaperSupported();
+    }
+
     public static boolean isWallpaperAllowed(Context context) {
         return context.getSystemService(WallpaperManager.class).isSetWallpaperAllowed();
     }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index a7fc2f5..ea9b69c 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -113,6 +113,7 @@
 import com.android.launcher3.widget.LauncherAppWidgetHost;
 import com.android.launcher3.widget.LauncherAppWidgetHost.ProviderChangedListener;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
+import com.android.launcher3.widget.NavigableAppWidgetHostView;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
 import com.android.launcher3.widget.PendingAppWidgetHostView;
@@ -2751,6 +2752,12 @@
                         info = ((AppInfo) info).makeWorkspaceItem();
                         d.dragInfo = info;
                     }
+                    if (info instanceof WorkspaceItemInfo
+                            && info.container == LauncherSettings.Favorites.CONTAINER_PREDICTION) {
+                        // Came from all apps prediction row -- make a copy
+                        info = new WorkspaceItemInfo((WorkspaceItemInfo) info);
+                        d.dragInfo = info;
+                    }
                     if (info instanceof SearchActionItemInfo) {
                         info = ((SearchActionItemInfo) info).createWorkspaceItem(
                                 mLauncher.getModel());
@@ -2831,7 +2838,8 @@
     }
 
     private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
-            DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell, boolean scale) {
+            DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell, boolean scale,
+            final View finalView) {
         // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
         // location and size on the home screen.
         int spanX = info.spanX;
@@ -2840,6 +2848,14 @@
         Rect r = estimateItemPosition(layout, targetCell[0], targetCell[1], spanX, spanY);
         if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) {
             DeviceProfile profile = mLauncher.getDeviceProfile();
+            if (profile.shouldInsetWidgets() && finalView instanceof NavigableAppWidgetHostView) {
+                Rect widgetPadding = new Rect();
+                ((NavigableAppWidgetHostView) finalView).getWidgetInset(profile, widgetPadding);
+                r.left -= widgetPadding.left;
+                r.right += widgetPadding.right;
+                r.top -= widgetPadding.top;
+                r.bottom += widgetPadding.bottom;
+            }
             Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
         }
 
@@ -2886,7 +2902,7 @@
         float scaleXY[] = new float[2];
         boolean scalePreview = !(info instanceof PendingAddShortcutInfo);
         getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell,
-                scalePreview);
+                scalePreview, finalView);
 
         Resources res = mLauncher.getResources();
         final int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 11e0a1f..e279f59 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -175,22 +175,27 @@
     @Override
     protected View replaceRVContainer(boolean showTabs) {
         View rvContainer = super.replaceRVContainer(showTabs);
+
+        removeCustomRules(rvContainer);
         if (FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get()) {
-            alignParentTop(rvContainer);
+            alignParentTop(rvContainer, showTabs);
             layoutAboveSearchContainer(rvContainer);
         } else {
-            layoutBelowSearchContainer(rvContainer);
+            layoutBelowSearchContainer(rvContainer, showTabs);
         }
+
         return rvContainer;
     }
 
     @Override
     void setupHeader() {
         super.setupHeader();
+
+        removeCustomRules(mHeader);
         if (FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get()) {
-            alignParentTop(mHeader);
+            alignParentTop(mHeader, false /* includeTabsMargin */);
         } else {
-            layoutBelowSearchContainer(mHeader);
+            layoutBelowSearchContainer(mHeader, false /* includeTabsMargin */);
         }
     }
 
@@ -226,31 +231,55 @@
         return super.getHeaderBottom() + mSearchContainer.getBottom();
     }
 
-    private void layoutBelowSearchContainer(View v) {
+    private void layoutBelowSearchContainer(View v, boolean includeTabsMargin) {
         if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams)) {
             return;
         }
+
         RelativeLayout.LayoutParams layoutParams = (LayoutParams) v.getLayoutParams();
-        layoutParams.removeRule(RelativeLayout.ALIGN_PARENT_TOP);
-        layoutParams.removeRule(RelativeLayout.ABOVE);
-        layoutParams.addRule(RelativeLayout.BELOW, R.id.search_container_all_apps);
+        layoutParams.addRule(RelativeLayout.ALIGN_TOP, R.id.search_container_all_apps);
+
+        int topMargin = getContext().getResources().getDimensionPixelSize(
+                R.dimen.all_apps_header_top_margin);
+        if (includeTabsMargin) {
+            topMargin = topMargin + getContext().getResources().getDimensionPixelSize(
+                    R.dimen.all_apps_header_pill_height);
+        }
+        layoutParams.topMargin = topMargin;
     }
 
     private void layoutAboveSearchContainer(View v) {
         if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams)) {
             return;
         }
+
         RelativeLayout.LayoutParams layoutParams = (LayoutParams) v.getLayoutParams();
         layoutParams.addRule(RelativeLayout.ABOVE, R.id.search_container_all_apps);
     }
 
-    private void alignParentTop(View v) {
+    private void alignParentTop(View v, boolean includeTabsMargin) {
         if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams)) {
             return;
         }
+
         RelativeLayout.LayoutParams layoutParams = (LayoutParams) v.getLayoutParams();
-        layoutParams.removeRule(RelativeLayout.BELOW);
         layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+        layoutParams.topMargin =
+                includeTabsMargin
+                        ? getContext().getResources().getDimensionPixelSize(
+                                R.dimen.all_apps_header_pill_height)
+                        : 0;
+    }
+
+    private void removeCustomRules(View v) {
+        if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams)) {
+            return;
+        }
+
+        RelativeLayout.LayoutParams layoutParams = (LayoutParams) v.getLayoutParams();
+        layoutParams.removeRule(RelativeLayout.ABOVE);
+        layoutParams.removeRule(RelativeLayout.ALIGN_TOP);
+        layoutParams.removeRule(RelativeLayout.ALIGN_PARENT_TOP);
     }
 
     @Override
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 7727fae..626e15c 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -261,6 +261,14 @@
     public static final BooleanFlag ENABLE_ONE_SEARCH_MOTION = new DeviceFlag(
             "ENABLE_ONE_SEARCH_MOTION", true, "Enables animations in OneSearch.");
 
+    public static final BooleanFlag USE_LOCAL_ICON_OVERRIDES = getDebugFlag(
+            "USE_LOCAL_ICON_OVERRIDES", true,
+            "Use inbuilt monochrome icons if app doesn't provide one");
+
+    public static final BooleanFlag ENABLE_DISMISS_PREDICTION_UNDO = getDebugFlag(
+            "ENABLE_DISMISS_PREDICTION_UNDO", false,
+            "Show an 'Undo' snackbar when users dismiss a predicted hotseat item");
+
     public static void initialize(Context context) {
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/icons/LauncherIconProvider.java b/src/com/android/launcher3/icons/LauncherIconProvider.java
index a7379bb..c4d5f2b 100644
--- a/src/com/android/launcher3/icons/LauncherIconProvider.java
+++ b/src/com/android/launcher3/icons/LauncherIconProvider.java
@@ -23,6 +23,7 @@
 import android.util.Log;
 
 import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.Themes;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -55,7 +56,8 @@
      */
     public void setIconThemeSupported(boolean isSupported) {
         mSupportsIconTheme = isSupported;
-        mThemedIconMap = isSupported ? null : DISABLED_MAP;
+        mThemedIconMap = isSupported && FeatureFlags.USE_LOCAL_ICON_OVERRIDES.get()
+                ? null : DISABLED_MAP;
     }
 
     @Override
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index e6dc21e..7e6f81c 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -552,7 +552,37 @@
 
         @UiEvent(doc = "User scrolled on one of the all apps surfaces such as A-Z list, search "
                 + "result page etc.")
-        LAUNCHER_ALLAPPS_SCROLLED(985);
+        LAUNCHER_ALLAPPS_SCROLLED(985),
+
+        @UiEvent(doc = "User tapped taskbar home button")
+        LAUNCHER_TASKBAR_HOME_BUTTON_TAP(1003),
+
+        @UiEvent(doc = "User tapped taskbar back button")
+        LAUNCHER_TASKBAR_BACK_BUTTON_TAP(1004),
+
+        @UiEvent(doc = "User tapped taskbar overview/recents button")
+        LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP(1005),
+
+        @UiEvent(doc = "User tapped taskbar IME switcher button")
+        LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP(1006),
+
+        @UiEvent(doc = "User tapped taskbar a11y button")
+        LAUNCHER_TASKBAR_A11Y_BUTTON_TAP(1007),
+
+        @UiEvent(doc = "User tapped taskbar home button")
+        LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS(1008),
+
+        @UiEvent(doc = "User tapped taskbar back button")
+        LAUNCHER_TASKBAR_BACK_BUTTON_LONGPRESS(1009),
+
+        @UiEvent(doc = "User tapped taskbar overview/recents button")
+        LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS(1010),
+
+        @UiEvent(doc = "User tapped taskbar a11y button")
+        LAUNCHER_TASKBAR_A11Y_BUTTON_LONGPRESS(1011),
+
+        @UiEvent(doc = "Show an 'Undo' snackbar when users dismiss a predicted hotseat item")
+        LAUNCHER_DISMISS_PREDICTION_UNDO(1035);
 
         // ADD MORE
 
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 91fb44e..ca91296 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -15,22 +15,17 @@
  */
 package com.android.launcher3.model;
 
-import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
-
 import android.content.Intent;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.os.UserHandle;
 import android.util.Log;
-import android.util.LongSparseArray;
 import android.util.Pair;
 
-import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel.CallbackTask;
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.data.AppInfo;
@@ -41,9 +36,7 @@
 import com.android.launcher3.pm.InstallSessionHelper;
 import com.android.launcher3.pm.PackageInstallInfo;
 import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.PackageManagerHelper;
 
 import java.util.ArrayList;
@@ -58,11 +51,23 @@
 
     private final List<Pair<ItemInfo, Object>> mItemList;
 
+    private final WorkspaceItemSpaceFinder mItemSpaceFinder;
+
     /**
      * @param itemList items to add on the workspace
      */
     public AddWorkspaceItemsTask(List<Pair<ItemInfo, Object>> itemList) {
+        this(itemList, new WorkspaceItemSpaceFinder());
+    }
+
+    /**
+     * @param itemList items to add on the workspace
+     * @param itemSpaceFinder inject WorkspaceItemSpaceFinder dependency for testing
+     */
+    public AddWorkspaceItemsTask(List<Pair<ItemInfo, Object>> itemList,
+            WorkspaceItemSpaceFinder itemSpaceFinder) {
         mItemList = itemList;
+        mItemSpaceFinder = itemSpaceFinder;
     }
 
     @Override
@@ -74,7 +79,7 @@
         final ArrayList<ItemInfo> addedItemsFinal = new ArrayList<>();
         final IntArray addedWorkspaceScreensFinal = new IntArray();
 
-        synchronized(dataModel) {
+        synchronized (dataModel) {
             IntArray workspaceScreens = dataModel.collectWorkspaceScreens();
 
             List<ItemInfo> filteredItems = new ArrayList<>();
@@ -117,7 +122,7 @@
 
             for (ItemInfo item : filteredItems) {
                 // Find appropriate space for the item.
-                int[] coords = findSpaceForItem(app, dataModel, workspaceScreens,
+                int[] coords = mItemSpaceFinder.findSpaceForItem(app, dataModel, workspaceScreens,
                         addedWorkspaceScreensFinal, item.spanX, item.spanY);
                 int screenId = coords[0];
 
@@ -288,82 +293,4 @@
         }
         return false;
     }
-
-    /**
-     * Find a position on the screen for the given size or adds a new screen.
-     * @return screenId and the coordinates for the item in an int array of size 3.
-     */
-    protected int[] findSpaceForItem( LauncherAppState app, BgDataModel dataModel,
-            IntArray workspaceScreens, IntArray addedWorkspaceScreensFinal, int spanX, int spanY) {
-        LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
-
-        // Use sBgItemsIdMap as all the items are already loaded.
-        synchronized (dataModel) {
-            for (ItemInfo info : dataModel.itemsIdMap) {
-                if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
-                    ArrayList<ItemInfo> items = screenItems.get(info.screenId);
-                    if (items == null) {
-                        items = new ArrayList<>();
-                        screenItems.put(info.screenId, items);
-                    }
-                    items.add(info);
-                }
-            }
-        }
-
-        // Find appropriate space for the item.
-        int screenId = 0;
-        int[] coordinates = new int[2];
-        boolean found = false;
-
-        int screenCount = workspaceScreens.size();
-        // First check the preferred screen.
-        IntSet screensToExclude = new IntSet();
-        if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
-            screensToExclude.add(FIRST_SCREEN_ID);
-        }
-
-        for (int screen = 0; screen < screenCount; screen++) {
-            screenId = workspaceScreens.get(screen);
-            if (!screensToExclude.contains(screenId) && findNextAvailableIconSpaceInScreen(
-                    app, screenItems.get(screenId), coordinates, spanX, spanY)) {
-                // We found a space for it
-                found = true;
-                break;
-            }
-        }
-
-        if (!found) {
-            // Still no position found. Add a new screen to the end.
-            screenId = LauncherSettings.Settings.call(app.getContext().getContentResolver(),
-                    LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
-                    .getInt(LauncherSettings.Settings.EXTRA_VALUE);
-
-            // Save the screen id for binding in the workspace
-            workspaceScreens.add(screenId);
-            addedWorkspaceScreensFinal.add(screenId);
-
-            // If we still can't find an empty space, then God help us all!!!
-            if (!findNextAvailableIconSpaceInScreen(
-                    app, screenItems.get(screenId), coordinates, spanX, spanY)) {
-                throw new RuntimeException("Can't find space to add the item");
-            }
-        }
-        return new int[] {screenId, coordinates[0], coordinates[1]};
-    }
-
-    private boolean findNextAvailableIconSpaceInScreen(
-            LauncherAppState app, ArrayList<ItemInfo> occupiedPos,
-            int[] xy, int spanX, int spanY) {
-        InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
-
-        GridOccupancy occupied = new GridOccupancy(profile.numColumns, profile.numRows);
-        if (occupiedPos != null) {
-            for (ItemInfo r : occupiedPos) {
-                occupied.markCells(r, true);
-            }
-        }
-        return occupied.findVacantCell(xy, spanX, spanY);
-    }
-
 }
diff --git a/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java b/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java
new file mode 100644
index 0000000..93fc6a5
--- /dev/null
+++ b/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2022 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 static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
+
+import android.util.LongSparseArray;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSet;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class to help find space for new workspace items
+ */
+public class WorkspaceItemSpaceFinder {
+
+    /**
+     * Find a position on the screen for the given size or adds a new screen.
+     *
+     * @return screenId and the coordinates for the item in an int array of size 3.
+     */
+    public int[] findSpaceForItem(LauncherAppState app, BgDataModel dataModel,
+            IntArray workspaceScreens, IntArray addedWorkspaceScreensFinal, int spanX, int spanY) {
+        LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
+
+        // Use sBgItemsIdMap as all the items are already loaded.
+        synchronized (dataModel) {
+            for (ItemInfo info : dataModel.itemsIdMap) {
+                if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                    ArrayList<ItemInfo> items = screenItems.get(info.screenId);
+                    if (items == null) {
+                        items = new ArrayList<>();
+                        screenItems.put(info.screenId, items);
+                    }
+                    items.add(info);
+                }
+            }
+        }
+
+        // Find appropriate space for the item.
+        int screenId = 0;
+        int[] coordinates = new int[2];
+        boolean found = false;
+
+        int screenCount = workspaceScreens.size();
+        // First check the preferred screen.
+        IntSet screensToExclude = new IntSet();
+        if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
+            screensToExclude.add(FIRST_SCREEN_ID);
+        }
+
+        for (int screen = 0; screen < screenCount; screen++) {
+            screenId = workspaceScreens.get(screen);
+            if (!screensToExclude.contains(screenId) && findNextAvailableIconSpaceInScreen(
+                    app, screenItems.get(screenId), coordinates, spanX, spanY)) {
+                // We found a space for it
+                found = true;
+                break;
+            }
+        }
+
+        if (!found) {
+            // Still no position found. Add a new screen to the end.
+            screenId = LauncherSettings.Settings.call(app.getContext().getContentResolver(),
+                    LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
+                    .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+
+            // Save the screen id for binding in the workspace
+            workspaceScreens.add(screenId);
+            addedWorkspaceScreensFinal.add(screenId);
+
+            // If we still can't find an empty space, then God help us all!!!
+            if (!findNextAvailableIconSpaceInScreen(
+                    app, screenItems.get(screenId), coordinates, spanX, spanY)) {
+                throw new RuntimeException("Can't find space to add the item");
+            }
+        }
+        return new int[]{screenId, coordinates[0], coordinates[1]};
+    }
+
+    private boolean findNextAvailableIconSpaceInScreen(
+            LauncherAppState app, ArrayList<ItemInfo> occupiedPos,
+            int[] xy, int spanX, int spanY) {
+        InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
+
+        GridOccupancy occupied = new GridOccupancy(profile.numColumns, profile.numRows);
+        if (occupiedPos != null) {
+            for (ItemInfo r : occupiedPos) {
+                occupied.markCells(r, true);
+            }
+        }
+        return occupied.findVacantCell(xy, spanX, spanY);
+    }
+}
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 2fa7945..7692bbf 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -148,7 +148,6 @@
     public OnClickListener getItemClickListener() {
         return (view) -> {
             mActivityContext.getItemOnClickListener().onClick(view);
-            close(true);
         };
     }
 
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 88a4d66..cba5be5 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -454,8 +454,8 @@
 
     @Override
     public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
-            int parentWidth, int parentHeight,
-            StagedSplitBounds splitBoundsConfig, DeviceProfile dp) {
+            int parentWidth, int parentHeight, StagedSplitBounds splitBoundsConfig,
+            DeviceProfile dp, boolean isRtl) {
         int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
         int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
         int dividerBar = splitBoundsConfig.appsStackedVertically
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index 510a728..911f2b0 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -158,7 +158,7 @@
 
     void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
             int parentWidth, int parentHeight,
-            StagedSplitBounds splitBoundsConfig, DeviceProfile dp);
+            StagedSplitBounds splitBoundsConfig, DeviceProfile dp, boolean isRtl);
 
     // Overview TaskMenuView methods
     void setTaskIconParams(FrameLayout.LayoutParams iconParams,
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index d88656c..2ca236e 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -571,8 +571,8 @@
 
     @Override
     public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
-            int parentWidth, int parentHeight,
-            StagedSplitBounds splitBoundsConfig, DeviceProfile dp) {
+            int parentWidth, int parentHeight, StagedSplitBounds splitBoundsConfig,
+            DeviceProfile dp, boolean isRtl) {
         int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
         int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
         int dividerBar = splitBoundsConfig.appsStackedVertically
@@ -591,7 +591,13 @@
             secondarySnapshotHeight = totalThumbnailHeight;
             secondarySnapshotWidth = parentWidth - primarySnapshotWidth - dividerBar;
             int translationX = primarySnapshotWidth + dividerBar;
-            secondarySnapshot.setTranslationX(translationX);
+            if (isRtl) {
+                primarySnapshot.setTranslationX(-translationX);
+                secondarySnapshot.setTranslationX(0);
+            } else {
+                secondarySnapshot.setTranslationX(translationX);
+                primarySnapshot.setTranslationX(0);
+            }
             secondarySnapshot.setTranslationY(spaceAboveSnapshot);
         } else {
             primarySnapshotWidth = parentWidth;
@@ -602,6 +608,7 @@
             int translationY = primarySnapshotHeight + spaceAboveSnapshot + dividerBar;
             secondarySnapshot.setTranslationY(translationY);
             secondarySnapshot.setTranslationX(0);
+            primarySnapshot.setTranslationX(0);
         }
         primarySnapshot.measure(
                 View.MeasureSpec.makeMeasureSpec(primarySnapshotWidth, View.MeasureSpec.EXACTLY),
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 8b4ff85..8005181 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -26,7 +26,6 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
-import static com.android.launcher3.util.window.WindowManagerProxy.MIN_LARGE_TABLET_WIDTH;
 import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH;
 
 import android.annotation.SuppressLint;
@@ -306,7 +305,7 @@
         public Info(Context context, Display display,
                 WindowManagerProxy wmProxy,
                 ArrayMap<String, Pair<CachedDisplayInfo, WindowBounds[]>> perDisplayBoundsCache) {
-            CachedDisplayInfo displayInfo = wmProxy.getDisplayInfo(display);
+            CachedDisplayInfo displayInfo = wmProxy.getDisplayInfo(context, display);
             rotation = displayInfo.rotation;
             currentSize = displayInfo.size;
             displayId = displayInfo.id;
@@ -349,13 +348,6 @@
         }
 
         /**
-         * Returns {@code true} if the bounds represent a large tablet.
-         */
-        public boolean isLargeTablet(WindowBounds bounds) {
-            return smallestSizeDp(bounds) >= MIN_LARGE_TABLET_WIDTH;
-        }
-
-        /**
          * Returns smallest size in dp for given bounds.
          */
         public float smallestSizeDp(WindowBounds bounds) {
@@ -372,7 +364,7 @@
         pw.println("  id=" + info.displayId);
         pw.println("  rotation=" + info.rotation);
         pw.println("  fontScale=" + info.fontScale);
-        pw.println("  densityDpi=" + info.displayId);
+        pw.println("  densityDpi=" + info.densityDpi);
         pw.println("  navigationMode=" + info.navigationMode.name());
         pw.println("  currentSize=" + info.currentSize);
         pw.println("  supportedBounds=" + info.supportedBounds);
diff --git a/src/com/android/launcher3/util/SplitConfigurationOptions.java b/src/com/android/launcher3/util/SplitConfigurationOptions.java
index b40493a..6a336cc 100644
--- a/src/com/android/launcher3/util/SplitConfigurationOptions.java
+++ b/src/com/android/launcher3/util/SplitConfigurationOptions.java
@@ -154,10 +154,12 @@
                 }
             }
 
-            leftTaskPercent = this.leftTopBounds.width() / (float) rightBottomBounds.right;
-            topTaskPercent = this.leftTopBounds.height() / (float) rightBottomBounds.bottom;
-            dividerWidthPercent = visualDividerBounds.width() / (float) rightBottomBounds.right;
-            dividerHeightPercent = visualDividerBounds.height() / (float) rightBottomBounds.bottom;
+            float totalWidth = rightBottomBounds.right - leftTopBounds.left;
+            float totalHeight = rightBottomBounds.bottom - leftTopBounds.top;
+            leftTaskPercent = leftTopBounds.width() / totalWidth;
+            topTaskPercent = leftTopBounds.height() / totalHeight;
+            dividerWidthPercent = visualDividerBounds.width() / totalWidth;
+            dividerHeightPercent = visualDividerBounds.height() / totalHeight;
         }
     }
 
diff --git a/src/com/android/launcher3/util/window/WindowManagerProxy.java b/src/com/android/launcher3/util/window/WindowManagerProxy.java
index ba3d981..5aaa275 100644
--- a/src/com/android/launcher3/util/window/WindowManagerProxy.java
+++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.util.window;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
 import static com.android.launcher3.ResourceUtils.INVALID_RESOURCE_HANDLE;
 import static com.android.launcher3.ResourceUtils.NAVBAR_HEIGHT;
 import static com.android.launcher3.ResourceUtils.NAVBAR_HEIGHT_LANDSCAPE;
@@ -47,7 +50,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.ResourceBasedOverride;
 import com.android.launcher3.util.WindowBounds;
@@ -58,7 +60,6 @@
 public class WindowManagerProxy implements ResourceBasedOverride {
 
     public static final int MIN_TABLET_WIDTH = 600;
-    public static final int MIN_LARGE_TABLET_WIDTH = 720;
 
     public static final MainThreadInitializedObject<WindowManagerProxy> INSTANCE =
             forOverride(WindowManagerProxy.class, R.string.window_manager_proxy_class);
@@ -91,7 +92,10 @@
         ArrayMap<String, Pair<CachedDisplayInfo, WindowBounds[]>> result = new ArrayMap<>();
         for (Display display : displays) {
             if (isInternalDisplay(display)) {
-                CachedDisplayInfo info = getDisplayInfo(display).normalize();
+                Context displayContext = Utilities.ATLEAST_S
+                        ? context.createWindowContext(display, TYPE_APPLICATION, null)
+                        : context.createDisplayContext(display);
+                CachedDisplayInfo info = getDisplayInfo(displayContext, display).normalize();
                 WindowBounds[] bounds = estimateWindowBounds(context, info);
                 result.put(info.id, Pair.create(info, bounds));
             }
@@ -122,7 +126,7 @@
         }
 
         WindowMetrics wm = windowContext.getSystemService(WindowManager.class)
-                .getCurrentWindowMetrics();
+                .getMaximumWindowMetrics();
 
         Rect insets = new Rect();
         normalizeWindowInsets(windowContext, wm.getWindowInsets(), insets);
@@ -161,6 +165,17 @@
         insetsBuilder.setInsets(WindowInsets.Type.navigationBars(), newNavInsets);
         insetsBuilder.setInsetsIgnoringVisibility(WindowInsets.Type.navigationBars(), newNavInsets);
 
+        Insets statusBarInsets = oldInsets.getInsets(WindowInsets.Type.statusBars());
+        int statusBarHeight = getDimenByName("status_bar_height", systemRes, 0);
+        Insets newStatusBarInsets = Insets.of(
+                statusBarInsets.left,
+                Math.max(statusBarInsets.top, statusBarHeight),
+                statusBarInsets.right,
+                statusBarInsets.bottom);
+        insetsBuilder.setInsets(WindowInsets.Type.statusBars(), newStatusBarInsets);
+        insetsBuilder.setInsetsIgnoringVisibility(
+                WindowInsets.Type.statusBars(), newStatusBarInsets);
+
         // Override the tappable insets to be 0 on the bottom for gesture nav (otherwise taskbar
         // would count towards it). This is used for the bottom protection in All Apps for example.
         if (isGesture) {
@@ -268,21 +283,23 @@
      * Returns a CachedDisplayInfo initialized for the current display
      */
     @TargetApi(Build.VERSION_CODES.S)
-    public CachedDisplayInfo getDisplayInfo(Display display) {
-        int rotation = display.getRotation();
-
-        Point size = new Point();
-        display.getRealSize(size);
-
+    public CachedDisplayInfo getDisplayInfo(Context displayContext, Display display) {
+        int rotation = getRotation(displayContext);
         Rect cutoutRect = new Rect();
+        Point size = new Point();
         if (Utilities.ATLEAST_S) {
-            DisplayCutout cutout = display.getCutout();
+            WindowMetrics wm = displayContext.getSystemService(WindowManager.class)
+                    .getMaximumWindowMetrics();
+            DisplayCutout cutout = wm.getWindowInsets().getDisplayCutout();
             if (cutout != null) {
                 cutoutRect.set(cutout.getSafeInsetLeft(), cutout.getSafeInsetTop(),
                         cutout.getSafeInsetRight(), cutout.getSafeInsetBottom());
             }
-        }
 
+            size.set(wm.getBounds().right, wm.getBounds().bottom);
+        } else {
+            display.getRealSize(size);
+        }
         return new CachedDisplayInfo(getDisplayId(display), size, rotation, cutoutRect);
     }
 
@@ -305,7 +322,9 @@
                 // Ignore
             }
         }
-        return d == null ? DisplayController.INSTANCE.get(context).getInfo().rotation
-                : d.getRotation();
+        if (d == null) {
+            d = context.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
+        }
+        return d.getRotation();
     }
 }
diff --git a/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt b/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
new file mode 100644
index 0000000..f91f1c4
--- /dev/null
+++ b/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2022 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
+
+import android.content.Context
+import android.graphics.PointF
+import androidx.test.core.app.ApplicationProvider
+import com.android.launcher3.util.DisplayController.Info
+import com.android.launcher3.util.WindowBounds
+import org.junit.Before
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when` as whenever
+
+abstract class DeviceProfileBaseTest {
+
+    protected var context: Context? = null
+    protected var inv: InvariantDeviceProfile? = null
+    protected var info: Info = mock(Info::class.java)
+    protected var windowBounds: WindowBounds? = null
+    protected var isMultiWindowMode: Boolean = false
+    protected var transposeLayoutWithOrientation: Boolean = false
+    protected var useTwoPanels: Boolean = false
+    protected var isGestureMode: Boolean = true
+
+    @Before
+    fun setUp() {
+        context = ApplicationProvider.getApplicationContext()
+        // make sure to reset values
+        useTwoPanels = false
+        isGestureMode = true
+    }
+
+    protected fun newDP(): DeviceProfile = DeviceProfile(
+        context,
+        inv,
+        info,
+        windowBounds,
+        isMultiWindowMode,
+        transposeLayoutWithOrientation,
+        useTwoPanels,
+        isGestureMode
+    )
+
+    protected fun initializeVarsForPhone(isLandscape: Boolean = false) {
+        val (x, y) = if (isLandscape)
+            Pair(3120, 1440)
+        else
+            Pair(1440, 3120)
+
+        windowBounds = WindowBounds(x, y, x, y - 100, 0)
+
+        whenever(info.isTablet(any())).thenReturn(false)
+
+        inv = newScalableInvariantDeviceProfile()
+    }
+
+    protected fun initializeVarsForTablet(isLandscape: Boolean = false) {
+        val (x, y) = if (isLandscape)
+            Pair(2560, 1600)
+        else
+            Pair(1600, 2560)
+
+        windowBounds = WindowBounds(x, y, x, y - 100, 0)
+
+        whenever(info.isTablet(any())).thenReturn(true)
+
+        inv = newScalableInvariantDeviceProfile()
+    }
+
+    /**
+     * A very generic grid, just to make qsb tests work. For real calculations, make sure to use
+     * values that better represent a real grid.
+     */
+    protected fun newScalableInvariantDeviceProfile(): InvariantDeviceProfile =
+        InvariantDeviceProfile().apply {
+            isScalable = true
+            numColumns = 4
+            numRows = 4
+            numShownHotseatIcons = 4
+            numDatabaseHotseatIcons = 6
+            numShrunkenHotseatIcons = 5
+            horizontalMargin = FloatArray(4) { 22f }
+            borderSpaces = listOf(
+                PointF(16f, 16f),
+                PointF(16f, 16f),
+                PointF(16f, 16f),
+                PointF(16f, 16f)
+            ).toTypedArray()
+            allAppsBorderSpaces = listOf(
+                PointF(16f, 16f),
+                PointF(16f, 16f),
+                PointF(16f, 16f),
+                PointF(16f, 16f)
+            ).toTypedArray()
+            hotseatBorderSpaces = FloatArray(4) { 16f }
+            iconSize = FloatArray(4) { 56f }
+            allAppsIconSize = FloatArray(4) { 56f }
+            iconTextSize = FloatArray(4) { 14f }
+            allAppsIconTextSize = FloatArray(4) { 14f }
+            minCellSize = listOf(
+                PointF(64f, 83f),
+                PointF(64f, 83f),
+                PointF(64f, 83f),
+                PointF(64f, 83f)
+            ).toTypedArray()
+            allAppsCellSize = listOf(
+                PointF(64f, 83f),
+                PointF(64f, 83f),
+                PointF(64f, 83f),
+                PointF(64f, 83f)
+            ).toTypedArray()
+            inlineQsb = booleanArrayOf(
+                false,
+                false,
+                false,
+                false
+            )
+        }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/DeviceProfileTest.kt b/tests/src/com/android/launcher3/DeviceProfileTest.kt
deleted file mode 100644
index d1e91ed..0000000
--- a/tests/src/com/android/launcher3/DeviceProfileTest.kt
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright (C) 2022 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
-
-import android.content.Context
-import android.graphics.PointF
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.launcher3.util.DisplayController.Info
-import com.android.launcher3.util.WindowBounds
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.*
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class DeviceProfileTest {
-
-    private var context: Context? = null
-    private var inv: InvariantDeviceProfile? = null
-    private var info: Info = mock(Info::class.java)
-    private var windowBounds: WindowBounds? = null
-    private var isMultiWindowMode: Boolean = false
-    private var transposeLayoutWithOrientation: Boolean = false
-    private var useTwoPanels: Boolean = false
-    private var isGestureMode: Boolean = true
-
-    @Before
-    fun setUp() {
-        context = ApplicationProvider.getApplicationContext()
-        // make sure to reset values
-        useTwoPanels = false
-    }
-
-    @Test
-    fun qsbWidth_is_match_parent_for_phones() {
-        initializeVarsForPhone()
-
-        val dp = DeviceProfile(
-            context,
-            inv,
-            info,
-            windowBounds,
-            isMultiWindowMode,
-            transposeLayoutWithOrientation,
-            useTwoPanels,
-            isGestureMode
-        )
-
-        assertThat(dp.isQsbInline).isFalse()
-        assertThat(dp.qsbWidth).isEqualTo(0)
-    }
-
-    @Test
-    fun qsbWidth_is_match_parent_for_tablet_portrait() {
-        initializeVarsForLargeTablet()
-
-        val dp = DeviceProfile(
-            context,
-            inv,
-            info,
-            windowBounds,
-            isMultiWindowMode,
-            transposeLayoutWithOrientation,
-            useTwoPanels,
-            isGestureMode
-        )
-
-        assertThat(dp.isQsbInline).isFalse()
-        assertThat(dp.qsbWidth).isEqualTo(0)
-    }
-
-    @Test
-    fun qsbWidth_has_size_for_large_tablet_landscape() {
-        initializeVarsForLargeTablet(true)
-
-        val dp = DeviceProfile(
-            context,
-            inv,
-            info,
-            windowBounds,
-            isMultiWindowMode,
-            transposeLayoutWithOrientation,
-            useTwoPanels,
-            isGestureMode
-        )
-
-        if (dp.hotseatQsbHeight > 0) {
-            assertThat(dp.isQsbInline).isTrue()
-            assertThat(dp.qsbWidth).isGreaterThan(0)
-        } else {
-            assertThat(dp.isQsbInline).isFalse()
-            assertThat(dp.qsbWidth).isEqualTo(0)
-        }
-    }
-
-    /**
-     * This test is to make sure that two panels don't inline the QSB as tablets do
-     */
-    @Test
-    fun qsbWidth_is_match_parent_for_small_two_panel_landscape() {
-        initializeVarsForSmallTablet(true)
-        useTwoPanels = true
-
-        val dp = DeviceProfile(
-            context,
-            inv,
-            info,
-            windowBounds,
-            isMultiWindowMode,
-            transposeLayoutWithOrientation,
-            useTwoPanels,
-            isGestureMode
-        )
-
-        assertThat(dp.isQsbInline).isFalse()
-        assertThat(dp.qsbWidth).isEqualTo(0)
-    }
-
-    private fun initializeVarsForPhone(isLandscape: Boolean = false) {
-        val (x, y) = if (isLandscape)
-            Pair(3120, 1440)
-        else
-            Pair(1440, 3120)
-
-        windowBounds = WindowBounds(x, y, x, y - 100, 0)
-
-        `when`(info.isTablet(any())).thenReturn(false)
-        `when`(info.isLargeTablet(any())).thenReturn(false)
-
-        scalableInvariantDeviceProfile()
-    }
-
-    private fun initializeVarsForSmallTablet(isLandscape: Boolean = false) {
-        val (x, y) = if (isLandscape)
-            Pair(2560, 1600)
-        else
-            Pair(1600, 2560)
-
-        windowBounds = WindowBounds(x, y, x, y - 100, 0)
-
-        `when`(info.isTablet(any())).thenReturn(true)
-        `when`(info.isLargeTablet(any())).thenReturn(false)
-
-        scalableInvariantDeviceProfile()
-    }
-
-    private fun initializeVarsForLargeTablet(isLandscape: Boolean = false) {
-        val (x, y) = if (isLandscape)
-            Pair(2560, 1600)
-        else
-            Pair(1600, 2560)
-
-        windowBounds = WindowBounds(x, y, x, y - 100, 0)
-
-        `when`(info.isTablet(any())).thenReturn(true)
-        `when`(info.isLargeTablet(any())).thenReturn(true)
-
-        scalableInvariantDeviceProfile()
-    }
-
-    /**
-     * A very generic grid, just to make qsb tests work. For real calculations, make sure to use
-     * values that better represent a real grid.
-     */
-    private fun scalableInvariantDeviceProfile() {
-        inv = InvariantDeviceProfile().apply {
-            isScalable = true
-            numColumns = 5
-            numRows = 5
-            horizontalMargin = FloatArray(4) { 22f }
-            borderSpaces = listOf(
-                PointF(16f, 16f),
-                PointF(16f, 16f),
-                PointF(16f, 16f),
-                PointF(16f, 16f)
-            ).toTypedArray()
-            allAppsBorderSpaces = listOf(
-                PointF(16f, 16f),
-                PointF(16f, 16f),
-                PointF(16f, 16f),
-                PointF(16f, 16f)
-            ).toTypedArray()
-            hotseatBorderSpaces = FloatArray(4) { 16f }
-            iconSize = FloatArray(4) { 56f }
-            allAppsIconSize = FloatArray(4) { 56f }
-            iconTextSize = FloatArray(4) { 14f }
-            allAppsIconTextSize = FloatArray(4) { 14f }
-            minCellSize = listOf(
-                PointF(64f, 83f),
-                PointF(64f, 83f),
-                PointF(64f, 83f),
-                PointF(64f, 83f)
-            ).toTypedArray()
-            allAppsCellSize = listOf(
-                PointF(64f, 83f),
-                PointF(64f, 83f),
-                PointF(64f, 83f),
-                PointF(64f, 83f)
-            ).toTypedArray()
-        }
-    }
-}
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/HotseatSizeTest.kt b/tests/src/com/android/launcher3/HotseatSizeTest.kt
new file mode 100644
index 0000000..a44939f
--- /dev/null
+++ b/tests/src/com/android/launcher3/HotseatSizeTest.kt
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2022 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
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.InvariantDeviceProfile.TYPE_MULTI_DISPLAY
+import com.android.launcher3.InvariantDeviceProfile.TYPE_PHONE
+import com.android.launcher3.InvariantDeviceProfile.TYPE_TABLET
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.`when` as whenever
+
+/**
+ * Test for [DeviceProfile]
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class HotseatSizeTest : DeviceProfileBaseTest() {
+
+    @Test
+    fun hotseat_size_is_normal_for_handhelds() {
+        initializeVarsForPhone()
+        inv = newScalableInvariantDeviceProfile().apply {
+            deviceType = TYPE_PHONE
+        }
+
+        val dp = newDP()
+
+        assertThat(dp.isQsbInline).isFalse()
+        assertThat(dp.numShownHotseatIcons).isEqualTo(4)
+    }
+
+    @Test
+    fun hotseat_size_is_max_when_large_screen() {
+        initializeVarsForTablet(isLandscape = true)
+        inv = newScalableInvariantDeviceProfile().apply {
+            deviceType = TYPE_MULTI_DISPLAY
+        }
+        useTwoPanels = true
+
+        val dp = newDP()
+
+        assertThat(dp.isQsbInline).isFalse()
+        assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+    }
+
+    @Test
+    fun hotseat_size_is_shrunk_if_needed_when_large_screen() {
+        initializeVarsForTablet(isLandscape = true)
+        inv = newScalableInvariantDeviceProfile().apply {
+            deviceType = TYPE_MULTI_DISPLAY
+            inlineQsb = booleanArrayOf(
+                false,
+                false,
+                false,
+                true // two panels landscape
+            )
+        }
+        useTwoPanels = true
+
+        isGestureMode = false
+        val dp = newDP()
+
+        if (dp.hotseatQsbHeight > 0) {
+            assertThat(dp.isQsbInline).isTrue()
+            assertThat(dp.numShownHotseatIcons).isEqualTo(5)
+        } else { // Launcher3 doesn't have QSB height
+            assertThat(dp.isQsbInline).isFalse()
+            assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+        }
+    }
+
+    /**
+     * For consistency, the hotseat should shrink if any orientation on the device type has an
+     * inline qsb
+     */
+    @Test
+    fun hotseat_size_is_shrunk_even_in_portrait_when_large_screen() {
+        initializeVarsForTablet()
+        inv = newScalableInvariantDeviceProfile().apply {
+            deviceType = TYPE_MULTI_DISPLAY
+            inlineQsb = booleanArrayOf(
+                false,
+                false,
+                false,
+                true // two panels landscape
+            )
+        }
+        useTwoPanels = true
+
+        isGestureMode = false
+        val dp = newDP()
+
+        if (dp.hotseatQsbHeight > 0) {
+            assertThat(dp.isQsbInline).isFalse()
+            assertThat(dp.numShownHotseatIcons).isEqualTo(5)
+        } else { // Launcher3 doesn't have QSB height
+            assertThat(dp.isQsbInline).isFalse()
+            assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+        }
+    }
+
+    @Test
+    fun hotseat_size_is_default_when_small_screen() {
+        initializeVarsForPhone()
+        inv = newScalableInvariantDeviceProfile().apply {
+            deviceType = TYPE_MULTI_DISPLAY
+        }
+        useTwoPanels = true
+
+        val dp = newDP()
+
+        assertThat(dp.numShownHotseatIcons).isEqualTo(4)
+    }
+
+    @Test
+    fun hotseat_size_is_not_shrunk_on_gesture_tablet() {
+        initializeVarsForTablet(isLandscape = true)
+        inv = newScalableInvariantDeviceProfile().apply {
+            deviceType = TYPE_TABLET
+            inlineQsb = booleanArrayOf(
+                    false,
+                    true, // landscape
+                    false,
+                    false
+            )
+            numShownHotseatIcons = 6
+        }
+
+        isGestureMode = true
+        val dp = newDP()
+
+        if (dp.hotseatQsbHeight > 0) {
+            assertThat(dp.isQsbInline).isTrue()
+            assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+        } else { // Launcher3 doesn't have QSB height
+            assertThat(dp.isQsbInline).isFalse()
+            assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+        }
+    }
+
+    @Test
+    fun hotseat_size_is_shrunk_if_needed_on_tablet() {
+        initializeVarsForTablet(isLandscape = true)
+        inv = newScalableInvariantDeviceProfile().apply {
+            deviceType = TYPE_TABLET
+            inlineQsb = booleanArrayOf(
+                false,
+                true, // landscape
+                false,
+                false
+            )
+            numShownHotseatIcons = 6
+        }
+
+        isGestureMode = false
+        val dp = newDP()
+
+        if (dp.hotseatQsbHeight > 0) {
+            assertThat(dp.isQsbInline).isTrue()
+            assertThat(dp.numShownHotseatIcons).isEqualTo(5)
+        } else { // Launcher3 doesn't have QSB height
+            assertThat(dp.isQsbInline).isFalse()
+            assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+        }
+    }
+
+    /**
+     * For consistency, the hotseat should shrink if any orientation on the device type has an
+     * inline qsb
+     */
+    @Test
+    fun hotseat_size_is_shrunk_even_in_portrait_on_tablet() {
+        initializeVarsForTablet()
+        inv = newScalableInvariantDeviceProfile().apply {
+            deviceType = TYPE_TABLET
+            inlineQsb = booleanArrayOf(
+                false,
+                true, // landscape
+                false,
+                false
+            )
+            numShownHotseatIcons = 6
+        }
+
+        isGestureMode = false
+        val dp = newDP()
+
+        if (dp.hotseatQsbHeight > 0) {
+            assertThat(dp.isQsbInline).isFalse()
+            assertThat(dp.numShownHotseatIcons).isEqualTo(5)
+        } else { // Launcher3 doesn't have QSB height
+            assertThat(dp.isQsbInline).isFalse()
+            assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/InlineQsbTest.kt b/tests/src/com/android/launcher3/InlineQsbTest.kt
new file mode 100644
index 0000000..e00dca8
--- /dev/null
+++ b/tests/src/com/android/launcher3/InlineQsbTest.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2022 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
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Test for [DeviceProfile]
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InlineQsbTest : DeviceProfileBaseTest() {
+
+    @Test
+    fun qsbWidth_is_match_parent_for_phones() {
+        initializeVarsForPhone()
+
+        val dp = newDP()
+
+        assertThat(dp.isQsbInline).isFalse()
+        assertThat(dp.qsbWidth).isEqualTo(0)
+    }
+
+    @Test
+    fun qsbWidth_is_match_parent_for_tablet_portrait() {
+        initializeVarsForTablet()
+        inv = newScalableInvariantDeviceProfile().apply {
+            inlineQsb = booleanArrayOf(
+                false,
+                true, // landscape
+                false,
+                false
+            )
+        }
+
+        val dp = DeviceProfile(
+            context,
+            inv,
+            info,
+            windowBounds,
+            isMultiWindowMode,
+            transposeLayoutWithOrientation,
+            useTwoPanels,
+            isGestureMode
+        )
+
+        assertThat(dp.isQsbInline).isFalse()
+        assertThat(dp.qsbWidth).isEqualTo(0)
+    }
+
+    @Test
+    fun qsbWidth_has_size_for_tablet_landscape() {
+        initializeVarsForTablet(isLandscape = true)
+        inv = newScalableInvariantDeviceProfile().apply {
+            inlineQsb = booleanArrayOf(
+                false,
+                true, // landscape
+                false,
+                false
+            )
+        }
+
+        val dp = newDP()
+
+        if (dp.hotseatQsbHeight > 0) {
+            assertThat(dp.isQsbInline).isTrue()
+            assertThat(dp.qsbWidth).isGreaterThan(0)
+        } else { // Launcher3 doesn't have QSB height
+            assertThat(dp.isQsbInline).isFalse()
+            assertThat(dp.qsbWidth).isEqualTo(0)
+        }
+    }
+
+    /**
+     * This test is to make sure that a tablet doesn't inline the QSB if the layout doesn't support
+     */
+    @Test
+    fun qsbWidth_is_match_parent_for_tablet_landscape_without_inline() {
+        initializeVarsForTablet(isLandscape = true)
+        useTwoPanels = true
+
+        val dp = newDP()
+
+        assertThat(dp.isQsbInline).isFalse()
+        assertThat(dp.qsbWidth).isEqualTo(0)
+    }
+
+}
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/model/AbstractWorkspaceModelTest.kt b/tests/src/com/android/launcher3/model/AbstractWorkspaceModelTest.kt
new file mode 100644
index 0000000..d26381d
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/AbstractWorkspaceModelTest.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2022 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.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.Rect
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherSettings
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.util.ContentWriter
+import com.android.launcher3.util.GridOccupancy
+import com.android.launcher3.util.IntArray
+import com.android.launcher3.util.IntSparseArrayMap
+import com.android.launcher3.util.LauncherModelHelper
+import java.util.UUID
+
+/**
+ * Base class for workspace related tests.
+ */
+abstract class AbstractWorkspaceModelTest {
+    companion object {
+        val emptyScreenSpaces = listOf(Rect(0, 0, 5, 5))
+        val fullScreenSpaces = emptyList<Rect>()
+        val nonEmptyScreenSpaces = listOf(Rect(1, 2, 3, 4))
+    }
+
+    protected lateinit var mTargetContext: Context
+    protected lateinit var mIdp: InvariantDeviceProfile
+    protected lateinit var mAppState: LauncherAppState
+    protected lateinit var mModelHelper: LauncherModelHelper
+    protected lateinit var mExistingScreens: IntArray
+    protected lateinit var mNewScreens: IntArray
+    protected lateinit var mScreenOccupancy: IntSparseArrayMap<GridOccupancy>
+
+    open fun setup() {
+        mModelHelper = LauncherModelHelper()
+        mTargetContext = mModelHelper.sandboxContext
+        mIdp = InvariantDeviceProfile.INSTANCE[mTargetContext]
+        mIdp.numRows = 5
+        mIdp.numColumns = mIdp.numRows
+        mAppState = LauncherAppState.getInstance(mTargetContext)
+        mExistingScreens = IntArray()
+        mScreenOccupancy = IntSparseArrayMap()
+        mNewScreens = IntArray()
+    }
+
+    open fun tearDown() {
+        mModelHelper.destroy()
+    }
+
+
+    /**
+     * Sets up workspaces with the given screen IDs with some items and a 2x2 space.
+     */
+    fun setupWorkspaces(screenIdsWithItems: List<Int>) {
+        var nextItemId = 1
+        screenIdsWithItems.forEach { screenId ->
+            nextItemId = setupWorkspace(nextItemId, screenId, nonEmptyScreenSpaces)
+        }
+    }
+
+    /**
+     * Sets up the given workspaces with the given spaces, and fills the remaining space with items.
+     */
+    fun setupWorkspacesWithSpaces(
+        screen0: List<Rect>? = null,
+        screen1: List<Rect>? = null,
+        screen2: List<Rect>? = null,
+        screen3: List<Rect>? = null,
+    ) = listOf(screen0, screen1, screen2, screen3)
+        .let(this::setupWithSpaces)
+
+    private fun setupWithSpaces(workspaceSpaces: List<List<Rect>?>) {
+        var nextItemId = 1
+        workspaceSpaces.forEachIndexed { screenId, spaces ->
+            if (spaces != null) {
+                nextItemId = setupWorkspace(nextItemId, screenId, spaces)
+            }
+        }
+    }
+
+    private fun setupWorkspace(startId: Int, screenId: Int, spaces: List<Rect>): Int {
+        return mModelHelper.executeSimpleTask { dataModel ->
+            writeWorkspaceWithSpaces(dataModel, startId, screenId, spaces)
+        }
+    }
+
+    private fun writeWorkspaceWithSpaces(
+        bgDataModel: BgDataModel,
+        itemStartId: Int,
+        screenId: Int,
+        spaces: List<Rect>,
+    ): Int {
+        var itemId = itemStartId
+        val occupancy = GridOccupancy(mIdp.numColumns, mIdp.numRows)
+        occupancy.markCells(0, 0, mIdp.numColumns, mIdp.numRows, true)
+        spaces.forEach { spaceRect ->
+            occupancy.markCells(spaceRect, false)
+        }
+        mExistingScreens.add(screenId)
+        mScreenOccupancy.append(screenId, occupancy)
+        for (x in 0 until mIdp.numColumns) {
+            for (y in 0 until mIdp.numRows) {
+                if (!occupancy.cells[x][y]) {
+                    continue
+                }
+                val info = getExistingItem()
+                info.id = itemId++
+                info.screenId = screenId
+                info.cellX = x
+                info.cellY = y
+                info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP
+                bgDataModel.addItem(mTargetContext, info, false)
+                val writer = ContentWriter(mTargetContext)
+                info.writeToValues(writer)
+                writer.put(LauncherSettings.Favorites._ID, info.id)
+                mTargetContext.contentResolver.insert(
+                    LauncherSettings.Favorites.CONTENT_URI,
+                    writer.getValues(mTargetContext)
+                )
+            }
+        }
+        return itemId
+    }
+
+    fun getExistingItem() = WorkspaceItemInfo()
+        .apply { intent = Intent().setComponent(ComponentName("a", "b")) }
+
+    fun getNewItem(): WorkspaceItemInfo {
+        val itemPackage = UUID.randomUUID().toString()
+        return WorkspaceItemInfo()
+            .apply { intent = Intent().setComponent(ComponentName(itemPackage, itemPackage)) }
+    }
+}
+
+data class NewItemSpace(
+    val screenId: Int,
+    val cellX: Int,
+    val cellY: Int
+) {
+    fun toIntArray() = intArrayOf(screenId, cellX, cellY)
+
+    companion object {
+        fun fromIntArray(array: kotlin.IntArray) = NewItemSpace(array[0], array[1], array[2])
+    }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
deleted file mode 100644
index 8a4590a..0000000
--- a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
+++ /dev/null
@@ -1,201 +0,0 @@
-package com.android.launcher3.model;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Rect;
-import android.util.Pair;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.model.BgDataModel.Callbacks;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.util.ContentWriter;
-import com.android.launcher3.util.Executors;
-import com.android.launcher3.util.GridOccupancy;
-import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.IntSparseArrayMap;
-import com.android.launcher3.util.LauncherModelHelper;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Tests for {@link AddWorkspaceItemsTask}
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class AddWorkspaceItemsTaskTest {
-
-    private final ComponentName mComponent1 = new ComponentName("a", "b");
-    private final ComponentName mComponent2 = new ComponentName("b", "b");
-
-    private Context mTargetContext;
-    private InvariantDeviceProfile mIdp;
-    private LauncherAppState mAppState;
-    private LauncherModelHelper mModelHelper;
-
-    private IntArray mExistingScreens;
-    private IntArray mNewScreens;
-    private IntSparseArrayMap<GridOccupancy> mScreenOccupancy;
-
-    @Before
-    public void setup() {
-        mModelHelper = new LauncherModelHelper();
-        mTargetContext = mModelHelper.sandboxContext;
-        mIdp = InvariantDeviceProfile.INSTANCE.get(mTargetContext);
-        mIdp.numColumns = mIdp.numRows = 5;
-        mAppState = LauncherAppState.getInstance(mTargetContext);
-
-        mExistingScreens = new IntArray();
-        mScreenOccupancy = new IntSparseArrayMap<>();
-        mNewScreens = new IntArray();
-    }
-
-    @After
-    public void tearDown() {
-        mModelHelper.destroy();
-    }
-
-    private AddWorkspaceItemsTask newTask(ItemInfo... items) {
-        List<Pair<ItemInfo, Object>> list = new ArrayList<>();
-        for (ItemInfo item : items) {
-            list.add(Pair.create(item, null));
-        }
-        return new AddWorkspaceItemsTask(list);
-    }
-
-    @Test
-    public void testFindSpaceForItem_prefers_second() throws Exception {
-        // First screen has only one hole of size 1
-        int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
-
-        // Second screen has 2 holes of sizes 3x2 and 2x3
-        setupWorkspaceWithHoles(nextId, 2, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
-
-        int[] spaceFound = newTask().findSpaceForItem(
-                mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 1, 1);
-        assertEquals(1, spaceFound[0]);
-        assertTrue(mScreenOccupancy.get(spaceFound[0])
-                .isRegionVacant(spaceFound[1], spaceFound[2], 1, 1));
-
-        // Find a larger space
-        spaceFound = newTask().findSpaceForItem(
-                mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 2, 3);
-        assertEquals(2, spaceFound[0]);
-        assertTrue(mScreenOccupancy.get(spaceFound[0])
-                .isRegionVacant(spaceFound[1], spaceFound[2], 2, 3));
-    }
-
-    @Test
-    public void testFindSpaceForItem_adds_new_screen() throws Exception {
-        // First screen has 2 holes of sizes 3x2 and 2x3
-        setupWorkspaceWithHoles(1, 1, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
-
-        IntArray oldScreens = mExistingScreens.clone();
-        int[] spaceFound = newTask().findSpaceForItem(
-                mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 3, 3);
-        assertFalse(oldScreens.contains(spaceFound[0]));
-        assertTrue(mNewScreens.contains(spaceFound[0]));
-    }
-
-    @Test
-    public void testAddItem_existing_item_ignored() throws Exception {
-        WorkspaceItemInfo info = new WorkspaceItemInfo();
-        info.intent = new Intent().setComponent(mComponent1);
-
-        // Setup a screen with a hole
-        setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
-
-        // Nothing was added
-        assertTrue(mModelHelper.executeTaskForTest(newTask(info)).isEmpty());
-    }
-
-    @Test
-    public void testAddItem_some_items_added() throws Exception {
-        Callbacks callbacks = mock(Callbacks.class);
-        Executors.MAIN_EXECUTOR.submit(() -> mModelHelper.getModel().addCallbacks(callbacks)).get();
-
-        WorkspaceItemInfo info = new WorkspaceItemInfo();
-        info.intent = new Intent().setComponent(mComponent1);
-
-        WorkspaceItemInfo info2 = new WorkspaceItemInfo();
-        info2.intent = new Intent().setComponent(mComponent2);
-
-        // Setup a screen with a hole
-        setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
-
-        mModelHelper.executeTaskForTest(newTask(info, info2)).get(0).run();
-        ArgumentCaptor<ArrayList> notAnimated = ArgumentCaptor.forClass(ArrayList.class);
-        ArgumentCaptor<ArrayList> animated = ArgumentCaptor.forClass(ArrayList.class);
-
-        // only info2 should be added because info was already added to the workspace
-        // in setupWorkspaceWithHoles()
-        verify(callbacks).bindAppsAdded(any(IntArray.class), notAnimated.capture(),
-                animated.capture());
-        assertTrue(notAnimated.getValue().isEmpty());
-
-        assertEquals(1, animated.getValue().size());
-        assertTrue(animated.getValue().contains(info2));
-    }
-
-    private int setupWorkspaceWithHoles(int startId, int screenId, Rect... holes) throws Exception {
-        return mModelHelper.executeSimpleTask(
-                model -> writeWorkspaceWithHoles(model, startId, screenId, holes));
-    }
-
-    private int writeWorkspaceWithHoles(
-            BgDataModel bgDataModel, int startId, int screenId, Rect... holes) {
-        GridOccupancy occupancy = new GridOccupancy(mIdp.numColumns, mIdp.numRows);
-        occupancy.markCells(0, 0, mIdp.numColumns, mIdp.numRows, true);
-        for (Rect r : holes) {
-            occupancy.markCells(r, false);
-        }
-
-        mExistingScreens.add(screenId);
-        mScreenOccupancy.append(screenId, occupancy);
-
-        for (int x = 0; x < mIdp.numColumns; x++) {
-            for (int y = 0; y < mIdp.numRows; y++) {
-                if (!occupancy.cells[x][y]) {
-                    continue;
-                }
-
-                WorkspaceItemInfo info = new WorkspaceItemInfo();
-                info.intent = new Intent().setComponent(mComponent1);
-                info.id = startId++;
-                info.screenId = screenId;
-                info.cellX = x;
-                info.cellY = y;
-                info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
-                bgDataModel.addItem(mTargetContext, info, false);
-
-                ContentWriter writer = new ContentWriter(mTargetContext);
-                info.writeToValues(writer);
-                writer.put(Favorites._ID, info.id);
-                mTargetContext.getContentResolver().insert(Favorites.CONTENT_URI,
-                        writer.getValues(mTargetContext));
-            }
-        }
-        return startId;
-    }
-}
diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
new file mode 100644
index 0000000..65d938b
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2022 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.util.Pair
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.util.Executors
+import com.android.launcher3.util.IntArray
+import com.android.launcher3.util.same
+import com.android.launcher3.util.eq
+import com.android.launcher3.util.any
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.times
+import org.mockito.Mockito.`when` as whenever
+
+/**
+ * Tests for [AddWorkspaceItemsTask]
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() {
+
+    @Captor
+    private lateinit var mAnimatedItemArgumentCaptor: ArgumentCaptor<ArrayList<ItemInfo>>
+
+    @Captor
+    private lateinit var mNotAnimatedItemArgumentCaptor: ArgumentCaptor<ArrayList<ItemInfo>>
+
+    @Mock
+    private lateinit var mDataModelCallbacks: BgDataModel.Callbacks
+
+    @Mock
+    private lateinit var mWorkspaceItemSpaceFinder: WorkspaceItemSpaceFinder
+
+
+    @Before
+    override fun setup() {
+        super.setup()
+        MockitoAnnotations.initMocks(this)
+        Executors.MAIN_EXECUTOR.submit { mModelHelper.model.addCallbacks(mDataModelCallbacks) }
+            .get()
+    }
+
+    @After
+    override fun tearDown() {
+        super.tearDown()
+    }
+
+    @Test
+    fun givenNewItemAndNonEmptyPages_whenExecuteTask_thenAddNewItem() {
+        val itemToAdd = getNewItem()
+        val nonEmptyScreenIds = listOf(0, 1, 2)
+        givenNewItemSpaces(NewItemSpace(1, 2, 2))
+
+        val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd)
+
+        assertThat(addedItems.size).isEqualTo(1)
+        assertThat(addedItems.first().itemInfo.screenId).isEqualTo(1)
+        assertThat(addedItems.first().isAnimated).isTrue()
+        verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 1)
+    }
+
+    @Test
+    fun givenNewAndExistingItems_whenExecuteTask_thenOnlyAddNewItem() {
+        val itemsToAdd = arrayOf(
+            getNewItem(),
+            getExistingItem()
+        )
+        givenNewItemSpaces(NewItemSpace(1, 0, 0))
+        val nonEmptyScreenIds = listOf(0)
+
+        val addedItems = testAddItems(nonEmptyScreenIds, *itemsToAdd)
+
+        assertThat(addedItems.size).isEqualTo(1)
+        assertThat(addedItems.first().itemInfo.screenId).isEqualTo(1)
+        assertThat(addedItems.first().isAnimated).isTrue()
+        verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 1)
+    }
+
+    @Test
+    fun givenOnlyExistingItem_whenExecuteTask_thenDoNotAddItem() {
+        val itemToAdd = getExistingItem()
+        givenNewItemSpaces(NewItemSpace(1, 0, 0))
+        val nonEmptyScreenIds = listOf(0)
+
+        val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd)
+
+        assertThat(addedItems.size).isEqualTo(0)
+        verifyZeroInteractions(mWorkspaceItemSpaceFinder, mDataModelCallbacks)
+    }
+
+    @Test
+    fun givenNonSequentialScreenIds_whenExecuteTask_thenReturnNewScreenId() {
+        val itemToAdd = getNewItem()
+        givenNewItemSpaces(NewItemSpace(2, 1, 3))
+        val nonEmptyScreenIds = listOf(0, 2, 3)
+
+        val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd)
+
+        assertThat(addedItems.size).isEqualTo(1)
+        assertThat(addedItems.first().itemInfo.screenId).isEqualTo(2)
+        assertThat(addedItems.first().isAnimated).isTrue()
+        verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 1)
+    }
+
+    @Test
+    fun givenMultipleItems_whenExecuteTask_thenAddThem() {
+        val itemsToAdd = arrayOf(
+            getNewItem(),
+            getExistingItem(),
+            getNewItem(),
+            getNewItem(),
+            getExistingItem(),
+        )
+        givenNewItemSpaces(
+            NewItemSpace(1, 3, 3),
+            NewItemSpace(2, 0, 0),
+            NewItemSpace(2, 0, 1),
+        )
+        val nonEmptyScreenIds = listOf(0, 1)
+
+        val addedItems = testAddItems(nonEmptyScreenIds, *itemsToAdd)
+
+        // Only the new items should be added
+        assertThat(addedItems.size).isEqualTo(3)
+
+        // Items that are added to the first screen should not be animated
+        val itemsAddedToFirstScreen = addedItems.filter { it.itemInfo.screenId == 1 }
+        assertThat(itemsAddedToFirstScreen.size).isEqualTo(1)
+        assertThat(itemsAddedToFirstScreen.first().isAnimated).isFalse()
+
+        // Items that are added to the second screen should be animated
+        val itemsAddedToSecondScreen = addedItems.filter { it.itemInfo.screenId == 2 }
+        assertThat(itemsAddedToSecondScreen.size).isEqualTo(2)
+        itemsAddedToSecondScreen.forEach {
+            assertThat(it.isAnimated).isTrue()
+        }
+        verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 3)
+    }
+
+    /**
+     * Sets up the item space data that will be returned from WorkspaceItemSpaceFinder.
+     */
+    private fun givenNewItemSpaces(vararg newItemSpaces: NewItemSpace) {
+        val spaceStack = newItemSpaces.toMutableList()
+        whenever(
+            mWorkspaceItemSpaceFinder.findSpaceForItem(
+                any(),
+                any(),
+                any(),
+                any(),
+                any(),
+                any()
+            )
+        )
+            .then { spaceStack.removeFirst().toIntArray() }
+    }
+
+    /**
+     * Verifies if WorkspaceItemSpaceFinder was called with proper arguments and how many times was
+     * it called.
+     */
+    private fun verifyItemSpaceFinderCall(
+        nonEmptyScreenIds: List<Int>,
+        numberOfExpectedCall: Int
+    ) {
+        verify(mWorkspaceItemSpaceFinder, times(numberOfExpectedCall))
+            .findSpaceForItem(
+                same(mAppState), same(mModelHelper.bgDataModel),
+                eq(IntArray.wrap(*nonEmptyScreenIds.toIntArray())), eq(IntArray()), eq(1), eq(1)
+            )
+    }
+
+    /**
+     * Sets up the workspaces with items, executes the task, collects the added items from the
+     * model callback then returns it.
+     */
+    private fun testAddItems(
+        nonEmptyScreenIds: List<Int>,
+        vararg itemsToAdd: WorkspaceItemInfo
+    ): List<AddedItem> {
+        setupWorkspaces(nonEmptyScreenIds)
+        val task = newTask(*itemsToAdd)
+        var updateCount = 0
+        mModelHelper.executeTaskForTest(task)
+            .forEach {
+                updateCount++
+                it.run()
+            }
+
+        val addedItems = mutableListOf<AddedItem>()
+        if (updateCount > 0) {
+            verify(mDataModelCallbacks).bindAppsAdded(
+                any(),
+                mNotAnimatedItemArgumentCaptor.capture(), mAnimatedItemArgumentCaptor.capture()
+            )
+            addedItems.addAll(mAnimatedItemArgumentCaptor.value.map { AddedItem(it, true) })
+            addedItems.addAll(mNotAnimatedItemArgumentCaptor.value.map { AddedItem(it, false) })
+
+        }
+
+        return addedItems
+    }
+
+    /**
+     * Creates the task with the given items and replaces the WorkspaceItemSpaceFinder dependency
+     * with a mock.
+     */
+    private fun newTask(vararg items: ItemInfo): AddWorkspaceItemsTask =
+        items.map { Pair.create(it, Any()) }
+            .toMutableList()
+            .let { AddWorkspaceItemsTask(it, mWorkspaceItemSpaceFinder) }
+}
+
+private data class AddedItem(
+    val itemInfo: ItemInfo,
+    val isAnimated: Boolean
+)
diff --git a/tests/src/com/android/launcher3/model/WorkspaceItemSpaceFinderTest.kt b/tests/src/com/android/launcher3/model/WorkspaceItemSpaceFinderTest.kt
new file mode 100644
index 0000000..bfb1ac6
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/WorkspaceItemSpaceFinderTest.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2022 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.graphics.Rect
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for [WorkspaceItemSpaceFinder]
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WorkspaceItemSpaceFinderTest : AbstractWorkspaceModelTest() {
+
+    private val mItemSpaceFinder = WorkspaceItemSpaceFinder()
+
+    @Before
+    override fun setup() {
+        super.setup()
+    }
+
+    @After
+    override fun tearDown() {
+        super.tearDown()
+    }
+
+    private fun findSpace(spanX: Int, spanY: Int): NewItemSpace =
+        mItemSpaceFinder.findSpaceForItem(
+            mAppState, mModelHelper.bgDataModel,
+            mExistingScreens, mNewScreens, spanX, spanY
+        )
+            .let { NewItemSpace.fromIntArray(it) }
+
+    private fun assertRegionVacant(newItemSpace: NewItemSpace, spanX: Int, spanY: Int) {
+        assertThat(
+            mScreenOccupancy[newItemSpace.screenId]
+                .isRegionVacant(newItemSpace.cellX, newItemSpace.cellY, spanX, spanY)
+        ).isTrue()
+    }
+
+    @Test
+    fun justEnoughSpaceOnFirstScreen_whenFindSpaceForItem_thenReturnFirstScreenId() {
+        setupWorkspacesWithSpaces(
+            // 3x2 space on screen 0, but it should be skipped
+            screen0 = listOf(Rect(2, 0, 5, 2)),
+            screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 space
+            //  2 spaces of sizes 3x2 and 2x3
+            screen2 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)),
+        )
+
+        val spaceFound = findSpace(1, 1)
+
+        assertThat(spaceFound.screenId).isEqualTo(1)
+        assertRegionVacant(spaceFound, 1, 1)
+    }
+
+    @Test
+    fun notEnoughSpaceOnFirstScreen_whenFindSpaceForItem_thenReturnSecondScreenId() {
+        setupWorkspacesWithSpaces(
+            // 3x2 space on screen 0, but it should be skipped
+            screen0 = listOf(Rect(2, 0, 5, 2)),
+            screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 space
+            //  2 spaces of sizes 3x2 and 2x3
+            screen2 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)),
+        )
+
+        // Find a larger space
+        val spaceFound = findSpace(2, 3)
+
+        assertThat(spaceFound.screenId).isEqualTo(2)
+        assertRegionVacant(spaceFound, 2, 3)
+    }
+
+    @Test
+    fun notEnoughSpaceOnExistingScreens_returnNewScreenId() {
+        setupWorkspacesWithSpaces(
+            // 3x2 space on screen 0, but it should be skipped
+            screen0 = listOf(Rect(2, 0, 5, 2)),
+            //  2 spaces of sizes 3x2 and 2x3
+            screen1 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)),
+            //  2 spaces of sizes 1x2 and 2x2
+            screen2 = listOf(Rect(1, 0, 2, 2), Rect(3, 2, 5, 4)),
+        )
+
+        val oldScreens = mExistingScreens.clone()
+        val spaceFound = findSpace(3, 3)
+
+        assertThat(oldScreens.contains(spaceFound.screenId)).isFalse()
+        assertThat(mNewScreens.contains(spaceFound.screenId)).isTrue()
+    }
+
+    @Test
+    fun firstScreenIsEmptyButSecondIsNotEmpty_returnSecondScreenId() {
+        setupWorkspacesWithSpaces(
+            // 3x2 space on screen 0, but it should be skipped
+            screen0 = listOf(Rect(2, 0, 5, 2)),
+            // empty screens are skipped
+            screen2 = listOf(Rect(2, 0, 5, 2)), // 3x2 space
+        )
+
+        val spaceFound = findSpace(2, 1)
+
+        assertThat(spaceFound.screenId).isEqualTo(2)
+        assertRegionVacant(spaceFound, 2, 1)
+    }
+
+    @Test
+    fun twoEmptyMiddleScreens_returnThirdScreen() {
+        setupWorkspacesWithSpaces(
+            // 3x2 space on screen 0, but it should be skipped
+            screen0 = listOf(Rect(2, 0, 5, 2)),
+            // empty screens are skipped
+            screen3 = listOf(Rect(1, 1, 4, 4)), // 3x3 space
+        )
+
+        val spaceFound = findSpace(2, 3)
+
+        assertThat(spaceFound.screenId).isEqualTo(3)
+        assertRegionVacant(spaceFound, 2, 3)
+    }
+
+    @Test
+    fun allExistingPagesAreFull_returnNewScreenId() {
+        setupWorkspacesWithSpaces(
+            // 3x2 space on screen 0, but it should be skipped
+            screen0 = listOf(Rect(2, 0, 5, 2)),
+            screen1 = fullScreenSpaces,
+            screen2 = fullScreenSpaces,
+        )
+
+        val spaceFound = findSpace(2, 3)
+
+        assertThat(spaceFound.screenId).isEqualTo(3)
+        assertThat(mNewScreens.contains(spaceFound.screenId)).isTrue()
+    }
+
+    @Test
+    fun firstTwoPagesAreFull_and_ThirdPageIsEmpty_returnThirdPage() {
+        setupWorkspacesWithSpaces(
+            // 3x2 space on screen 0, but it should be skipped
+            screen0 = listOf(Rect(2, 0, 5, 2)),
+            screen1 = fullScreenSpaces, // full screens are skipped
+            screen2 = fullScreenSpaces, // full screens are skipped
+            screen3 = emptyScreenSpaces
+        )
+
+        val spaceFound = findSpace(3, 1)
+
+        assertThat(spaceFound.screenId).isEqualTo(3)
+        assertRegionVacant(spaceFound, 3, 1)
+    }
+}
diff --git a/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
index d0daefc..f646b50 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
@@ -255,24 +255,24 @@
         Workspace workspace = mLauncher.getWorkspace();
 
         workspace.dragIcon(workspace.getWorkspaceAppIcon("Play Store"), 2);
-        workspace.dragIcon(workspace.getHotseatAppIcon("Camera"), 1);
+        workspace.dragIcon(workspace.getHotseatAppIcon("Chrome"), 1);
 
         executeOnLauncher(launcher -> {
             assertPagesExist(launcher, 0, 1, 2, 3);
             assertItemsOnPage(launcher, 0, "Maps");
             assertPageEmpty(launcher, 1);
             assertItemsOnPage(launcher, 2, "Play Store");
-            assertItemsOnPage(launcher, 3, "Camera");
+            assertItemsOnPage(launcher, 3, "Chrome");
         });
 
-        workspace.dragIcon(workspace.getWorkspaceAppIcon("Camera"), -1);
+        workspace.dragIcon(workspace.getWorkspaceAppIcon("Chrome"), -1);
         workspace.flingForward();
         workspace.dragIcon(workspace.getWorkspaceAppIcon("Play Store"), -2);
 
         executeOnLauncher(launcher -> {
             assertPagesExist(launcher, 0, 1);
             assertItemsOnPage(launcher, 0, "Play Store", "Maps");
-            assertItemsOnPage(launcher, 1, "Camera");
+            assertItemsOnPage(launcher, 1, "Chrome");
         });
     }
 
diff --git a/tests/src/com/android/launcher3/util/KotlinMockitoHelpers.kt b/tests/src/com/android/launcher3/util/KotlinMockitoHelpers.kt
new file mode 100644
index 0000000..57db13a
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/KotlinMockitoHelpers.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 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.util
+
+/**
+ * Kotlin versions of popular mockito methods that can return null in situations when Kotlin expects
+ * a non-null value. Kotlin will throw an IllegalStateException when this takes place ("x must not
+ * be null"). To fix this, we can use methods that modify the return type to be nullable. This
+ * causes Kotlin to skip the null checks.
+ */
+
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito
+
+/**
+ * Returns Mockito.eq() as nullable type to avoid java.lang.IllegalStateException when
+ * null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> eq(obj: T): T = Mockito.eq<T>(obj)
+
+/**
+ * Returns Mockito.same() as nullable type to avoid java.lang.IllegalStateException when
+ * null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> same(obj: T): T = Mockito.same<T>(obj)
+
+/**
+ * Returns Mockito.any() as nullable type to avoid java.lang.IllegalStateException when
+ * null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
+inline fun <reified T> any(): T = any(T::class.java)
+
+/**
+ * Kotlin type-inferred version of Mockito.nullable()
+ */
+inline fun <reified T> nullable(): T? = Mockito.nullable(T::class.java)
+
+/**
+ * Returns ArgumentCaptor.capture() as nullable type to avoid java.lang.IllegalStateException
+ * when null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+
+/**
+ * Helper function for creating an argumentCaptor in kotlin.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+inline fun <reified T : Any> argumentCaptor(): ArgumentCaptor<T> =
+    ArgumentCaptor.forClass(T::class.java)
+
+/**
+ * Helper function for creating new mocks, without the need to pass in a [Class] instance.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+inline fun <reified T : Any> mock(): T = Mockito.mock(T::class.java)
+
+/**
+ * A kotlin implemented wrapper of [ArgumentCaptor] which prevents the following exception when
+ * kotlin tests are mocking kotlin objects and the methods take non-null parameters:
+ *
+ *     java.lang.NullPointerException: capture() must not be null
+ */
+class KotlinArgumentCaptor<T> constructor(clazz: Class<T>) {
+    private val wrapped: ArgumentCaptor<T> = ArgumentCaptor.forClass(clazz)
+    fun capture(): T = wrapped.capture()
+    val value: T
+        get() = wrapped.value
+}
+
+/**
+ * Helper function for creating an argumentCaptor in kotlin.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+inline fun <reified T : Any> kotlinArgumentCaptor(): KotlinArgumentCaptor<T> =
+    KotlinArgumentCaptor(T::class.java)
+
+/**
+ * Helper function for creating and using a single-use ArgumentCaptor in kotlin.
+ *
+ *    val captor = argumentCaptor<Foo>()
+ *    verify(...).someMethod(captor.capture())
+ *    val captured = captor.value
+ *
+ * becomes:
+ *
+ *    val captured = withArgCaptor<Foo> { verify(...).someMethod(capture()) }
+ *
+ * NOTE: this uses the KotlinArgumentCaptor to avoid the NullPointerException.
+ */
+inline fun <reified T : Any> withArgCaptor(block: KotlinArgumentCaptor<T>.() -> Unit): T =
+    kotlinArgumentCaptor<T>().apply { block() }.value
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 8e0eb7b..84ef149 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -877,10 +877,9 @@
     }
 
     /**
+     * @return the Workspace object.
      * @deprecated use goHome().
      * Presses nav bar home button.
-     *
-     * @return the Workspace object.
      */
     @Deprecated
     public Workspace pressHome() {
@@ -1651,9 +1650,10 @@
     }
 
     Point getRealDisplaySize() {
-        final Point size = new Point();
-        getContext().getSystemService(WindowManager.class).getDefaultDisplay().getRealSize(size);
-        return size;
+        final Rect displayBounds = getContext().getSystemService(WindowManager.class)
+                .getMaximumWindowMetrics()
+                .getBounds();
+        return new Point(displayBounds.width(), displayBounds.height());
     }
 
     public void enableDebugTracing() {