Merge "Allow 5 hotseat icons in tablet 3 button" into tm-dev
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/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..06107b8 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;
 
@@ -606,16 +607,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/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index ff67b09..a38728a 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -174,7 +174,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 +372,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 +384,8 @@
         }
     }
 
-    private void initInputMonitor() {
-        disposeEventHandlers();
+    private void initInputMonitor(String reason) {
+        disposeEventHandlers("Initializing input monitor due to: " + reason);
 
         if (mDeviceState.isButtonNavMode()) {
             return;
@@ -401,7 +402,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 +521,7 @@
             mInputConsumer.unregisterInputConsumer();
             mOverviewComponentObserver.onDestroy();
         }
-        disposeEventHandlers();
+        disposeEventHandlers("TouchInteractionService onDestroy()");
         mDeviceState.destroy();
         SystemUiProxy.INSTANCE.get(this).clearProxy();
         ProtoTracer.INSTANCE.get(this).stop();
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/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/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/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/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/drawable/drop_target_frame.xml b/res/drawable/drop_target_frame.xml
index 9f04103..666a96e 100644
--- a/res/drawable/drop_target_frame.xml
+++ b/res/drawable/drop_target_frame.xml
@@ -17,6 +17,6 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="rectangle">
     <solid android:color="@android:color/transparent" />
-    <corners android:radius="80dp" />
+    <corners android:radius="28dp" />
     <stroke android:width="2dp" android:color="?attr/workspaceAccentColor" />
 </shape>
\ No newline at end of file
diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml
index 422240c..662b86e 100644
--- a/res/values-land/dimens.xml
+++ b/res/values-land/dimens.xml
@@ -22,22 +22,8 @@
     <dimen name="fastscroll_popup_text_size">24dp</dimen>
 
     <!-- Dynamic grid -->
-    <dimen name="dynamic_grid_edge_margin">15.28dp</dimen>
     <dimen name="dynamic_grid_icon_drawable_padding">4dp</dimen>
-    <dimen name="dynamic_grid_drop_target_size">36dp</dimen>
-    <dimen name="cell_layout_padding">20dp</dimen>
 
     <!-- Hotseat -->
     <dimen name="dynamic_grid_hotseat_side_padding">16dp</dimen>
-    <dimen name="spring_loaded_hotseat_top_margin">45dp</dimen>
-
-    <!-- Dragging -->
-    <dimen name="drop_target_button_gap">28dp</dimen>
-    <dimen name="drop_target_button_drawable_horizontal_padding">16dp</dimen>
-    <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
deleted file mode 100644
index daca048..0000000
--- a/res/values-sw600dp-land/dimens.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?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.
-  -->
-
-<resources>
-<!-- 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>
-
-<!-- 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..4a10e56 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -32,22 +32,8 @@
     <dimen name="fastscroll_popup_text_size">32dp</dimen>
 
 <!-- Dynamic grid -->
-    <dimen name="dynamic_grid_edge_margin">9dp</dimen>
     <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>
-
-<!-- Dragging -->
-    <dimen name="drop_target_top_margin">34dp</dimen>
-    <dimen name="drop_target_bottom_margin">16dp</dimen>
-    <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..9821526 100644
--- a/res/values-sw720dp-land/dimens.xml
+++ b/res/values-sw720dp-land/dimens.xml
@@ -15,23 +15,9 @@
 -->
 
 <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>
-
-<!-- Widget picker-->
+    <!-- Widget picker-->
     <dimen name="widget_list_horizontal_margin">49dp</dimen>
 
-<!-- Bottom sheet-->
+    <!-- Bottom sheet-->
     <dimen name="bottom_sheet_extra_top_padding">0dp</dimen>
 </resources>
diff --git a/res/values-sw720dp/dimens.xml b/res/values-sw720dp/dimens.xml
index 3ec211a..7ebc3f8 100644
--- a/res/values-sw720dp/dimens.xml
+++ b/res/values-sw720dp/dimens.xml
@@ -16,23 +16,7 @@
 
 <resources>
 <!-- AllApps -->
-    <dimen name="all_apps_bottom_sheet_horizontal_padding">28dp</dimen>
-
-<!-- Dynamic grid -->
-    <dimen name="dynamic_grid_edge_margin">27.59dp</dimen>
-    <dimen name="cell_layout_padding">36dp</dimen>
-
-<!-- Dragging -->
-    <dimen name="drop_target_text_size">20sp</dimen>
-    <dimen name="dynamic_grid_drop_target_size">72dp</dimen>
-    <dimen name="drop_target_button_drawable_horizontal_padding">24dp</dimen>
-    <dimen name="drop_target_button_drawable_vertical_padding">20dp</dimen>
-    <dimen name="drop_target_button_gap">32dp</dimen>
-    <dimen name="drop_target_top_margin">32dp</dimen>
-    <dimen name="drop_target_bottom_margin">32dp</dimen>
-
-<!-- Hotseat -->
-    <dimen name="spring_loaded_hotseat_top_margin">164dp</dimen>
+    <dimen name="all_apps_bottom_sheet_horizontal_padding">65dp</dimen>
 
 <!-- Widget picker-->
     <dimen name="widget_list_horizontal_margin">30dp</dimen>
diff --git a/res/values/config.xml b/res/values/config.xml
index 5e90bea..e2fd0e3 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -22,6 +22,9 @@
     <item type="id" name="drag_event_parity" />
 
     <!-- AllApps & Launcher transitions -->
+    <!-- Out of 100, the percent to shrink the workspace during spring loaded mode. -->
+    <integer name="config_workspaceSpringLoadShrinkPercentage">85</integer>
+
     <!-- The duration of the animation from search hint to text entry -->
     <integer name="config_searchHintAnimationDuration">50</integer>
 
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 2ee4c45..ce54565 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -19,14 +19,14 @@
     <dimen name="click_shadow_elevation">4dp</dimen>
 
     <!-- Dynamic Grid -->
-    <dimen name="dynamic_grid_edge_margin">10.77dp</dimen>
+    <dimen name="dynamic_grid_edge_margin">8dp</dimen>
     <dimen name="dynamic_grid_left_right_margin">8dp</dimen>
     <dimen name="dynamic_grid_icon_drawable_padding">7dp</dimen>
     <!-- Minimum space between workspace and hotseat in spring loaded mode -->
     <dimen name="dynamic_grid_min_spring_loaded_space">8dp</dimen>
 
     <dimen name="dynamic_grid_cell_border_spacing">16dp</dimen>
-    <dimen name="cell_layout_padding">10.77dp</dimen>
+    <dimen name="dynamic_grid_cell_layout_padding">5.5dp</dimen>
     <dimen name="dynamic_grid_cell_padding_x">8dp</dimen>
 
     <!-- Hotseat -->
@@ -34,7 +34,6 @@
     <dimen name="dynamic_grid_hotseat_bottom_padding">2dp</dimen>
     <dimen name="dynamic_grid_hotseat_bottom_tall_padding">0dp</dimen>
     <dimen name="inline_qsb_bottom_margin">0dp</dimen>
-    <dimen name="spring_loaded_hotseat_top_margin">76dp</dimen>
 
     <!-- Qsb -->
     <!-- Used for adjusting the position of QSB when placed in hotseat. This is a ratio and a higher
@@ -56,10 +55,8 @@
     <dimen name="workspace_page_indicator_overlap_workspace">0dp</dimen>
 
 <!-- Drop target bar -->
-    <dimen name="dynamic_grid_drop_target_size">56dp</dimen>
+    <dimen name="dynamic_grid_drop_target_size">52dp</dimen>
     <dimen name="drop_target_vertical_gap">20dp</dimen>
-    <dimen name="drop_target_top_margin">36dp</dimen>
-    <dimen name="drop_target_bottom_margin">16dp</dimen>
 
 <!-- App Widget resize frame -->
     <dimen name="widget_handle_margin">13dp</dimen>
@@ -210,9 +207,6 @@
     <dimen name="drop_target_shadow_elevation">2dp</dimen>
     <dimen name="drop_target_bar_margin_horizontal">4dp</dimen>
     <dimen name="drop_target_button_drawable_padding">8dp</dimen>
-    <dimen name="drop_target_button_drawable_horizontal_padding">16dp</dimen>
-    <dimen name="drop_target_button_drawable_vertical_padding">8dp</dimen>
-    <dimen name="drop_target_button_gap">22dp</dimen>
 
     <!-- the distance an icon must be dragged before button drop targets accept it -->
     <dimen name="drag_distanceThreshold">30dp</dimen>
@@ -367,9 +361,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/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 500244c..3489c31 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -97,16 +97,18 @@
     private static final float TALL_DEVICE_EXTRA_SPACE_THRESHOLD_DP = 252;
     private static final float TALL_DEVICE_MORE_EXTRA_SPACE_THRESHOLD_DP = 268;
 
+    // To evenly space the icons, increase the left/right margins for tablets in portrait mode.
+    private static final int PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER = 4;
+
     // Workspace
     public final int desiredWorkspaceHorizontalMarginOriginalPx;
     public int desiredWorkspaceHorizontalMarginPx;
     public Point cellLayoutBorderSpaceOriginalPx;
     public Point cellLayoutBorderSpacePx;
-    public Rect cellLayoutPaddingPx = new Rect();
-
+    public final int cellLayoutPaddingLeftRightPx;
+    public final int cellLayoutBottomPaddingPx;
     public final int edgeMarginPx;
-    public float workspaceSpringLoadShrunkTop;
-    public float workspaceSpringLoadShrunkBottom;
+    public float workspaceSpringLoadShrinkFactor;
     public final int workspaceSpringLoadedBottomSpace;
 
     private final int extraSpace;
@@ -162,7 +164,6 @@
     public int hotseatBarSizePx;
     public int hotseatBarTopPaddingPx;
     public final int hotseatBarBottomPaddingPx;
-    public int springLoadedHotseatBarTopMarginPx;
     // Start is the side next to the nav bar, end is the side next to the workspace
     public final int hotseatBarSidePaddingStartPx;
     public final int hotseatBarSidePaddingEndPx;
@@ -208,13 +209,8 @@
 
     // Drop Target
     public int dropTargetBarSizePx;
-    public int dropTargetBarTopMarginPx;
-    public int dropTargetBarBottomMarginPx;
     public int dropTargetDragPaddingPx;
     public int dropTargetTextSizePx;
-    public int dropTargetHorizontalPaddingPx;
-    public int dropTargetVerticalPaddingPx;
-    public int dropTargetGapPx;
 
     // Insets
     private final Rect mInsets = new Rect();
@@ -325,6 +321,23 @@
         folderCellLayoutBorderSpacePx = new Point(folderCellLayoutBorderSpaceOriginalPx,
                 folderCellLayoutBorderSpaceOriginalPx);
 
+        int cellLayoutPaddingLeftRightMultiplier = !isVerticalBarLayout() && isTablet
+                ? PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER : 1;
+        int cellLayoutPadding = isScalableGrid
+                ? 0
+                : res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_padding);
+
+        if (isTwoPanels) {
+            cellLayoutPaddingLeftRightPx = 0;
+            cellLayoutBottomPaddingPx = 0;
+        } else if (isLandscape) {
+            cellLayoutPaddingLeftRightPx = 0;
+            cellLayoutBottomPaddingPx = cellLayoutPadding;
+        } else {
+            cellLayoutPaddingLeftRightPx = cellLayoutPaddingLeftRightMultiplier * cellLayoutPadding;
+            cellLayoutBottomPaddingPx = 0;
+        }
+
         workspacePageIndicatorHeight = res.getDimensionPixelSize(
                 R.dimen.workspace_page_indicator_height);
         mWorkspacePageIndicatorOverlapWorkspace =
@@ -334,15 +347,8 @@
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
 
         dropTargetBarSizePx = res.getDimensionPixelSize(R.dimen.dynamic_grid_drop_target_size);
-        dropTargetBarTopMarginPx = res.getDimensionPixelSize(R.dimen.drop_target_top_margin);
-        dropTargetBarBottomMarginPx = res.getDimensionPixelSize(R.dimen.drop_target_bottom_margin);
         dropTargetDragPaddingPx = res.getDimensionPixelSize(R.dimen.drop_target_drag_padding);
         dropTargetTextSizePx = res.getDimensionPixelSize(R.dimen.drop_target_text_size);
-        dropTargetHorizontalPaddingPx = res.getDimensionPixelSize(
-                R.dimen.drop_target_button_drawable_horizontal_padding);
-        dropTargetVerticalPaddingPx = res.getDimensionPixelSize(
-                R.dimen.drop_target_button_drawable_vertical_padding);
-        dropTargetGapPx = res.getDimensionPixelSize(R.dimen.drop_target_button_gap);
 
         workspaceSpringLoadedBottomSpace =
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_min_spring_loaded_space);
@@ -383,8 +389,6 @@
                     + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
             qsbWidth = 0;
         }
