Merge "Close system dialogs on Taskbar home button tap" into tm-qpr-dev
diff --git a/quickstep/res/values-sw600dp/config.xml b/quickstep/res/values-sw600dp/config.xml
deleted file mode 100644
index e1e442f..0000000
--- a/quickstep/res/values-sw600dp/config.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2023 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.
--->
-<!-- Applies to large tablet screens portrait -->
-<resources>
-    <!-- Taskbar -->
-    <!-- Align the Taskbar to the start (Left/Right) of the device when 3 button nav is enabled. -->
-    <bool name="start_align_taskbar">true</bool>
-</resources>
\ No newline at end of file
diff --git a/quickstep/res/values-sw720dp-land/config.xml b/quickstep/res/values-sw720dp-land/config.xml
deleted file mode 100644
index bf0f9ad..0000000
--- a/quickstep/res/values-sw720dp-land/config.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2023 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.
--->
-<!-- Applies to large tablet screens landscape -->
-<resources>
-    <!-- Taskbar -->
-    <!-- Align the Taskbar to the start (Left/Right) of the device when 3 button nav is enabled. -->
-    <bool name="start_align_taskbar">false</bool>
-</resources>
\ No newline at end of file
diff --git a/quickstep/res/values-sw720dp/config.xml b/quickstep/res/values-sw720dp/config.xml
deleted file mode 100644
index e1e442f..0000000
--- a/quickstep/res/values-sw720dp/config.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2023 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.
--->
-<!-- Applies to large tablet screens portrait -->
-<resources>
-    <!-- Taskbar -->
-    <!-- Align the Taskbar to the start (Left/Right) of the device when 3 button nav is enabled. -->
-    <bool name="start_align_taskbar">true</bool>
-</resources>
\ No newline at end of file
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index 0d3aeb3..e45d9fd 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -52,10 +52,6 @@
 
     <string name="setup_wizard_pkg" translatable="false" />
 
-    <!-- Taskbar -->
-    <!-- Align the Taskbar to the start (Left/Right) of the device when 3 button nav is enabled. -->
-    <bool name="start_align_taskbar">false</bool>
-
     <!-- This is a float because it is converted to dp later in DeviceProfile -->
     <item name="taskbar_icon_size" type="dimen" format="float">44</item>
 </resources>
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index 941b4b0..c4255bf 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -49,7 +49,8 @@
     public static final int ALPHA_INDEX_STASHED = 0;
     public static final int ALPHA_INDEX_HOME_DISABLED = 1;
     public static final int ALPHA_INDEX_ASSISTANT_INVOKED = 2;
-    private static final int NUM_ALPHA_CHANNELS = 3;
+    public static final int ALPHA_INDEX_HIDDEN_WHILE_DREAMING = 3;
+    private static final int NUM_ALPHA_CHANNELS = 4;
 
     /**
      * The SharedPreferences key for whether the stashed handle region is dark.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index 2628a7f..2fba37e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -53,6 +53,10 @@
     // Translation property for taskbar background.
     private final AnimatedFloat mBgOffset = new AnimatedFloat(this::updateBackgroundOffset);
 
+    // Used to fade in/out the entirety of the taskbar, for a smooth transition before/after sysui
+    // changes the inset visibility.
+    private final AnimatedFloat mTaskbarAlpha = new AnimatedFloat(this::updateTaskbarAlpha);
+
     // Initialized in init.
     private TaskbarControllers mControllers;
     private TaskbarStashViaTouchController mTaskbarStashViaTouchController;
@@ -83,6 +87,9 @@
         mAssistantBgTaskbar.value = 1;
         mBgOverride.value = 1;
         updateBackgroundAlpha();
+
+        mTaskbarAlpha.value = 1;
+        updateTaskbarAlpha();
     }
 
     public void onDestroy() {
@@ -127,6 +134,10 @@
         return mBgOffset;
     }
 
+    public AnimatedFloat getTaskbarAlpha() {
+        return mTaskbarAlpha;
+    }
+
     /**
      * Make updates when configuration changes.
      */
@@ -165,6 +176,10 @@
         updateOnBackgroundNavButtonColorIntensity();
     }
 
+    private void updateTaskbarAlpha() {
+        mTaskbarDragLayer.setAlpha(mTaskbarAlpha.value);
+    }
+
     @Override
     public void setCornerRoundness(float cornerRoundness) {
         mTaskbarDragLayer.setCornerRoundness(cornerRoundness);
@@ -188,6 +203,7 @@
         pw.println(prefix + "TaskbarDragLayerController:");
 
         pw.println(prefix + "\tmBgOffset=" + mBgOffset.value);
+        pw.println(prefix + "\tmTaskbarAlpha=" + mTaskbarAlpha.value);
         pw.println(prefix + "\tmFolderMargin=" + mFolderMargin);
         pw.println(prefix + "\tmLastSetBackgroundAlpha=" + mLastSetBackgroundAlpha);
         pw.println(prefix + "\t\tmBgOverride=" + mBgOverride.value);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index b98ea81..58cb558 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -23,11 +23,15 @@
 import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
 import static com.android.systemui.animation.Interpolators.EMPHASIZED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_AWAKE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_MASK;
+import static com.android.systemui.shared.system.QuickStepContract.WAKEFULNESS_AWAKE;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
+import android.os.SystemClock;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
@@ -93,9 +97,25 @@
      */
     private static final int FLAG_LAUNCHER_WAS_ACTIVE_WHILE_AWAKE = 1 << 4;
 
-    /** Whether the device is currently locked. */
+    /**
+     * Whether the device is currently locked.
+     * <ul>
+     *  <li>While locked, the taskbar is always stashed.<li/>
+     *  <li>Navbar animations on FLAG_DEVICE_LOCKED transitions will get special treatment.</li>
+     * </ul>
+     */
     private static final int FLAG_DEVICE_LOCKED = 1 << 5;
 
+    /**
+     * Whether the complete taskbar is completely hidden (neither visible stashed or unstashed).
+     * This is tracked to allow a nice transition of the taskbar before SysUI forces it away by
+     * hiding the inset.
+     *
+     * This flag is predominanlty set while FLAG_DEVICE_LOCKED is set, thus the taskbar's invisible
+     * resting state while hidden is stashed.
+     */
+    private static final int FLAG_TASKBAR_HIDDEN = 1 << 6;
+
     private static final int FLAGS_LAUNCHER_ACTIVE = FLAG_RESUMED | FLAG_TRANSITION_TO_RESUMED;
     /** Equivalent to an int with all 1s for binary operation purposes */
     private static final int FLAGS_ALL = ~0;
@@ -104,11 +124,21 @@
     private static final float TASKBAR_BG_ALPHA_NOT_LAUNCHER_NOT_ALIGNED_DELAY_MULT = 0.33f;
     private static final float TASKBAR_BG_ALPHA_LAUNCHER_IS_ALIGNED_DURATION_MULT = 0.25f;
 
+    /**
+     * Delay for the taskbar fade-in.
+     *
+     * Helps to avoid visual noise when unlocking successfully via SFPS, and the device transitions
+     * to launcher directly. The delay avoids the navbar to become briefly visible. The duration
+     * is the same as in SysUI, see http://shortn/_uNSbDoRUSr.
+     */
+    private static final long TASKBAR_SHOW_DELAY_MS = 250;
+
     private final AnimatedFloat mIconAlignment =
             new AnimatedFloat(this::onIconAlignmentRatioChanged);
 
     private TaskbarControllers mControllers;
     private AnimatedFloat mTaskbarBackgroundAlpha;
+    private AnimatedFloat mTaskbarAlpha;
     private AnimatedFloat mTaskbarCornerRoundness;
     private MultiProperty mIconAlphaForHome;
     private QuickstepLauncher mLauncher;
@@ -117,6 +147,9 @@
     private int mState;
     private LauncherState mLauncherState = LauncherState.NORMAL;
 
+    // Time when FLAG_TASKBAR_HIDDEN was last cleared, SystemClock.elapsedRealtime (milliseconds).
+    private long mLastUnlockTimeMs = 0;
+
     private @Nullable TaskBarRecentsAnimationListener mTaskBarRecentsAnimationListener;
 
     private boolean mIsAnimatingToLauncher;
@@ -187,6 +220,7 @@
 
         mTaskbarBackgroundAlpha = mControllers.taskbarDragLayerController
                 .getTaskbarBackgroundAlpha();
+        mTaskbarAlpha = mControllers.taskbarDragLayerController.getTaskbarAlpha();
         mTaskbarCornerRoundness = mControllers.getTaskbarCornerRoundness();
         mIconAlphaForHome = mControllers.taskbarViewController
                 .getTaskbarIconAlpha().get(ALPHA_INDEX_HOME);
@@ -279,6 +313,15 @@
         boolean isDeviceLocked = hasAnyFlag(systemUiStateFlags, MASK_ANY_SYSUI_LOCKED);
         updateStateForFlag(FLAG_DEVICE_LOCKED, isDeviceLocked);
 
+        // Taskbar is hidden whenever the device is dreaming. The dreaming state includes the
+        // interactive dreams, AoD, screen off. Since the SYSUI_STATE_DEVICE_DREAMING only kicks in
+        // when the device is asleep, the second condition extends ensures that the transition from
+        // and to the WAKEFULNESS_ASLEEP state also hide the taskbar, and improves the taskbar
+        // hide/reveal animation timings.
+        boolean isTaskbarHidden = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_DEVICE_DREAMING)
+                || (systemUiStateFlags & SYSUI_STATE_WAKEFULNESS_MASK) != WAKEFULNESS_AWAKE;
+        updateStateForFlag(FLAG_TASKBAR_HIDDEN, isTaskbarHidden);
+
         if (skipAnim) {
             applyState(0);
         } else {
@@ -406,6 +449,41 @@
             AbstractFloatingView.closeAllOpenViews(mControllers.taskbarActivityContext);
         }
 
+        if (hasAnyFlag(changedFlags, FLAG_TASKBAR_HIDDEN) && !hasAnyFlag(FLAG_TASKBAR_HIDDEN)) {
+            // Take note of the current time, as the taskbar is made visible again.
+            mLastUnlockTimeMs = SystemClock.elapsedRealtime();
+        }
+
+        boolean isHidden = hasAnyFlag(FLAG_TASKBAR_HIDDEN);
+        float taskbarAlpha = isHidden ? 0 : 1;
+        if (mTaskbarAlpha.isAnimating() || mTaskbarAlpha.value != taskbarAlpha) {
+            Animator taskbarVisibility = mTaskbarAlpha.animateToValue(taskbarAlpha);
+
+            taskbarVisibility.setDuration(duration);
+            if (isHidden) {
+                // Stash the transient taskbar once the taskbar is not visible. This reduces
+                // visual noise when unlocking the device afterwards.
+                animatorSet.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        TaskbarStashController stashController =
+                                mControllers.taskbarStashController;
+                        stashController.updateAndAnimateTransientTaskbar(
+                                /* stash */ true, /* duration */ 0);
+                    }
+                });
+            } else {
+                // delay the fade in animation a bit to reduce visual noise when waking up a device
+                // with a fingerprint reader. This should only be done when the device was woken
+                // up via fingerprint reader, however since this information is currently not
+                // available, opting to always delay the fade-in a bit.
+                long durationSinceLastUnlockMs = SystemClock.elapsedRealtime() - mLastUnlockTimeMs;
+                taskbarVisibility.setStartDelay(
+                        Math.max(0, TASKBAR_SHOW_DELAY_MS - durationSinceLastUnlockMs));
+            }
+            animatorSet.play(taskbarVisibility);
+        }
+
         float backgroundAlpha = isInLauncher && isTaskbarAlignedWithHotseat() ? 0 : 1;
 
         // Don't animate if background has reached desired value.
@@ -564,7 +642,7 @@
         long resetDuration = mControllers.taskbarStashController.isInApp()
                 ? duration
                 : duration / 2;
-        if (!mControllers.taskbarTranslationController.willAnimateToZeroBefore(resetDuration)
+        if (mControllers.taskbarTranslationController.shouldResetBackToZero(resetDuration)
                 && (isAnimatingToLauncher() || mLauncherState == LauncherState.NORMAL)) {
             animatorSet.play(mControllers.taskbarTranslationController
                     .createAnimToResetTranslation(resetDuration));
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index c43b621..69ea9fd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -36,6 +36,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -43,6 +44,7 @@
 import android.app.RemoteAction;
 import android.content.SharedPreferences;
 import android.graphics.drawable.Icon;
+import android.os.SystemClock;
 import android.util.Log;
 import android.view.InsetsController;
 import android.view.View;
@@ -50,6 +52,7 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.Interpolator;
 
+import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
@@ -68,6 +71,8 @@
 import com.android.quickstep.SystemUiProxy;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.StringJoiner;
 import java.util.function.IntPredicate;
 
@@ -184,6 +189,40 @@
     // Auto stashes when user has not interacted with the Taskbar after X ms.
     private static final long NO_TOUCH_TIMEOUT_TO_STASH_MS = 5000;
 
+    // Duration for which an unlock event is considered "current", as other events are received
+    // asynchronously.
+    private static final long UNLOCK_TRANSITION_MEMOIZATION_MS = 200;
+
+    /**
+     * The default stash animation, morphing the taskbar into the navbar.
+     */
+    private static final int TRANSITION_DEFAULT = 0;
+    /**
+     * Transitioning from launcher to app. Same as TRANSITION_DEFAULT, differs in internal
+     * animation timings.
+     */
+    private static final int TRANSITION_HOME_TO_APP = 1;
+    /**
+     * Fading the navbar in and out, where the taskbar jumpcuts in and out at the very begin/end of
+     * the transition. Used to transition between the hotseat and navbar` without the stash/unstash
+     * transition.
+     */
+    private static final int TRANSITION_HANDLE_FADE = 2;
+    /**
+     * Same as TRANSITION_DEFAULT, but exclusively used during an "navbar unstash to hotseat
+     * animation" bound to the progress of a swipe gesture. It differs from TRANSITION_DEFAULT
+     * by not scaling the height of the taskbar background.
+     */
+    private static final int TRANSITION_UNSTASH_SUW_MANUAL = 3;
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            TRANSITION_DEFAULT,
+            TRANSITION_HOME_TO_APP,
+            TRANSITION_HANDLE_FADE,
+            TRANSITION_UNSTASH_SUW_MANUAL,
+    })
+    private @interface StashAnimation {}
+
     private final TaskbarActivityContext mActivity;
     private final SharedPreferences mPrefs;
     private final int mStashedHeight;