-        springLoadedHotseatBarTopMarginPx = res.getDimensionPixelSize(
-                R.dimen.spring_loaded_hotseat_top_margin);
         hotseatBarSidePaddingEndPx =
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding);
         // Add a bit of space between nav bar and hotseat in vertical bar layout.
@@ -478,12 +482,6 @@
             // Recalculate the available dimensions using the new hotseat size.
             updateAvailableDimensions(res);
         }
-
-        int cellLayoutPadding =
-                isTwoPanels ? cellLayoutBorderSpacePx.x / 2 : res.getDimensionPixelSize(
-                        R.dimen.cell_layout_padding);
-        cellLayoutPaddingPx = new Rect(cellLayoutPadding, cellLayoutPadding, cellLayoutPadding,
-                cellLayoutPadding);
         updateWorkspacePadding();
 
         flingToDeleteThresholdVelocity = res.getDimensionPixelSize(
@@ -596,6 +594,7 @@
         float appWidgetScaleX = (float) profile.getCellSize().x / getCellSize().x;
         float appWidgetScaleY = (float) profile.getCellSize().y / getCellSize().y;
         profile.appWidgetScale.set(appWidgetScaleX, appWidgetScaleY);
+        profile.updateWorkspacePadding();
 
         return profile;
     }
@@ -630,19 +629,18 @@
     }
 
     private void updateAllAppsContainerWidth(Resources res) {
-        int cellLayoutHorizontalPadding =
-                (cellLayoutPaddingPx.left + cellLayoutPaddingPx.right) / 2;
+
         if (isTablet) {
             allAppsLeftRightPadding =
-                    res.getDimensionPixelSize(R.dimen.all_apps_bottom_sheet_horizontal_padding);
-
+                    res.getDimensionPixelSize(R.dimen.all_apps_bottom_sheet_horizontal_padding)
+                            + cellLayoutPaddingLeftRightPx;
             int usedWidth = (allAppsCellWidthPx * numShownAllAppsColumns)
                     + (allAppsBorderSpacePx.x * (numShownAllAppsColumns - 1))
                     + allAppsLeftRightPadding * 2;
             allAppsLeftRightMargin = Math.max(1, (availableWidthPx - usedWidth) / 2);
         } else {
             allAppsLeftRightPadding =
-                    desiredWorkspaceHorizontalMarginPx + cellLayoutHorizontalPadding;
+                    desiredWorkspaceHorizontalMarginPx + cellLayoutPaddingLeftRightPx;
         }
     }
 