@@ -472,9 +511,16 @@
     }
 
     /**
-     * Stash or unstashes the transient taskbar.
+     * Stash or unstashes the transient taskbar, using the default TASKBAR_STASH_DURATION.
      */
     public void updateAndAnimateTransientTaskbar(boolean stash) {
+        updateAndAnimateTransientTaskbar(stash, TASKBAR_STASH_DURATION);
+    }
+
+    /**
+     * Stash or unstashes the transient taskbar.
+     */
+    public void updateAndAnimateTransientTaskbar(boolean stash, long duration) {
         if (!DisplayController.isTransientTaskbar(mActivity)) {
             return;
         }
@@ -553,8 +599,7 @@
         createAnimToIsStashed(
                 /* isStashed= */ false,
                 placeholderDuration,
-                /* animateBg= */ false,
-                /* changedFlags=*/ 0);
+                TRANSITION_UNSTASH_SUW_MANUAL);
         animation.play(mAnimator);
     }
 
@@ -562,10 +607,15 @@
      * Create a stash animation and save to {@link #mAnimator}.
      * @param isStashed whether it's a stash animation or an unstash animation
      * @param duration duration of the animation
-     * @param animateBg whether the taskbar's background should be animated
+     * @param animationType what transition type to play.
      */
-    private void createAnimToIsStashed(boolean isStashed, long duration, boolean animateBg,
-            int changedFlags) {
+    private void createAnimToIsStashed(boolean isStashed, long duration,
+            @StashAnimation int animationType) {
+        if (animationType == TRANSITION_UNSTASH_SUW_MANUAL && isStashed) {
+            // The STASH_ANIMATION_SUW_MANUAL must only be used during an unstash animation.
+            Log.e(TAG, "Illegal arguments:Using TRANSITION_UNSTASH_SUW_MANUAL to stash taskbar");
+        }
+
         if (mAnimator != null) {
             mAnimator.cancel();
         }
@@ -591,23 +641,10 @@
             return;
         }
 
-        // If Hotseat is not the top element during animation to/from Launcher, fade in/out a
-        // already stashed Taskbar.
-        boolean hotseatTopElement = mControllers.uiController.isHotseatIconOnTopWhenAligned()
-                || !hasAnyFlag(changedFlags, FLAG_IN_APP);
-        // If transitioning to unlocked device, do not play a stash animation.
-        // Keep isUnlockTransition in sync with its counterpart in
-        // TaskbarLauncherStateController#onStateChangeApplied.
-        boolean isUnlockTransition = hasAnyFlag(changedFlags, FLAG_STASHED_DEVICE_LOCKED)
-                && !hasAnyFlag(FLAG_STASHED_DEVICE_LOCKED);
-        boolean skipStashAnimation = !hotseatTopElement || isUnlockTransition;
-
         if (isTransientTaskbar) {
-            createTransientAnimToIsStashed(mAnimator, isStashed, duration, animateBg, changedFlags,
-                    skipStashAnimation);
+            createTransientAnimToIsStashed(mAnimator, isStashed, duration, animationType);
         } else {
-            createAnimToIsStashed(mAnimator, isStashed, duration, animateBg, skipStashAnimation,
-                    stashTranslation);
+            createAnimToIsStashed(mAnimator, isStashed, duration, stashTranslation, animationType);
         }
 
         mAnimator.addListener(new AnimatorListenerAdapter() {
@@ -636,7 +673,7 @@
     }
 
     private void createAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration,
-            boolean animateBg, boolean skipStashAnimation, float stashTranslation) {
+            float stashTranslation, @StashAnimation int animationType) {
         AnimatorSet fullLengthAnimatorSet = new AnimatorSet();
         // Not exactly half and may overlap. See [first|second]HalfDurationScale below.
         AnimatorSet firstHalfAnimatorSet = new AnimatorSet();
@@ -650,12 +687,7 @@
             secondHalfDurationScale = 0.5f;
 
             fullLengthAnimatorSet.play(mIconTranslationYForStash.animateToValue(stashTranslation));
-            if (animateBg) {
-                fullLengthAnimatorSet.play(mTaskbarBackgroundOffset.animateToValue(1));
-            } else {
-                fullLengthAnimatorSet.addListener(AnimatorListeners.forEndCallback(
-                        () -> mTaskbarBackgroundOffset.updateValue(1)));
-            }
+            fullLengthAnimatorSet.play(mTaskbarBackgroundOffset.animateToValue(1));
 
             firstHalfAnimatorSet.playTogether(
                     mIconAlphaForStash.animateToValue(0),
@@ -666,7 +698,7 @@
                     mTaskbarStashedHandleAlpha.animateToValue(1)
             );
 
-            if (skipStashAnimation) {
+            if (animationType == TRANSITION_HANDLE_FADE) {
                 fullLengthAnimatorSet.setInterpolator(INSTANT);
                 firstHalfAnimatorSet.setInterpolator(INSTANT);
             }
@@ -677,6 +709,8 @@
             fullLengthAnimatorSet.playTogether(
                     mIconScaleForStash.animateToValue(1),
                     mIconTranslationYForStash.animateToValue(0));
+
+            final boolean animateBg = animationType != TRANSITION_UNSTASH_SUW_MANUAL;
             if (animateBg) {
                 fullLengthAnimatorSet.play(mTaskbarBackgroundOffset.animateToValue(0));
             } else {
@@ -691,7 +725,7 @@
                     mIconAlphaForStash.animateToValue(1)
             );
 
-            if (skipStashAnimation) {
+            if (animationType == TRANSITION_HANDLE_FADE) {
                 fullLengthAnimatorSet.setInterpolator(FINAL_FRAME);
                 secondHalfAnimatorSet.setInterpolator(FINAL_FRAME);
             }
@@ -714,61 +748,78 @@
     }
 
     private void createTransientAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration,
-            boolean animateBg, int changedFlags, boolean skipStashAnimation) {
-        Interpolator skipInterpolator = null;
+            @StashAnimation int animationType) {
+        // Target values of the properties this is going to set
+        final float backgroundOffsetTarget = isStashed ? 1 : 0;
+        final float iconAlphaTarget = isStashed ? 0 : 1;
+        final float stashedHandleAlphaTarget = isStashed ? 1 : 0;
+
+        // Timing for the alpha values depend on the animation played
+        long iconAlphaStartDelay = 0, iconAlphaDuration = 0, stashedHandleAlphaDelay = 0,
+                stashedHandleAlphaDuration = 0;
+        if (duration > 0) {
+            if (animationType == TRANSITION_HANDLE_FADE) {
+                // When fading, the handle fades in/out at the beginning of the transition with
+                // TASKBAR_STASH_ALPHA_DURATION.
+                stashedHandleAlphaDuration = TASKBAR_STASH_ALPHA_DURATION;
+                // The iconAlphaDuration must be set to duration for the skippable interpolators
+                // below to work.
+                iconAlphaDuration = duration;
+            } else {
+                iconAlphaStartDelay = TASKBAR_STASH_ALPHA_START_DELAY;
+                iconAlphaDuration = TASKBAR_STASH_ALPHA_DURATION;
+                stashedHandleAlphaDuration = TASKBAR_STASH_ALPHA_DURATION;
+
+                if (isStashed) {
+                    if (animationType == TRANSITION_HOME_TO_APP) {
+                        iconAlphaStartDelay = TASKBAR_STASH_ICON_ALPHA_HOME_TO_APP_START_DELAY;
+                    }
+                    stashedHandleAlphaDelay = iconAlphaStartDelay;
+                    stashedHandleAlphaDuration = Math.max(0, duration - iconAlphaStartDelay);
+                }
+
+            }
+        }
+
+        play(as, mTaskbarStashedHandleAlpha.animateToValue(stashedHandleAlphaTarget),
+                stashedHandleAlphaDelay,
+                stashedHandleAlphaDuration, LINEAR);
+
+        // The rest of the animations might be "skipped" in TRANSITION_HANDLE_FADE transitions.
+        AnimatorSet skippable = as;
+        if (animationType == TRANSITION_HANDLE_FADE) {
+            skippable = new AnimatorSet();
+            as.play(skippable);
+            skippable.setInterpolator(isStashed ? INSTANT : FINAL_FRAME);
+        }
+
+        final boolean animateBg = animationType != TRANSITION_UNSTASH_SUW_MANUAL;
+        if (animateBg) {
+            play(skippable, mTaskbarBackgroundOffset.animateToValue(backgroundOffsetTarget), 0,
+                    duration, EMPHASIZED);
+        } else {
+            skippable.addListener(AnimatorListeners.forEndCallback(
+                    () -> mTaskbarBackgroundOffset.updateValue(backgroundOffsetTarget)));
+        }
+
+        play(skippable, mIconAlphaForStash.animateToValue(iconAlphaTarget), iconAlphaStartDelay,
+                iconAlphaDuration,
+                LINEAR);
 
         if (isStashed) {
-            if (animateBg) {
-                play(as, mTaskbarBackgroundOffset.animateToValue(1), 0, duration, EMPHASIZED);
-            } else {
-                as.addListener(AnimatorListeners.forEndCallback(
-                        () -> mTaskbarBackgroundOffset.updateValue(1)));
-            }
-
-            long alphaStartDelay = duration == 0 ? 0 : (changedFlags == FLAG_IN_APP)
-                    ? TASKBAR_STASH_ICON_ALPHA_HOME_TO_APP_START_DELAY
-                    : TASKBAR_STASH_ALPHA_START_DELAY;
-            long alphaDuration = duration == 0 ? 0 : TASKBAR_STASH_ALPHA_DURATION;
-            play(as, mIconAlphaForStash.animateToValue(0), alphaStartDelay, alphaDuration, LINEAR);
-            play(as, mTaskbarStashedHandleAlpha.animateToValue(1), alphaStartDelay,
-                    Math.max(0, duration - alphaStartDelay), LINEAR);
-
-            play(as, mControllers.taskbarSpringOnStashController.createSpringToStash(), 0, duration,
-                    LINEAR);
-
-            if (skipStashAnimation) {
-                skipInterpolator = INSTANT;
-            }
-        } else  {
-            if (animateBg) {
-                play(as, mTaskbarBackgroundOffset.animateToValue(0), 0, duration, EMPHASIZED);
-            } else {
-                as.addListener(AnimatorListeners.forEndCallback(
-                        () -> mTaskbarBackgroundOffset.updateValue(0)));
-            }
-
-            long alphaStartDelay = duration == 0 ? 0 : TASKBAR_STASH_ALPHA_START_DELAY;
-            long alphaDuration = duration == 0 ? 0 : TASKBAR_STASH_ALPHA_DURATION;
-            play(as, mIconAlphaForStash.animateToValue(1), alphaStartDelay, alphaDuration, LINEAR);
-            play(as, mTaskbarStashedHandleAlpha.animateToValue(0), 0, alphaDuration, LINEAR);
-
-            if (skipStashAnimation) {
-                skipInterpolator = FINAL_FRAME;
-            }
+            play(skippable, mControllers.taskbarSpringOnStashController.createSpringToStash(),
+                    0, duration, LINEAR);
         }
-        mControllers.taskbarViewController.addRevealAnimToIsStashed(as, isStashed, duration,
+
+        mControllers.taskbarViewController.addRevealAnimToIsStashed(skippable, isStashed, duration,
                 EMPHASIZED);
 
-        if (skipInterpolator != null) {
-            as.setInterpolator(skipInterpolator);
-        }
-
-        play(as, mControllers.stashedHandleViewController
+        play(skippable, mControllers.stashedHandleViewController
                 .createRevealAnimToIsStashed(isStashed), 0, duration, EMPHASIZED);
 
         // Return the stashed handle to its default scale in case it was changed as part of the
         // feedforward hint. Note that the reveal animation above also visually scales it.
-        as.play(mTaskbarStashedHandleHintScale.animateToValue(1f)
+        skippable.play(mTaskbarStashedHandleHintScale.animateToValue(1f)
                 .setDuration(isStashed ? duration / 2 : duration));
     }
 
@@ -927,17 +978,10 @@
         updateStateForFlag(FLAG_STASHED_SYSUI,
                 hasAnyFlag(systemUiStateFlags, SYSUI_STATE_SCREEN_PINNING));
 
-        boolean isLocked = hasAnyFlag(systemUiStateFlags, MASK_ANY_SYSUI_LOCKED);
-        boolean wasLocked = hasAnyFlag(FLAG_STASHED_DEVICE_LOCKED);
+        boolean isLocked = hasAnyFlag(systemUiStateFlags, MASK_ANY_SYSUI_LOCKED)
+                && !hasAnyFlag(systemUiStateFlags, SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY);
         updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED, isLocked);
 
-        if (isLocked && !wasLocked && DisplayController.isTransientTaskbar(mActivity)) {
-            // Stash the transient taskbar when locking the device. This improves the transition
-            // to AoD (otherwise the taskbar stays a bit too long above the collapsing AoD scrim),
-            // and ensures the taskar state is reset when unlocking the device afterwards.
-            updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, true);
-        }
-
         // Only update FLAG_STASHED_IN_APP_IME when system gesture is not in progress.
         mIsImeShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SHOWING);
         mIsImeSwitcherShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SWITCHER_SHOWING);
@@ -1150,9 +1194,11 @@
         private final IntPredicate mStashCondition;
 
         private boolean mIsStashed;
-        private boolean mIsHotseatIconOnTopWhenAligned;
+        private @StashAnimation int mLastStartedTransitionType = TRANSITION_DEFAULT;
         private int mPrevFlags;
 
+        private long mLastUnlockTransitionTimeout = 0;
+
         StatePropertyHolder(IntPredicate stashCondition) {
             mStashCondition = stashCondition;
         }
@@ -1170,7 +1216,7 @@
 
             if (DEBUG) {
                 String stateString = formatFlagChange(flags, mPrevFlags,
-                            TaskbarStashController::getStateString);
+                        TaskbarStashController::getStateString);
                 Log.d(TAG, "createSetStateAnimator: flags: " + stateString
                         + ", duration: " + duration
                         + ", isStashed: " + isStashed
@@ -1182,28 +1228,76 @@
                 onStateChangeApplied(changedFlags);
                 mPrevFlags = flags;
             }
-            boolean isHotseatIconOnTopWhenAligned =
-                    mControllers.uiController.isHotseatIconOnTopWhenAligned();
-            // If an animation has started and mIsHotseatIconOnTopWhenAligned is changed, we need
-            // to restart the animation with new parameters.
-            if (mIsStashed != isStashed
-                    || (mIsHotseatIconOnTopWhenAligned != isHotseatIconOnTopWhenAligned
-                    && mAnimator != null && mAnimator.isStarted())) {
+
+            boolean isUnlockTransition = hasAnyFlag(changedFlags, FLAG_STASHED_DEVICE_LOCKED)
+                    && !hasAnyFlag(FLAG_STASHED_DEVICE_LOCKED);
+            if (isUnlockTransition) {
+                // the launcher might not be resumed at the time the device is considered
+                // unlocked (when the keyguard goes away), but possibly shortly afterwards.
+                // To play the unlock transition at the time the unstash animation actually happens,
+                // this memoizes the state transition for UNLOCK_TRANSITION_MEMOIZATION_MS.
+                mLastUnlockTransitionTimeout =
+                        SystemClock.elapsedRealtime() + UNLOCK_TRANSITION_MEMOIZATION_MS;
+            }
+
+            @StashAnimation int animationType = computeTransitionType(changedFlags);
+
+            // Allow re-starting animation if upgrading from default animation type, otherwise
+            // stick with the already started transition.
+            boolean transitionTypeChanged = mAnimator != null && mAnimator.isStarted()
+                    && mLastStartedTransitionType == TRANSITION_DEFAULT
+                    && animationType != TRANSITION_DEFAULT;
+
+            if (mIsStashed != isStashed || transitionTypeChanged) {
                 if (TestProtocol.sDebugTracing) {
                     Log.d(TestProtocol.TASKBAR_IN_APP_STATE, String.format(
-                            "setState: mIsStashed=%b, isStashed=%b, duration=%d",
+                            "setState: mIsStashed=%b, isStashed=%b, "
+                                    + "mAnimationType=%d, animationType=%d, duration=%d",
                             mIsStashed,
                             isStashed,
+                            mLastStartedTransitionType,
+                            animationType,
                             duration));
                 }
                 mIsStashed = isStashed;
-                mIsHotseatIconOnTopWhenAligned = isHotseatIconOnTopWhenAligned;
+                mLastStartedTransitionType = animationType;
 
                 // This sets mAnimator.
-                createAnimToIsStashed(mIsStashed, duration, /* animateBg= */ true, changedFlags);
+                createAnimToIsStashed(mIsStashed, duration, animationType);
                 return mAnimator;
             }
             return null;
         }
+
+        private @StashAnimation int computeTransitionType(int changedFlags) {
+
+            boolean hotseatHiddenDuringAppLaunch =
+                    !mControllers.uiController.isHotseatIconOnTopWhenAligned()
+                            && hasAnyFlag(changedFlags, FLAG_IN_APP);
+            if (hotseatHiddenDuringAppLaunch) {
+                // When launching an app from the all-apps drawer, the hotseat is hidden behind the
+                // drawer. In this case, the navbar must just fade in, without a stash transition,
+                // as the taskbar stash animation would otherwise be visible above the all-apps
+                // drawer once the hotseat is detached.
+                return TRANSITION_HANDLE_FADE;
+            }
+
+            boolean isUnlockTransition =
+                    SystemClock.elapsedRealtime() < mLastUnlockTransitionTimeout;
+            if (isUnlockTransition) {
+                // When transitioning to unlocked device, the  hotseat will already be visible on
+                // the homescreen, thus do not play an un-stash animation.
+                // Keep isUnlockTransition in sync with its counterpart in
+                // TaskbarLauncherStateController#onStateChangeApplied.
+                return TRANSITION_HANDLE_FADE;
+            }
+
+            boolean homeToApp = hasAnyFlag(changedFlags, FLAG_IN_APP) && hasAnyFlag(FLAG_IN_APP);
+            if (homeToApp) {
+                return TRANSITION_HOME_TO_APP;
+            }
+
+            return TRANSITION_DEFAULT;
+        }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java
index 065d111..4b18bb6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java
@@ -54,6 +54,7 @@
     private boolean mHasSprungOnceThisGesture;
     private @Nullable ValueAnimator mSpringBounce;
     private boolean mGestureEnded;
+    private boolean mGestureInProgress;
     private boolean mAnimationToHomeRunning;
 
     private final boolean mIsTransientTaskbar;
@@ -123,6 +124,7 @@
 
     private void reset() {
         mGestureEnded = false;
+        mGestureInProgress = false;
         mHasSprungOnceThisGesture = false;
     }
 
@@ -134,18 +136,24 @@
     }
 
     /**
-     * Returns true if we will animate to zero before the input duration.
+     * Returns {@code true} if we should reset the animation back to zero.
+     *
+     * Returns {@code false} if there is a gesture in progress, or if we are already animating
+     * to 0 within the specified duration.
      */
-    public boolean willAnimateToZeroBefore(long duration) {
+    public boolean shouldResetBackToZero(long duration) {
+        if (mGestureInProgress) {
+            return false;
+        }
         if (mSpringBounce != null && mSpringBounce.isRunning()) {
             long springDuration = mSpringBounce.getDuration();
             long current = mSpringBounce.getCurrentPlayTime();
-            return (springDuration - current < duration);
+            return (springDuration - current >= duration);
         }
         if (mTranslationYForSwipe.isAnimatingToValue(0)) {
-            return mTranslationYForSwipe.getRemainingTime() < duration;
+            return mTranslationYForSwipe.getRemainingTime() >= duration;
         }
-        return false;
+        return true;
     }
 
     /**
@@ -188,6 +196,7 @@
             mAnimationToHomeRunning = false;
             cancelSpringIfExists();
             reset();
+            mGestureInProgress = true;
         }
         /**
          * Called when there is movement to move the taskbar.
@@ -211,6 +220,7 @@
                 mGestureEnded = true;
                 startSpring();
             }
+            mGestureInProgress = false;
         }
     }
 
@@ -222,6 +232,7 @@
         pw.println(prefix + "\tmHasSprungOnceThisGesture=" + mHasSprungOnceThisGesture);
         pw.println(prefix + "\tmAnimationToHomeRunning=" + mAnimationToHomeRunning);
         pw.println(prefix + "\tmGestureEnded=" + mGestureEnded);
+        pw.println(prefix + "\tmGestureInProgress=" + mGestureInProgress);
         pw.println(prefix + "\tmSpringBounce is running=" + (mSpringBounce != null
                 && mSpringBounce.isRunning()));
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index b9242b2..1435cb0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -244,7 +244,8 @@
                                     taskAttributes.getIconView().getDrawable(),
                                     taskAttributes.getThumbnailView(),
                                     taskAttributes.getThumbnailView().getThumbnail(),
-                                    null /* intent */);
+                                    null /* intent */,
+                                    null /* user */);
                             return;
                         }
                     }
@@ -256,7 +257,8 @@
                             new BitmapDrawable(info.bitmap.icon),
                             startingView,
                             null /* thumbnail */,
-                            intent);
+                            intent,
+                            info.user);
                 }
         );
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 87df5b0..a3e6814 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -57,7 +57,8 @@
 /**
  * Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
  */
-public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconParent, Insettable {
+public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconParent, Insettable,
+        DeviceProfile.OnDeviceProfileChangeListener {
     private static final String TAG = TaskbarView.class.getSimpleName();
 
     private static final Rect sTmpRect = new Rect();
@@ -92,7 +93,7 @@
 
     private float mTransientTaskbarAllAppsButtonTranslationXOffset;
 
-    private final boolean mShouldTryStartAlign;
+    private boolean mShouldTryStartAlign;
 
     public TaskbarView(@NonNull Context context) {
         this(context, null);
@@ -121,8 +122,8 @@
                 resources.getDimension(isTransientTaskbar
                         ? R.dimen.transient_taskbar_all_apps_button_translation_x_offset
                         : R.dimen.taskbar_all_apps_button_translation_x_offset);
-        mShouldTryStartAlign = mActivityContext.isThreeButtonNav()
-                && resources.getBoolean(R.bool.start_align_taskbar);
+
+        onDeviceProfileChanged(mActivityContext.getDeviceProfile());
 
         int actualMargin = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
         int actualIconSize = mActivityContext.getDeviceProfile().taskbarIconSize;
@@ -162,6 +163,23 @@
     }
 
     @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mActivityContext.addOnDeviceProfileChangeListener(this);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mActivityContext.removeOnDeviceProfileChangeListener(this);
+    }
+
+    @Override
+    public void onDeviceProfileChanged(DeviceProfile dp) {
+        mShouldTryStartAlign = mActivityContext.isThreeButtonNav() && dp.startAlignTaskbar;
+    }
+
+    @Override
     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
         if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) {
             announceForAccessibility(mContext.getString(R.string.taskbar_a11y_shown_title));
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 1f4085f..5a883bf 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -88,11 +88,17 @@
     private final StateManager mStateManager;
     @Nullable
     private DepthController mDepthController;
-    private @StagePosition int mStagePosition;
+    private @StagePosition int mInitialStagePosition;
     private ItemInfo mItemInfo;
+    /** {@link #mInitialTaskIntent} and {@link #mInitialUser} (the user of the Intent) are set
+     * together when split is initiated from an Intent. */
     private Intent mInitialTaskIntent;
+    private UserHandle mInitialUser;
     private int mInitialTaskId = INVALID_TASK_ID;
+    /** {@link #mSecondTaskIntent} and {@link #mSecondUser} (the user of the Intent) are set
+     * together when split is confirmed with an Intent. */
     private Intent mSecondTaskIntent;
+    private UserHandle mSecondUser;
     private int mSecondTaskId = INVALID_TASK_ID;
     private boolean mRecentsAnimationRunning;
     /** If {@code true}, animates the existing task view split placeholder view */
@@ -102,8 +108,6 @@
      * split pair task view without wanting to animate current task dismissal overall
      */
     private boolean mDismissingFromSplitPair;
-    @Nullable
-    private UserHandle mUser;
     /** If not null, this is the TaskView we want to launch from */
     @Nullable
     private GroupedTaskView mLaunchingTaskView;
@@ -137,7 +141,7 @@
             mInitialTaskId = alreadyRunningTask;
         } else {
             mInitialTaskIntent = intent;
-            mUser = itemInfo.user;
+            mInitialUser = itemInfo.user;
         }
 
         setInitialData(stagePosition, splitEvent, itemInfo);