@@ -652,12 +650,11 @@
     private int updateAvailableDimensions(Resources res) {
         updateIconSize(1f, res);
 
-        updateWorkspacePadding();
         Point workspacePadding = getTotalWorkspacePadding();
 
         // Check to see if the icons fit within the available height.
         float usedHeight = getCellLayoutHeight();
-        final int maxHeight = getWorkspaceHeight(workspacePadding);
+        final int maxHeight = availableHeightPx - workspacePadding.y;
         float extraHeight = Math.max(0, maxHeight - usedHeight);
         float scaleY = maxHeight / usedHeight;
         boolean shouldScale = scaleY < 1f;
@@ -667,7 +664,10 @@
             // We scale to fit the cellWidth and cellHeight in the available space.
             // The benefit of scalable grids is that we can get consistent aspect ratios between
             // devices.
-            float usedWidth = getCellLayoutWidth() + (desiredWorkspaceHorizontalMarginPx * 2);
+            int numColumns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns;
+            float usedWidth = (cellWidthPx * numColumns)
+                    + (cellLayoutBorderSpacePx.x * (numColumns - 1))
+                    + (desiredWorkspaceHorizontalMarginPx * 2);
             // We do not subtract padding here, as we also scale the workspace padding if needed.
             scaleX = availableWidthPx / usedWidth;
             shouldScale = true;
@@ -684,14 +684,7 @@
     }
 
     private int getCellLayoutHeight() {
-        return (cellHeightPx * inv.numRows) + (cellLayoutBorderSpacePx.y * (inv.numRows - 1))
-                + cellLayoutPaddingPx.top + cellLayoutPaddingPx.bottom;
-    }
-
-    private int getCellLayoutWidth() {
-        int numColumns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns;
-        return (cellWidthPx * numColumns) + (cellLayoutBorderSpacePx.x * (numColumns - 1))
-                + cellLayoutPaddingPx.left + cellLayoutPaddingPx.right;
+        return (cellHeightPx * inv.numRows) + (cellLayoutBorderSpacePx.y * (inv.numRows - 1));
     }
 
     /**
@@ -751,6 +744,18 @@
         }
         updateHotseatIconSize(iconSizePx);
 
+        if (!isVerticalLayout) {
+            int expectedWorkspaceHeight = availableHeightPx - hotseatBarSizePx
+                    - workspacePageIndicatorHeight - edgeMarginPx;
+            float minRequiredHeight = dropTargetBarSizePx + workspaceSpringLoadedBottomSpace;
+            workspaceSpringLoadShrinkFactor = Math.min(
+                    res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f,
+                    1 - (minRequiredHeight / expectedWorkspaceHeight));
+        } else {
+            workspaceSpringLoadShrinkFactor =
+                    res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
+        }
+
         // Folder icon
         folderIconSizePx = IconNormalizer.getNormalizedCircleSize(iconSizePx);
         folderIconOffsetYPx = (iconSizePx - folderIconSizePx) / 2;
@@ -860,6 +865,7 @@
 
     public void updateInsets(Rect insets) {
         mInsets.set(insets);
+        updateWorkspacePadding();
     }
 
     /**
@@ -886,62 +892,23 @@
         int numColumns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns;
         int screenWidthPx = getWorkspaceWidth(padding);
         result.x = calculateCellWidth(screenWidthPx, cellLayoutBorderSpacePx.x, numColumns);
-        int screenHeightPx = getWorkspaceHeight(padding);
-        result.y = calculateCellHeight(screenHeightPx, cellLayoutBorderSpacePx.y, inv.numRows);
+        result.y = calculateCellHeight(availableHeightPx - padding.y
+                - cellLayoutBottomPaddingPx, cellLayoutBorderSpacePx.y, inv.numRows);
         return result;
     }
 
-    /**
-     * Gets the space in px from the bottom of last item in the vertical-bar hotseat to the
-     * bottom of the screen.
-     */
-    public int getVerticalHotseatLastItemBottomOffset() {
-        int cellHeight = calculateCellHeight(
-                heightPx - mHotseatPadding.top - mHotseatPadding.bottom, hotseatBorderSpace,
-                numShownHotseatIcons);
-        int hotseatSize = (cellHeight * numShownHotseatIcons)
-                + (hotseatBorderSpace * (numShownHotseatIcons - 1));
-        int extraHotseatEndSpacing = (heightPx - hotseatSize) / 2;
-        int extraIconEndSpacing = (cellHeight - iconSizePx) / 2;
-        return extraHotseatEndSpacing + extraIconEndSpacing + mHotseatPadding.bottom;
-    }
-
-    /**
-     * Gets the scaled top of the workspace in px for the spring-loaded edit state.
-     */
-    public float getWorkspaceSpringLoadShrunkTop() {
-        workspaceSpringLoadShrunkTop = mInsets.top + dropTargetBarTopMarginPx + dropTargetBarSizePx
-                + dropTargetBarBottomMarginPx;
-        return workspaceSpringLoadShrunkTop;
-    }
-
-    /**
-     * Gets the scaled bottom of the workspace in px for the spring-loaded edit state.
-     */
-    public float getWorkspaceSpringLoadShrunkBottom() {
-        int topOfHotseat = hotseatBarSizePx + springLoadedHotseatBarTopMarginPx;
-        workspaceSpringLoadShrunkBottom =
-                heightPx - (isVerticalBarLayout() ? getVerticalHotseatLastItemBottomOffset()
-                        : topOfHotseat);
-        return workspaceSpringLoadShrunkBottom;
-    }
-
     public int getWorkspaceWidth() {
         return getWorkspaceWidth(getTotalWorkspacePadding());
     }
 
     public int getWorkspaceWidth(Point workspacePadding) {
         int cellLayoutTotalPadding =
-                (isTwoPanels ? 2 : 1) * (cellLayoutPaddingPx.left + cellLayoutPaddingPx.right);
+                isTwoPanels ? 4 * cellLayoutPaddingLeftRightPx : 2 * cellLayoutPaddingLeftRightPx;
         return availableWidthPx - workspacePadding.x - cellLayoutTotalPadding;
     }
 