@@ -157,7 +161,7 @@
     private void setInitialData(@StagePosition int stagePosition,
             StatsLogManager.EventEnum splitEvent, ItemInfo itemInfo) {
         mItemInfo = itemInfo;
-        mStagePosition = stagePosition;
+        mInitialStagePosition = stagePosition;
         mSplitEvent = splitEvent;
     }
 
@@ -214,7 +218,7 @@
         Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
                 LogUtils.getShellShareableInstanceId();
         launchTasks(mInitialTaskId, mInitialTaskIntent, mSecondTaskId, mSecondTaskIntent,
-                mStagePosition, callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO,
+                mInitialStagePosition, callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO,
                 instanceIds.first);
 
         mStatsLogManager.logger()
@@ -231,8 +235,14 @@
         mSecondTaskId = task.key.id;
     }
 
-    public void setSecondTask(Intent intent) {
+    /**
+     * To be called as soon as user selects the second app (even if animations aren't complete)
+     * @param intent The second intent that will be launched.
+     * @param user The user of that intent.
+     */
+    public void setSecondTask(Intent intent, UserHandle user) {
         mSecondTaskIntent = intent;
+        mSecondUser = user;
     }
 
     /**
@@ -289,16 +299,17 @@
                         null /* options2 */, stagePosition, splitRatio, remoteTransition,
                         shellInstanceId);
             } else if (intent2 == null) {
-                launchIntentOrShortcut(intent1, options1, taskId2, stagePosition, splitRatio,
-                        remoteTransition, shellInstanceId);
+                launchIntentOrShortcut(intent1, mInitialUser, options1, taskId2, stagePosition,
+                        splitRatio, remoteTransition, shellInstanceId);
             } else if (intent1 == null) {
-                launchIntentOrShortcut(intent2, options1, taskId1,
+                launchIntentOrShortcut(intent2, mSecondUser, options1, taskId1,
                         getOppositeStagePosition(stagePosition), splitRatio, remoteTransition,
                         shellInstanceId);
             } else {
-                mSystemUiProxy.startIntents(getPendingIntent(intent1), options1.toBundle(),
-                        getPendingIntent(intent2), null /* options2 */, stagePosition,
-                        splitRatio, remoteTransition, shellInstanceId);
+                mSystemUiProxy.startIntents(getPendingIntent(intent1, mInitialUser),
+                        options1.toBundle(), getPendingIntent(intent2, mSecondUser),
+                        null /* options2 */, stagePosition, splitRatio, remoteTransition,
+                        shellInstanceId);
             }
         } else {
             final RemoteSplitLaunchAnimationRunner animationRunner =
@@ -312,60 +323,63 @@
                         taskId2, null /* options2 */, stagePosition, splitRatio, adapter,
                         shellInstanceId);
             } else if (intent2 == null) {
-                launchIntentOrShortcutLegacy(intent1, options1, taskId2, stagePosition, splitRatio,
-                        adapter, shellInstanceId);
+                launchIntentOrShortcutLegacy(intent1, mInitialUser, options1, taskId2,
+                        stagePosition, splitRatio, adapter, shellInstanceId);
             } else if (intent1 == null) {
-                launchIntentOrShortcutLegacy(intent2, options1, taskId1,
+                launchIntentOrShortcutLegacy(intent2, mSecondUser, options1, taskId1,
                         getOppositeStagePosition(stagePosition), splitRatio, adapter,
                         shellInstanceId);
             } else {
                 mSystemUiProxy.startIntentsWithLegacyTransition(
-                        getPendingIntent(intent1), getShortcutInfo(intent1), options1.toBundle(),
-                        getPendingIntent(intent2), getShortcutInfo(intent2), null /* options2 */,
-                        stagePosition, splitRatio, adapter, shellInstanceId);
+                        getPendingIntent(intent1, mInitialUser),
+                        getShortcutInfo(intent1, mInitialUser), options1.toBundle(),
+                        getPendingIntent(intent2, mSecondUser),
+                        getShortcutInfo(intent2, mSecondUser), null /* options2 */, stagePosition,
+                        splitRatio, adapter, shellInstanceId);
             }
         }
     }
 
-    private void launchIntentOrShortcut(Intent intent, ActivityOptions options1, int taskId,
-            @StagePosition int stagePosition, float splitRatio, RemoteTransition remoteTransition,
-            @Nullable InstanceId shellInstanceId) {
-        final ShortcutInfo shortcutInfo = getShortcutInfo(intent);
+    private void launchIntentOrShortcut(Intent intent, UserHandle user, ActivityOptions options1,
+            int taskId, @StagePosition int stagePosition, float splitRatio,
+            RemoteTransition remoteTransition, @Nullable InstanceId shellInstanceId) {
+        final ShortcutInfo shortcutInfo = getShortcutInfo(intent, user);
         if (shortcutInfo != null) {
             mSystemUiProxy.startShortcutAndTask(shortcutInfo,
                     options1.toBundle(), taskId, null /* options2 */, stagePosition,
                     splitRatio, remoteTransition, shellInstanceId);
         } else {
-            mSystemUiProxy.startIntentAndTask(getPendingIntent(intent), options1.toBundle(), taskId,
-                    null /* options2 */, stagePosition, splitRatio, remoteTransition,
-                    shellInstanceId);
+            mSystemUiProxy.startIntentAndTask(getPendingIntent(intent, user),
+                    options1.toBundle(), taskId, null /* options2 */, stagePosition, splitRatio,
+                    remoteTransition, shellInstanceId);
         }
     }
 
-    private void launchIntentOrShortcutLegacy(Intent intent, ActivityOptions options1, int taskId,
-            @StagePosition int stagePosition, float splitRatio, RemoteAnimationAdapter adapter,
+    private void launchIntentOrShortcutLegacy(Intent intent, UserHandle user,
+            ActivityOptions options1, int taskId, @StagePosition int stagePosition,
+            float splitRatio, RemoteAnimationAdapter adapter,
             @Nullable InstanceId shellInstanceId) {
-        final ShortcutInfo shortcutInfo = getShortcutInfo(intent);
+        final ShortcutInfo shortcutInfo = getShortcutInfo(intent, user);
         if (shortcutInfo != null) {
             mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(shortcutInfo,
                     options1.toBundle(), taskId, null /* options2 */, stagePosition,
                     splitRatio, adapter, shellInstanceId);
         } else {
-            mSystemUiProxy.startIntentAndTaskWithLegacyTransition(getPendingIntent(intent),
-                    options1.toBundle(), taskId, null /* options2 */, stagePosition, splitRatio,
-                    adapter, shellInstanceId);
+            mSystemUiProxy.startIntentAndTaskWithLegacyTransition(
+                    getPendingIntent(intent, user), options1.toBundle(), taskId,
+                    null /* options2 */, stagePosition, splitRatio, adapter, shellInstanceId);
         }
     }
 
-    private PendingIntent getPendingIntent(Intent intent) {
-        return intent == null ? null : (mUser != null
+    private PendingIntent getPendingIntent(Intent intent, UserHandle user) {
+        return intent == null ? null : (user != null
                 ? PendingIntent.getActivityAsUser(mContext, 0, intent,
-                FLAG_MUTABLE, null /* options */, mUser)
+                FLAG_MUTABLE, null /* options */, user)
                 : PendingIntent.getActivity(mContext, 0, intent, FLAG_MUTABLE));
     }
 
     public @StagePosition int getActiveSplitStagePosition() {
-        return mStagePosition;
+        return mInitialStagePosition;
     }
 
     public StatsLogManager.EventEnum getSplitEvent() {
@@ -377,7 +391,7 @@
     }
 
     @Nullable
-    private ShortcutInfo getShortcutInfo(Intent intent) {
+    private ShortcutInfo getShortcutInfo(Intent intent, UserHandle user) {
         if (intent == null || intent.getPackage() == null) {
             return null;
         }
@@ -389,7 +403,7 @@
 
         try {
             final Context context = mContext.createPackageContextAsUser(
-                    intent.getPackage(), 0 /* flags */, mUser);
+                    intent.getPackage(), 0 /* flags */, user);
             return new ShortcutInfo.Builder(context, shortcutId).build();
         } catch (PackageManager.NameNotFoundException e) {
             Log.w(TAG, "Failed to create a ShortcutInfo for " + intent.getPackage());
@@ -519,7 +533,9 @@
         mInitialTaskIntent = null;
         mSecondTaskId = INVALID_TASK_ID;
         mSecondTaskIntent = null;
-        mStagePosition = SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
+        mInitialUser = null;
+        mSecondUser = null;
+        mInitialStagePosition = SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
         mRecentsAnimationRunning = false;
         mLaunchingTaskView = null;
         mItemInfo = null;
diff --git a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
index e5c74dc..dd10c2d 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
@@ -24,6 +24,7 @@
 import android.content.Intent;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.os.UserHandle;
 import android.view.View;
 
 import com.android.launcher3.DeviceProfile;
@@ -66,21 +67,24 @@
         }
         Object tag = view.getTag();
         Intent intent;
+        UserHandle user;
         BitmapInfo bitmapInfo;
         if (tag instanceof WorkspaceItemInfo) {
             final WorkspaceItemInfo workspaceItemInfo = (WorkspaceItemInfo) tag;
             intent = workspaceItemInfo.intent;
+            user = workspaceItemInfo.user;
             bitmapInfo = workspaceItemInfo.bitmap;
         } else if (tag instanceof com.android.launcher3.model.data.AppInfo) {
             final com.android.launcher3.model.data.AppInfo appInfo =
                     (com.android.launcher3.model.data.AppInfo) tag;
             intent = appInfo.intent;
+            user = appInfo.user;
             bitmapInfo = appInfo.bitmap;
         } else {
             return false;
         }
 
-        mController.setSecondTask(intent);
+        mController.setSecondTask(intent, user);
 
         boolean isTablet = mLauncher.getDeviceProfile().isTablet;
         SplitAnimationTimings timings = AnimUtils.getDeviceSplitToConfirmTimings(isTablet);
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 6899f9e..904537f 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -4575,11 +4575,13 @@
      *                   is (either the ThumbnailView or the tapped icon).
      * @param intent If we are launching a fresh instance of the app, this is the Intent for it. If
      *               the second app is already running in Recents, this will be null.
+     * @param user If we are launching a fresh instance of the app, this is the UserHandle for it.
+     *             If the second app is already running in Recents, this will be null.
      * @return true if waiting for confirmation of second app or if split animations are running,
      *          false otherwise
      */
     public boolean confirmSplitSelect(TaskView containerTaskView, Task task, Drawable drawable,
-            View secondView, @Nullable Bitmap thumbnail, Intent intent) {
+            View secondView, @Nullable Bitmap thumbnail, Intent intent, UserHandle user) {
         if (canLaunchFullscreenTask()) {
             return false;
         }
@@ -4595,7 +4597,7 @@
             }
             mSplitSelectStateController.setSecondTask(task);
         } else {
-            mSplitSelectStateController.setSecondTask(intent);
+            mSplitSelectStateController.setSecondTask(intent, user);
         }
 
         RectF secondTaskStartingBounds = new RectF();
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index df90583..42589ce 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -761,7 +761,8 @@
         if (container != null) {
             return getRecentsView().confirmSplitSelect(this, container.getTask(),
                     container.getIconView().getDrawable(), container.getThumbnailView(),
-                    container.getThumbnailView().getThumbnail(), /* intent */ null);
+                    container.getThumbnailView().getThumbnail(), /* intent */ null,
+                    /* user */ null);
         }
         return false;
     }
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 7d6e83d..2dcde21 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -156,11 +156,11 @@
     <string name="accessibility_close" msgid="2277148124685870734">"बन्द गर्नुहोस्"</string>
     <string name="notification_dismissed" msgid="6002233469409822874">"सूचना खारेज गरियो"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"व्यक्तिगत"</string>
-    <string name="all_apps_work_tab" msgid="4884822796154055118">"कार्यसम्बन्धी"</string>
+    <string name="all_apps_work_tab" msgid="4884822796154055118">"कामसम्बन्धी"</string>
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"कार्य प्रोफाइल"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"कामसम्बन्धी एपहरूमा ब्याज अङ्कित हुन्छ र तपाईंका IT एड्मिन ती एप हेर्न सक्छन्"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"बुझेँ"</string>
-    <string name="work_apps_paused_title" msgid="3040901117349444598">"कार्यसम्बन्धी एपहरू पज गरिएका छन्"</string>
+    <string name="work_apps_paused_title" msgid="3040901117349444598">"कामसम्बन्धी एपहरू पज गरिएका छन्"</string>
     <string name="work_apps_paused_body" msgid="261634750995824906">"तपाईंका कामसम्बन्धी एपहरूले तपाईंलाई सूचना पठाउन, तपाईंको डिभाइसको ब्याट्री प्रयोग गर्न वा तपाईंको लोकेसन हेर्न सक्दैनन्"</string>
     <string name="work_apps_paused_content_description" msgid="5149623040804051095">"कामसम्बन्धी एपहरू अफ गरिएका छन्। तपाईंका कामसम्बन्धी एपहरूले तपाईंलाई सूचना पठाउन, तपाईंको डिभाइसको ब्याट्री प्रयोग गर्न वा तपाईंको लोकेसन हेर्न सक्दैनन्"</string>
     <string name="work_apps_paused_edu_banner" msgid="8872412121608402058">"कामसम्बन्धी एपमा ब्याज अङ्कित हुन्छ र तपाईंका IT एड्मिन ती एप हेर्न सक्नुहुन्छ"</string>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 682153f..417ae61 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -405,6 +405,16 @@
         <!-- defaults to iconTextSize, if not specified -->
         <attr name="iconTextSizeTwoPanelLandscape" format="float" />
 
+        <!-- If true, used to layout taskbar in 3 button navigation mode. -->
+        <!-- defaults to false if not specified -->
+        <attr name="startAlignTaskbar" format="boolean" />
+        <!-- defaults to startAlignTaskbar, if not specified -->
+        <attr name="startAlignTaskbarLandscape" format="boolean" />
+        <!-- defaults to startAlignTaskbarLandscape, if not specified -->
+        <attr name="startAlignTaskbarTwoPanelLandscape" format="boolean" />
+        <!-- defaults to startAlignTaskbar, if not specified -->
+        <attr name="startAlignTaskbarTwoPanelPortrait" format="boolean" />
+
         <!-- If set, this display option is used to determine the default grid -->
         <attr name="canBeDefault" format="boolean" />
 
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index fcb220e..86c9f16 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -267,6 +267,8 @@
     public final int stashedTaskbarHeight;
     public final int taskbarBottomMargin;
     public final int taskbarIconSize;
+    // If true, used to layout taskbar in 3 button navigation mode.
+    public final boolean startAlignTaskbar;
 
     // DragController
     public int flingToDeleteThresholdVelocity;
@@ -338,12 +340,14 @@
                     res.getDimensionPixelSize(R.dimen.transient_taskbar_stashed_height);
             taskbarBottomMargin =
                     res.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin);