-    private int getWorkspaceHeight(Point workspacePadding) {
-        return availableHeightPx - workspacePadding.y - (cellLayoutPaddingPx.top
-                + cellLayoutPaddingPx.bottom);
-    }
-
     public Point getTotalWorkspacePadding() {
+        updateWorkspacePadding();
         return new Point(workspacePadding.left + workspacePadding.right,
                 workspacePadding.top + workspacePadding.bottom);
     }
@@ -967,26 +934,12 @@
             int hotseatTop = hotseatBarSizePx;
             int paddingBottom = hotseatTop + workspacePageIndicatorHeight
                     + workspaceBottomPadding - mWorkspacePageIndicatorOverlapWorkspace;
-            int paddingTop = workspaceTopPadding + (isScalableGrid ? 0 : edgeMarginPx);
-            int paddingSide = desiredWorkspaceHorizontalMarginPx;
 
-            padding.set(paddingSide, paddingTop, paddingSide, paddingBottom);
+            padding.set(desiredWorkspaceHorizontalMarginPx,
+                    workspaceTopPadding + (isScalableGrid ? 0 : edgeMarginPx),
+                    desiredWorkspaceHorizontalMarginPx,
+                    paddingBottom);
         }
-        insetPadding(workspacePadding, cellLayoutPaddingPx);
-    }
-
-    private void insetPadding(Rect paddings, Rect insets) {
-        insets.left = Math.min(insets.left, paddings.left);
-        paddings.left -= insets.left;
-
-        insets.top = Math.min(insets.top, paddings.top);
-        paddings.top -= insets.top;
-
-        insets.right = Math.min(insets.right, paddings.right);
-        paddings.right -= insets.right;
-
-        insets.bottom = Math.min(insets.bottom, paddings.bottom);
-        paddings.bottom -= insets.bottom;
     }
 
     /**
@@ -1000,17 +953,16 @@
             // Workspace icons are moved up by a small factor. The variable diffOverlapFactor
             // is set to account for that difference.
             float diffOverlapFactor = iconSizePx * (ICON_OVERLAP_FACTOR - 1) / 2;
-            int paddingTop = Math.max((int) (mInsets.top + cellLayoutPaddingPx.top
-                    - diffOverlapFactor), 0);
-            int paddingBottom = Math.max((int) (mInsets.bottom + cellLayoutPaddingPx.bottom
+            int paddingTop = Math.max((int) (mInsets.top - diffOverlapFactor), 0);
+            int paddingBottom = Math.max((int) (mInsets.bottom + cellLayoutBottomPaddingPx
                     + diffOverlapFactor), 0);
 
             if (isSeascape()) {
-                mHotseatPadding.set(mInsets.left + hotseatBarSidePaddingStartPx, paddingTop,
-                        hotseatBarSidePaddingEndPx, paddingBottom);
+                mHotseatPadding.set(mInsets.left + hotseatBarSidePaddingStartPx,
+                        mInsets.top, hotseatBarSidePaddingEndPx, mInsets.bottom);
             } else {
-                mHotseatPadding.set(hotseatBarSidePaddingEndPx, paddingTop,
-                        mInsets.right + hotseatBarSidePaddingStartPx, paddingBottom);
+                mHotseatPadding.set(hotseatBarSidePaddingEndPx, mInsets.top,
+                        mInsets.right + hotseatBarSidePaddingStartPx, mInsets.bottom);
             }
         } else if (isTaskbarPresent) {
             int hotseatHeight = workspacePadding.bottom;
@@ -1049,12 +1001,14 @@
             float workspaceCellWidth = (float) widthPx / inv.numColumns;
             float hotseatCellWidth = (float) widthPx / numShownHotseatIcons;
             int hotseatAdjustment = Math.round((workspaceCellWidth - hotseatCellWidth) / 2);
-            mHotseatPadding.set(hotseatAdjustment + workspacePadding.left + cellLayoutPaddingPx.left
-                            + mInsets.left, hotseatBarTopPaddingPx,
-                    hotseatAdjustment + workspacePadding.right + cellLayoutPaddingPx.right
+            mHotseatPadding.set(
+                    hotseatAdjustment + workspacePadding.left + cellLayoutPaddingLeftRightPx
+                            + mInsets.left,
+                    hotseatBarTopPaddingPx,
+                    hotseatAdjustment + workspacePadding.right + cellLayoutPaddingLeftRightPx
                             + mInsets.right,
                     hotseatBarSizePx - hotseatCellHeightPx - hotseatBarTopPaddingPx
-                            + mInsets.bottom);
+                            + cellLayoutBottomPaddingPx + mInsets.bottom);
         }
         return mHotseatPadding;
     }
@@ -1138,8 +1092,6 @@
                     .getInfo().rotation == Surface.ROTATION_270;
             if (mIsSeascape != isSeascape) {
                 mIsSeascape = isSeascape;
-                // Hotseat changing sides requires updating workspace left/right paddings
-                updateWorkspacePadding();
                 return true;
             }
         }
@@ -1217,11 +1169,6 @@
                 cellLayoutBorderSpacePx.x));
         writer.println(prefix + pxToDpStr("cellLayoutBorderSpacePx Vertical",
                 cellLayoutBorderSpacePx.y));
-        writer.println(prefix + pxToDpStr("cellLayoutPaddingPx.left", cellLayoutPaddingPx.left));
-        writer.println(prefix + pxToDpStr("cellLayoutPaddingPx.top", cellLayoutPaddingPx.top));
-        writer.println(prefix + pxToDpStr("cellLayoutPaddingPx.right", cellLayoutPaddingPx.right));
-        writer.println(
-                prefix + pxToDpStr("cellLayoutPaddingPx.bottom", cellLayoutPaddingPx.bottom));
 
         writer.println(prefix + pxToDpStr("iconSizePx", iconSizePx));
         writer.println(prefix + pxToDpStr("iconTextSizePx", iconTextSizePx));
@@ -1259,12 +1206,6 @@
                 hotseatBarSidePaddingStartPx));
         writer.println(prefix + pxToDpStr("hotseatBarSidePaddingEndPx",
                 hotseatBarSidePaddingEndPx));
-        writer.println(prefix + pxToDpStr("springLoadedHotseatBarTopMarginPx",
-                springLoadedHotseatBarTopMarginPx));
-        writer.println(prefix + pxToDpStr("mHotseatPadding.top", mHotseatPadding.top));
-        writer.println(prefix + pxToDpStr("mHotseatPadding.bottom", mHotseatPadding.bottom));
-        writer.println(prefix + pxToDpStr("mHotseatPadding.left", mHotseatPadding.left));
-        writer.println(prefix + pxToDpStr("mHotseatPadding.right", mHotseatPadding.right));
         writer.println(prefix + "\tnumShownHotseatIcons: " + numShownHotseatIcons);
         writer.println(prefix + pxToDpStr("hotseatBorderSpace", hotseatBorderSpace));
         writer.println(prefix + "\tisQsbInline: " + isQsbInline);
@@ -1315,16 +1256,6 @@
         writer.println(prefix + pxToDpStr("overviewPageSpacing", overviewPageSpacing));
         writer.println(prefix + pxToDpStr("overviewRowSpacing", overviewRowSpacing));
         writer.println(prefix + pxToDpStr("overviewGridSideMargin", overviewGridSideMargin));
-
-        writer.println(prefix + pxToDpStr("dropTargetBarTopMarginPx", dropTargetBarTopMarginPx));
-        writer.println(prefix + pxToDpStr("dropTargetBarSizePx", dropTargetBarSizePx));
-        writer.println(
-                prefix + pxToDpStr("dropTargetBarBottomMarginPx", dropTargetBarBottomMarginPx));
-
-        writer.println(
-                prefix + pxToDpStr("workspaceSpringLoadShrunkTop", workspaceSpringLoadShrunkTop));
-        writer.println(prefix + pxToDpStr("workspaceSpringLoadShrunkBottom",
-                workspaceSpringLoadShrunkBottom));
     }
 
     private static Context getContext(Context c, Info info, int orientation, WindowBounds bounds) {
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index 73289fb..9fb14f6 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -17,6 +17,8 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.ButtonDropTarget.TOOLTIP_DEFAULT;
+import static com.android.launcher3.ButtonDropTarget.TOOLTIP_LEFT;
+import static com.android.launcher3.ButtonDropTarget.TOOLTIP_RIGHT;
 import static com.android.launcher3.anim.AlphaUpdateListener.updateVisibility;
 
 import android.animation.TimeInterpolator;
@@ -39,8 +41,6 @@
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.testing.TestProtocol;
 
-import java.util.Arrays;
-
 /*
  * The top bar containing various drop targets: Delete/App Info/Uninstall.
  */