+            startAlignTaskbar = false;
         } else {
             taskbarIconSize = pxFromDp(ResourcesCompat.getFloat(res, R.dimen.taskbar_icon_size),
                     mMetrics);
             taskbarHeight = res.getDimensionPixelSize(R.dimen.taskbar_size);
             stashedTaskbarHeight = res.getDimensionPixelSize(R.dimen.taskbar_stashed_size);
             taskbarBottomMargin = 0;
+            startAlignTaskbar = inv.startAlignTaskbar[mTypeIndex];
         }
 
         edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 485b36c..4c34648 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -149,6 +149,8 @@
 
     public float[] transientTaskbarIconSize;
 
+    public boolean[] startAlignTaskbar;
+
     /**
      * Number of icons inside the hotseat area.
      */
@@ -394,6 +396,8 @@
 
         transientTaskbarIconSize = displayOption.transientTaskbarIconSize;
 
+        startAlignTaskbar = displayOption.startAlignTaskbar;
+
         // If the partner customization apk contains any grid overrides, apply them
         // Supported overrides: numRows, numColumns, iconSize
         applyPartnerDeviceProfileOverrides(context, metrics);
@@ -915,6 +919,8 @@
 
         private final float[] transientTaskbarIconSize = new float[COUNT_SIZES];
 
+        private final boolean[] startAlignTaskbar = new boolean[COUNT_SIZES];
+
         DisplayOption(GridOption grid, Context context, AttributeSet attrs) {
             this.grid = grid;
 
@@ -1147,6 +1153,18 @@
                     R.styleable.ProfileDisplayOption_transientTaskbarIconSizeTwoPanelPortrait,
                     transientTaskbarIconSize[INDEX_DEFAULT]);
 
+            startAlignTaskbar[INDEX_DEFAULT] = a.getBoolean(
+                    R.styleable.ProfileDisplayOption_startAlignTaskbar, false);
+            startAlignTaskbar[INDEX_LANDSCAPE] = a.getBoolean(
+                    R.styleable.ProfileDisplayOption_startAlignTaskbarLandscape,
+                    startAlignTaskbar[INDEX_DEFAULT]);
+            startAlignTaskbar[INDEX_TWO_PANEL_LANDSCAPE] = a.getBoolean(
+                    R.styleable.ProfileDisplayOption_startAlignTaskbarTwoPanelLandscape,
+                    startAlignTaskbar[INDEX_LANDSCAPE]);
+            startAlignTaskbar[INDEX_TWO_PANEL_PORTRAIT] = a.getBoolean(
+                    R.styleable.ProfileDisplayOption_startAlignTaskbarTwoPanelPortrait,
+                    startAlignTaskbar[INDEX_DEFAULT]);
+
             a.recycle();
         }
 
@@ -1169,6 +1187,7 @@
                 allAppsIconTextSizes[i] = 0;
                 allAppsBorderSpaces[i] = new PointF();
                 transientTaskbarIconSize[i] = 0;
+                startAlignTaskbar[i] = false;
             }
         }
 
@@ -1213,6 +1232,7 @@
                 allAppsBorderSpaces[i].x += p.allAppsBorderSpaces[i].x;
                 allAppsBorderSpaces[i].y += p.allAppsBorderSpaces[i].y;
                 transientTaskbarIconSize[i] += p.transientTaskbarIconSize[i];
+                startAlignTaskbar[i] |= p.startAlignTaskbar[i];
             }
 
             return this;
diff --git a/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt b/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
index a5f33c0..c22cf40 100644
--- a/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
+++ b/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
@@ -153,6 +153,9 @@
 
                 isScalable = true
 
+                transientTaskbarIconSize = FloatArray(4) { 44f }
+                startAlignTaskbar = BooleanArray(4) { false }
+
                 inlineQsb = BooleanArray(4) { false }
 
                 devicePaddingId = R.xml.paddings_handhelds
@@ -233,6 +236,9 @@
                 isScalable = true
                 devicePaddingId = R.xml.paddings_6x5
 
+                transientTaskbarIconSize = FloatArray(4) { 44f }
+                startAlignTaskbar = booleanArrayOf(true, false, true, true)
+
                 inlineQsb = booleanArrayOf(false, true, false, false)
 
                 devicePaddingId = R.xml.paddings_handhelds
@@ -308,6 +314,9 @@
 
                 isScalable = true
 
+                transientTaskbarIconSize = FloatArray(4) { 44f }
+                startAlignTaskbar = BooleanArray(4) { true }
+
                 inlineQsb = booleanArrayOf(false, false, false, false)
 
                 devicePaddingId = R.xml.paddings_handhelds