@@ -94,28 +94,30 @@
         lp.rightMargin = insets.right;
         int tooltipLocation = TOOLTIP_DEFAULT;
 
-        int horizontalMargin;
-        if (grid.isTablet) {
-            // XXX: If the icon size changes across orientations, we will have to take
-            //      that into account here too.
-            horizontalMargin = ((grid.widthPx - 2 * grid.edgeMarginPx
-                    - (grid.inv.numColumns * grid.cellWidthPx))
-                    / (2 * (grid.inv.numColumns + 1)))
-                    + grid.edgeMarginPx;
+        if (grid.isVerticalBarLayout()) {
+            lp.width = grid.dropTargetBarSizePx;
+            lp.height = grid.availableHeightPx - 2 * grid.edgeMarginPx;
+            lp.gravity = grid.isSeascape() ? Gravity.RIGHT : Gravity.LEFT;
+            tooltipLocation = grid.isSeascape() ? TOOLTIP_LEFT : TOOLTIP_RIGHT;
         } else {
-            horizontalMargin = getContext().getResources()
-                    .getDimensionPixelSize(R.dimen.drop_target_bar_margin_horizontal);
-        }
-        lp.topMargin += grid.dropTargetBarTopMarginPx;
-        lp.bottomMargin += grid.dropTargetBarBottomMarginPx;
-        lp.width = grid.availableWidthPx - 2 * horizontalMargin;
-        if (mIsVertical) {
-            lp.leftMargin = (grid.widthPx - lp.width) / 2;
-            lp.rightMargin = (grid.widthPx - lp.width) / 2;
-        }
-        lp.height = grid.dropTargetBarSizePx;
-        lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
+            int gap;
+            if (grid.isTablet) {
+                // XXX: If the icon size changes across orientations, we will have to take
+                //      that into account here too.
+                gap = ((grid.widthPx - 2 * grid.edgeMarginPx
+                        - (grid.inv.numColumns * grid.cellWidthPx))
+                        / (2 * (grid.inv.numColumns + 1)))
+                        + grid.edgeMarginPx;
+            } else {
+                gap = getContext().getResources()
+                        .getDimensionPixelSize(R.dimen.drop_target_bar_margin_horizontal);
+            }
+            lp.width = grid.availableWidthPx - 2 * gap;
 
+            lp.topMargin += grid.edgeMarginPx;
+            lp.height = grid.dropTargetBarSizePx;
+            lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
+        }
         setLayoutParams(lp);
         for (ButtonDropTarget button : mDropTargets) {
             button.setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.dropTargetTextSizePx);
@@ -137,7 +139,19 @@
         int height = MeasureSpec.getSize(heightMeasureSpec);
 
         int visibleCount = getVisibleButtonsCount();
-        if (visibleCount > 0) {
+        if (visibleCount == 0) {
+            // do nothing
+        } else if (mIsVertical) {
+            int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
+            int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
+
+            for (ButtonDropTarget button : mDropTargets) {
+                if (button.getVisibility() != GONE) {
+                    button.setTextVisible(false);
+                    button.measure(widthSpec, heightSpec);
+                }
+            }
+        } else {
             int availableWidth = width / visibleCount;
             boolean textVisible = true;
             for (ButtonDropTarget buttons : mDropTargets) {
@@ -162,91 +176,31 @@
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         int visibleCount = getVisibleButtonsCount();
         if (visibleCount == 0) {
-            return;
-        }
+            // do nothing
+        } else if (mIsVertical) {
+            int gap = getResources().getDimensionPixelSize(R.dimen.drop_target_vertical_gap);
+            int start = gap;
+            int end;
 
-        Launcher launcher = Launcher.getLauncher(getContext());
-        Workspace workspace = launcher.getWorkspace();
-        DeviceProfile dp = launcher.getDeviceProfile();
-        int buttonHorizontalPadding = dp.dropTargetHorizontalPaddingPx;
-        int buttonVerticalPadding = dp.dropTargetVerticalPaddingPx;
-        int barCenter = (right - left) / 2;
-
-        ButtonDropTarget[] visibleButtons = Arrays.stream(mDropTargets)
-                .filter(b -> b.getVisibility() != GONE)
-                .toArray(ButtonDropTarget[]::new);
-        Arrays.stream(visibleButtons).forEach(
-                b -> b.setPadding(buttonHorizontalPadding, buttonVerticalPadding,
-                        buttonHorizontalPadding, buttonVerticalPadding));
-
-        if (visibleCount == 1) {
-            ButtonDropTarget button = visibleButtons[0];
-            button.layout(barCenter - (button.getMeasuredWidth() / 2), 0,
-                    barCenter + (button.getMeasuredWidth() / 2), button.getMeasuredHeight());
-        } else if (visibleCount == 2) {
-            int buttonGap = dp.dropTargetGapPx;
-
-            if (dp.isTwoPanels) {
-                ButtonDropTarget leftButton = visibleButtons[0];
-                leftButton.layout(barCenter - leftButton.getMeasuredWidth() - (buttonGap / 2), 0,
-                        barCenter - (buttonGap / 2), leftButton.getMeasuredHeight());
-
-                ButtonDropTarget rightButton = visibleButtons[1];
-                rightButton.layout(barCenter + (buttonGap / 2), 0,
-                        barCenter + rightButton.getMeasuredWidth() + (buttonGap / 2),
-                        rightButton.getMeasuredHeight());
-            } else if (dp.isTablet) {
-                int numberOfMargins = visibleCount - 1;
-                int buttonWidths = Arrays.stream(mDropTargets)
-                        .filter(b -> b.getVisibility() != GONE)
-                        .mapToInt(ButtonDropTarget::getMeasuredWidth)
-                        .sum();
-                int totalWidth = buttonWidths + (numberOfMargins * buttonGap);
-                int buttonsStartMargin = barCenter - (totalWidth / 2);
-
-                int start = buttonsStartMargin;
-                for (ButtonDropTarget button : visibleButtons) {
-                    int margin = (start != buttonsStartMargin) ? buttonGap : 0;
-                    button.layout(start + margin, 0, start + margin + button.getMeasuredWidth(),
-                            button.getMeasuredHeight());
-                    start += button.getMeasuredWidth() + margin;
+            for (ButtonDropTarget button : mDropTargets) {
+                if (button.getVisibility() != GONE) {
+                    end = start + button.getMeasuredHeight();
+                    button.layout(0, start, button.getMeasuredWidth(), end);
+                    start = end + gap;
                 }
-            } else if (mIsVertical) {
-                // Center buttons over workspace, not screen.
-                int verticalCenter = (workspace.getRight() - workspace.getLeft()) / 2;
-                ButtonDropTarget leftButton = visibleButtons[0];
-                leftButton.layout(verticalCenter - leftButton.getMeasuredWidth() - (buttonGap / 2),
-                        0, verticalCenter - (buttonGap / 2), leftButton.getMeasuredHeight());
+            }
+        } else {
+            int frameSize = (right - left) / visibleCount;
 
-                ButtonDropTarget rightButton = visibleButtons[1];
-                rightButton.layout(verticalCenter + (buttonGap / 2), 0,
-                        verticalCenter + rightButton.getMeasuredWidth() + (buttonGap / 2),
-                        rightButton.getMeasuredHeight());
-            } else if (dp.isPhone) {
-                // Buttons aligned to outer edges of scaled workspace.
-                float shrunkTop = dp.getWorkspaceSpringLoadShrunkTop();
-                float shrunkBottom = dp.getWorkspaceSpringLoadShrunkBottom();
-                float scale =
-                        (shrunkBottom - shrunkTop) / launcher.getWorkspace().getNormalChildHeight();
-                int workspaceWidth = (int) (launcher.getWorkspace().getNormalChildWidth() * scale);
-                int start = barCenter - (workspaceWidth / 2);
-                int end = barCenter + (workspaceWidth / 2);
-
-                ButtonDropTarget leftButton = visibleButtons[0];
-                ButtonDropTarget rightButton = visibleButtons[1];
-
-                // If the text within the buttons is too long, the buttons can overlap
-                int overlap = start + leftButton.getMeasuredWidth() + rightButton.getMeasuredWidth()
-                        - end;
-                if (overlap > 0) {
-                    start -= overlap / 2;
-                    end += overlap / 2;
+            int start = frameSize / 2;
+            int halfWidth;
+            for (ButtonDropTarget button : mDropTargets) {
+                if (button.getVisibility() != GONE) {
+                    halfWidth = button.getMeasuredWidth() / 2;
+                    button.layout(start - halfWidth, 0,
+                            start + halfWidth, button.getMeasuredHeight());
+                    start = start + frameSize;
                 }
-
-                leftButton.layout(start, 0, start + leftButton.getMeasuredWidth(),
-                        leftButton.getMeasuredHeight());
-                rightButton.layout(end - rightButton.getMeasuredWidth(), 0, end,
-                        rightButton.getMeasuredHeight());
             }
         }
     }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index a7fc2f5..635b7cc 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;
@@ -324,14 +325,37 @@
             setPageSpacing(Math.max(maxInsets, maxPadding));
         }
 
-        updateCellLayoutPadding();
+        updateWorkspaceScreensPadding();
         updateWorkspaceWidgetsSizes();
     }
 
-    private void updateCellLayoutPadding() {
-        Rect padding = mLauncher.getDeviceProfile().cellLayoutPaddingPx;
-        mWorkspaceScreens.forEach(
-                s -> s.setPadding(padding.left, padding.top, padding.right, padding.bottom));
+    private void updateWorkspaceScreensPadding() {
+        DeviceProfile grid = mLauncher.getDeviceProfile();
+        int paddingLeftRight = grid.cellLayoutPaddingLeftRightPx;
+        int paddingBottom = grid.cellLayoutBottomPaddingPx;
+
+        int panelCount = getPanelCount();
+        int rightPanelModulus = mIsRtl ? 0 : panelCount - 1;
+        int leftPanelModulus = mIsRtl ? panelCount - 1 : 0;
+        int numberOfScreens = mScreenOrder.size();
+        for (int i = 0; i < numberOfScreens; i++) {
+            int paddingLeft = paddingLeftRight;
+            int paddingRight = paddingLeftRight;
+            // Add missing cellLayout border in-between panels.
+            if (panelCount > 1) {
+                if (i % panelCount == leftPanelModulus) {
+                    paddingRight += grid.cellLayoutBorderSpacePx.x / 2;
+                } else if (i % panelCount == rightPanelModulus) { // right side panel
+                    paddingLeft += grid.cellLayoutBorderSpacePx.x / 2;
+                } else { // middle panel
+                    paddingLeft += grid.cellLayoutBorderSpacePx.x / 2;
+                    paddingRight += grid.cellLayoutBorderSpacePx.x / 2;
+                }
+            }
+            // SparseArrayMap doesn't keep the order
+            mWorkspaceScreens.get(mScreenOrder.get(i))
+                    .setPadding(paddingLeft, 0, paddingRight, paddingBottom);
+        }
     }
 
     private void updateWorkspaceWidgetsSizes() {
@@ -629,7 +653,7 @@
                 mLauncher.getStateManager().getState(), newScreen, insertIndex);
 
         updatePageScrollValues();
-        updateCellLayoutPadding();
+        updateWorkspaceScreensPadding();
         return newScreen;
     }
 
@@ -2831,7 +2855,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 +2865,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 +2919,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/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 3dfece7..cc17064 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -225,21 +225,21 @@
         mHotseat.resetLayout(false);
 
         CellLayout firstScreen = mRootView.findViewById(R.id.workspace);
-        firstScreen.setPadding(mDp.workspacePadding.left + mDp.cellLayoutPaddingPx.left,
-                mDp.workspacePadding.top + mDp.cellLayoutPaddingPx.top,
+        firstScreen.setPadding(mDp.workspacePadding.left + mDp.cellLayoutPaddingLeftRightPx,
+                mDp.workspacePadding.top,
                 (mDp.isTwoPanels ? mDp.cellLayoutBorderSpacePx.x / 2
-                        : mDp.workspacePadding.right) + mDp.cellLayoutPaddingPx.right,
-                mDp.workspacePadding.bottom + mDp.cellLayoutPaddingPx.bottom
+                        : mDp.workspacePadding.right) + mDp.cellLayoutPaddingLeftRightPx,
+                mDp.workspacePadding.bottom
         );
         mWorkspaceScreens.put(FIRST_SCREEN_ID, firstScreen);
 
         if (mDp.isTwoPanels) {
             CellLayout rightPanel = mRootView.findViewById(R.id.workspace_right);
             rightPanel.setPadding(
-                    mDp.cellLayoutBorderSpacePx.x / 2  + mDp.cellLayoutPaddingPx.left,
-                    mDp.workspacePadding.top + mDp.cellLayoutPaddingPx.top,
-                    mDp.workspacePadding.right + mDp.cellLayoutPaddingPx.right,
-                    mDp.workspacePadding.bottom + mDp.cellLayoutPaddingPx.bottom
+                    mDp.cellLayoutBorderSpacePx.x / 2 + mDp.cellLayoutPaddingLeftRightPx,
+                    mDp.workspacePadding.top,
+                    mDp.workspacePadding.right + mDp.cellLayoutPaddingLeftRightPx,
+                    mDp.workspacePadding.bottom
             );
             mWorkspaceScreens.put(Workspace.SECOND_SCREEN_ID, rightPanel);
         }
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index e6dc21e..b8ecec1 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -552,7 +552,34 @@
 
         @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);
 
         // ADD MORE
 
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/secondarydisplay/SecondaryDragLayer.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
index 9201006..967f2c8 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
@@ -113,17 +113,13 @@
         for (int i = 0; i < count; i++) {
             final View child = getChildAt(i);
             if (child == mAppsView) {
-                int horizontalPadding = (2 * grid.desiredWorkspaceHorizontalMarginPx)
-                        + grid.cellLayoutPaddingPx.left + grid.cellLayoutPaddingPx.right;
-                int verticalPadding =
-                        grid.cellLayoutPaddingPx.top + grid.cellLayoutPaddingPx.bottom;
+                int padding = 2 * (grid.desiredWorkspaceHorizontalMarginPx
+                        + grid.cellLayoutPaddingLeftRightPx);
 
-                int maxWidth =
-                        grid.allAppsCellWidthPx * grid.numShownAllAppsColumns + horizontalPadding;
+                int maxWidth = grid.allAppsCellWidthPx * grid.numShownAllAppsColumns + padding;
                 int appsWidth = Math.min(width, maxWidth);
 
-                int maxHeight =
-                        grid.allAppsCellHeightPx * grid.numShownAllAppsColumns + verticalPadding;
+                int maxHeight = grid.allAppsCellHeightPx * grid.numShownAllAppsColumns + padding;
                 int appsHeight = Math.min(height, maxHeight);
 
                 mAppsView.measure(
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 9be3cc5..d52594e 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -18,6 +18,7 @@
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
 
 import android.content.Context;
+import android.graphics.Rect;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
@@ -51,15 +52,28 @@
             return super.getWorkspaceScaleAndTranslation(launcher);
         }
 
-        float shrunkTop = grid.getWorkspaceSpringLoadShrunkTop();
-        float shrunkBottom = grid.getWorkspaceSpringLoadShrunkBottom();
-        float scale = (shrunkBottom - shrunkTop) / ws.getNormalChildHeight();
+        if (grid.isVerticalBarLayout()) {
+            float scale = grid.workspaceSpringLoadShrinkFactor;
+            return new ScaleAndTranslation(scale, 0, 0);
+        }
+
+        float scale = grid.workspaceSpringLoadShrinkFactor;
+        Rect insets = launcher.getDragLayer().getInsets();
+
+        float scaledHeight = scale * ws.getNormalChildHeight();
+        float shrunkTop = insets.top + grid.dropTargetBarSizePx;
+        float shrunkBottom = ws.getMeasuredHeight() - insets.bottom
+                - grid.workspacePadding.bottom
+                - grid.workspaceSpringLoadedBottomSpace;
+        float totalShrunkSpace = shrunkBottom - shrunkTop;
+
+        float desiredCellTop = shrunkTop + (totalShrunkSpace - scaledHeight) / 2;
 
         float halfHeight = ws.getHeight() / 2;
         float myCenter = ws.getTop() + halfHeight;
         float cellTopFromCenter = halfHeight - ws.getChildAt(0).getTop();
         float actualCellTop = myCenter - cellTopFromCenter * scale;
-        return new ScaleAndTranslation(scale, 0, (shrunkTop - actualCellTop) / scale);
+        return new ScaleAndTranslation(scale, 0, (desiredCellTop - actualCellTop) / scale);
     }
 
     @Override
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/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index f270cd5..84ef149 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -1650,16 +1650,10 @@
     }
 
     Point getRealDisplaySize() {
-        final Point size = new Point();
-        getContext().getSystemService(WindowManager.class).getDefaultDisplay().getRealSize(size);
-        final Rect winMetricsHeight = getContext().getSystemService(WindowManager.class)
+        final Rect displayBounds = getContext().getSystemService(WindowManager.class)
                 .getMaximumWindowMetrics()
                 .getBounds();
-        final Point winMetricsSize = new Point(winMetricsHeight.width(), winMetricsHeight.height());
-        if (!winMetricsSize.equals(size)) {
-            fail("Display size mismatch: " + size + " vs " + winMetricsSize);
-        }
-        return size;
+        return new Point(displayBounds.width(), displayBounds.height());
     }
 
     public void enableDebugTracing() {