Merge "Support two line text in AllApps/OnDeviceSearch w/ feature flag" into tm-qpr-dev
diff --git a/quickstep/res/values-sw600dp/config.xml b/quickstep/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..e1e442f
--- /dev/null
+++ b/quickstep/res/values-sw600dp/config.xml
@@ -0,0 +1,21 @@
+<?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
new file mode 100644
index 0000000..bf0f9ad
--- /dev/null
+++ b/quickstep/res/values-sw720dp-land/config.xml
@@ -0,0 +1,21 @@
+<?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
new file mode 100644
index 0000000..e1e442f
--- /dev/null
+++ b/quickstep/res/values-sw720dp/config.xml
@@ -0,0 +1,21 @@
+<?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 d581582..b61bdfb 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -51,4 +51,8 @@
     </item>
 
     <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>
 </resources>
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index f5d7f3b..8c21778 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.taskbar;
 
 import static android.view.HapticFeedbackConstants.LONG_PRESS;
+import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
 
 import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
 import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
@@ -43,6 +44,7 @@
 import android.view.InsetsController;
 import android.view.View;
 import android.view.ViewConfiguration;
+import android.view.accessibility.AccessibilityManager;
 import android.view.animation.Interpolator;
 
 import androidx.annotation.NonNull;
@@ -189,6 +191,7 @@
     // Stashed handle properties.
     private MultiProperty mTaskbarStashedHandleAlpha;
     private AnimatedFloat mTaskbarStashedHandleHintScale;
+    private final AccessibilityManager mAccessibilityManager;
 
     /** Whether we are currently visually stashed (might change based on launcher state). */
     private boolean mIsStashed = false;
@@ -221,6 +224,7 @@
         mActivity = activity;
         mPrefs = LauncherPrefs.getPrefs(mActivity);
         mSystemUiProxy = SystemUiProxy.INSTANCE.get(activity);
+        mAccessibilityManager = mActivity.getSystemService(AccessibilityManager.class);
         if (isPhoneMode()) {
             // DeviceProfile's taskbar vars aren't initialized w/ the flag off
             Resources resources = mActivity.getResources();
@@ -1018,7 +1022,15 @@
         cancelTimeoutIfExists();
 
         mTimeoutAlarm.setOnAlarmListener(this::onTaskbarTimeout);
-        mTimeoutAlarm.setAlarm(NO_TOUCH_TIMEOUT_TO_STASH_MS);
+        mTimeoutAlarm.setAlarm(getTaskbarAutoHideTimeout());
+    }
+
+    /**
+     * returns appropriate timeout for taskbar to stash depending on accessibility being on/off.
+     */
+    private long getTaskbarAutoHideTimeout() {
+        return mAccessibilityManager.getRecommendedTimeoutMillis((int) NO_TOUCH_TIMEOUT_TO_STASH_MS,
+                FLAG_CONTENT_CONTROLS);
     }
 
     private void onTaskbarTimeout(Alarm alarm) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 11b07e8..e66856a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -94,6 +94,8 @@
 
     private float mTransientTaskbarAllAppsButtonTranslationXOffset;
 
+    private final boolean mStartAlignTaskbar;
+
     public TaskbarView(@NonNull Context context) {
         this(context, null);
     }
@@ -121,6 +123,8 @@
                 resources.getDimension(isTransientTaskbar
                         ? R.dimen.transient_taskbar_all_apps_button_translation_x_offset
                         : R.dimen.taskbar_all_apps_button_translation_x_offset);
+        mStartAlignTaskbar = mActivityContext.isThreeButtonNav()
+                && resources.getBoolean(R.bool.start_align_taskbar);
 
         int actualMargin = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
         int actualIconSize = mActivityContext.getDeviceProfile().iconSizePx;
@@ -372,10 +376,22 @@
         boolean needMoreSpaceForNav = layoutRtl ?
                 navSpaceNeeded > (iconEnd - spaceNeeded) :
                 iconEnd > (right - navSpaceNeeded);
-        if (needMoreSpaceForNav) {
-            int offset = layoutRtl ?
-                    navSpaceNeeded - (iconEnd - spaceNeeded) :
-                    (right - navSpaceNeeded) - iconEnd;
+
+        if (mStartAlignTaskbar) {
+            // Taskbar is aligned to the start
+            int startSpacingPx = deviceProfile.inlineNavButtonsEndSpacingPx;
+
+            if (layoutRtl) {
+                iconEnd = right - startSpacingPx;
+            } else {
+                iconEnd = startSpacingPx + spaceNeeded;
+            }
+        } else if (needMoreSpaceForNav) {
+            // Add offset to account for nav bar when taskbar is centered
+            int offset = layoutRtl
+                    ? navSpaceNeeded - (iconEnd - spaceNeeded)
+                    : (right - navSpaceNeeded) - iconEnd;
+
             iconEnd += offset;
         }
 
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
new file mode 100644
index 0000000..6dd67de
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -0,0 +1,163 @@
+/*
+ *  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.
+ *
+ */
+
+package com.android.quickstep.util
+
+import android.animation.ObjectAnimator
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.view.View
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.anim.PendingAnimation
+import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource
+import com.android.quickstep.views.TaskThumbnailView
+import com.android.quickstep.views.TaskView
+import com.android.quickstep.views.TaskView.TaskIdAttributeContainer
+import java.util.function.Supplier
+
+/**
+ * Utils class to help run animations for initiating split screen from launcher.
+ * Will be expanded with future refactors. Works in conjunction with the state stored in
+ * [SplitSelectStateController]
+ */
+class SplitAnimationController(val splitSelectStateController: SplitSelectStateController) {
+    companion object {
+        // Break this out into maybe enums? Abstractions into its own classes? Tbd.
+        data class SplitAnimInitProps(
+                val originalView: View,
+                val originalBitmap: Bitmap?,
+                val iconDrawable: Drawable,
+                val fadeWithThumbnail: Boolean,
+                val isStagedTask: Boolean,
+                val iconView: View?
+        )
+    }
+
+    /**
+     * Returns different elements to animate for the initial split selection animation
+     * depending on the state of the surface from which the split was initiated
+     */
+    fun getFirstAnimInitViews(taskViewSupplier: Supplier<TaskView>,
+                              splitSelectSourceSupplier: Supplier<SplitSelectSource>)
+            : SplitAnimInitProps {
+        if (!splitSelectStateController.isAnimateCurrentTaskDismissal) {
+            // Initiating from home
+            val splitSelectSource = splitSelectSourceSupplier.get()
+            return SplitAnimInitProps(splitSelectSource.view, originalBitmap = null,
+                    splitSelectSource.drawable, fadeWithThumbnail = false, isStagedTask = true,
+                    iconView = null)
+        } else if (splitSelectStateController.isDismissingFromSplitPair) {
+            // Initiating split from overview, but on a split pair
+            val taskView = taskViewSupplier.get()
+            for (container : TaskIdAttributeContainer in taskView.taskIdAttributeContainers) {
+                if (container.task.key.id == splitSelectStateController.initialTaskId) {
+                    return SplitAnimInitProps(container.thumbnailView,
+                            container.thumbnailView.thumbnail, container.iconView.drawable!!,
+                            fadeWithThumbnail = true, isStagedTask = true,
+                            iconView = container.iconView
+                    )
+                }
+            }
+            throw IllegalStateException("Attempting to init split from existing split pair " +
+                    "without a valid taskIdAttributeContainer")
+        } else {
+            // Initiating split from overview on fullscreen task TaskView
+            val taskView = taskViewSupplier.get()
+            return SplitAnimInitProps(taskView.thumbnail, taskView.thumbnail.thumbnail,
+                    taskView.iconView.drawable!!, fadeWithThumbnail = true, isStagedTask = true,
+                    taskView.iconView
+            )
+        }
+    }
+
+    /**
+     * When selecting first app from split pair, second app's thumbnail remains. This animates
+     * the second thumbnail by expanding it to take up the full taskViewWidth/Height and overlaying
+     * it with [TaskThumbnailView]'s splashView. Adds animations to the provided builder.
+     * Note: The app that **was not** selected as the first split app should be the container that's
+     * passed through.
+     *
+     * @param builder Adds animation to this
+     * @param taskIdAttributeContainer container of the app that **was not** selected
+     * @param isPrimaryTaskSplitting if true, task that was split would be top/left in the pair
+     *                               (opposite of that representing [taskIdAttributeContainer])
+     */
+    fun addInitialSplitFromPair(taskIdAttributeContainer: TaskIdAttributeContainer,
+                                builder: PendingAnimation, deviceProfile: DeviceProfile,
+                                taskViewWidth: Int, taskViewHeight: Int,
+                                isPrimaryTaskSplitting: Boolean) {
+        val thumbnail = taskIdAttributeContainer.thumbnailView
+        val iconView: View = taskIdAttributeContainer.iconView
+        builder.add(ObjectAnimator.ofFloat(thumbnail, TaskThumbnailView.SPLASH_ALPHA, 1f))
+        thumbnail.setShowSplashForSplitSelection(true)
+        if (deviceProfile.isLandscape) {
+            // Center view first so scaling happens uniformly, alternatively we can move pivotX to 0
+            val centerThumbnailTranslationX: Float = (taskViewWidth - thumbnail.width) / 2f
+            val centerIconTranslationX: Float = (taskViewWidth - iconView.width) / 2f
+            val finalScaleX: Float = taskViewWidth.toFloat() / thumbnail.width
+            builder.add(ObjectAnimator.ofFloat(thumbnail,
+                    TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, centerThumbnailTranslationX))
+            // icons are anchored from Gravity.END, so need to use negative translation
+            builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X,
+                    -centerIconTranslationX))
+            builder.add(ObjectAnimator.ofFloat(thumbnail, View.SCALE_X, finalScaleX))
+
+            // Reset other dimensions
+            // TODO(b/271468547), can't set Y translate to 0, need to account for top space
+            thumbnail.scaleY = 1f
+            val translateYResetVal: Float = if (!isPrimaryTaskSplitting) 0f else
+                deviceProfile.overviewTaskThumbnailTopMarginPx.toFloat()
+            builder.add(ObjectAnimator.ofFloat(thumbnail,
+                    TaskThumbnailView.SPLIT_SELECT_TRANSLATE_Y,
+                    translateYResetVal))
+        } else {
+            val thumbnailSize = taskViewHeight - deviceProfile.overviewTaskThumbnailTopMarginPx
+            // Center view first so scaling happens uniformly, alternatively we can move pivotY to 0
+            // primary thumbnail has layout margin above it, so secondary thumbnail needs to take
+            // that into account. We should migrate to only using translations otherwise this
+            // asymmetry causes problems..
+
+            // Icon defaults to center | horizontal, we add additional translation for split
+            val centerIconTranslationX = 0f
+            var centerThumbnailTranslationY: Float
+
+            // TODO(b/271468547), primary thumbnail has layout margin above it, so secondary
+            //  thumbnail needs to take that into account. We should migrate to only using
+            //  translations otherwise this asymmetry causes problems..
+            if (isPrimaryTaskSplitting) {
+                centerThumbnailTranslationY = (thumbnailSize - thumbnail.height) / 2f
+                centerThumbnailTranslationY += deviceProfile.overviewTaskThumbnailTopMarginPx
+                        .toFloat()
+            } else {
+                centerThumbnailTranslationY = (thumbnailSize - thumbnail.height) / 2f
+            }
+            val finalScaleY: Float = thumbnailSize.toFloat() / thumbnail.height
+            builder.add(ObjectAnimator.ofFloat(thumbnail,
+                    TaskThumbnailView.SPLIT_SELECT_TRANSLATE_Y, centerThumbnailTranslationY))
+
+            // icons are anchored from Gravity.END, so need to use negative translation
+            builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X,
+                    centerIconTranslationX))
+            builder.add(ObjectAnimator.ofFloat(thumbnail, View.SCALE_Y, finalScaleY))
+
+            // Reset other dimensions
+            thumbnail.scaleX = 1f
+            builder.add(ObjectAnimator.ofFloat(thumbnail,
+                    TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, 0f))
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 5214f7c..1f4085f 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -82,6 +82,7 @@
     private final Context mContext;
     private final Handler mHandler;
     private final RecentsModel mRecentTasksModel;
+    private final SplitAnimationController mSplitAnimationController;
     private StatsLogManager mStatsLogManager;
     private final SystemUiProxy mSystemUiProxy;
     private final StateManager mStateManager;
@@ -96,6 +97,11 @@
     private boolean mRecentsAnimationRunning;
     /** If {@code true}, animates the existing task view split placeholder view */
     private boolean mAnimateCurrentTaskDismissal;
+    /**
+     * Acts as a subset of {@link #mAnimateCurrentTaskDismissal}, we can't be dismissing from a
+     * 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 */
@@ -116,6 +122,7 @@
         mStateManager = stateManager;
         mDepthController = depthController;
         mRecentTasksModel = recentsModel;
+        mSplitAnimationController = new SplitAnimationController(this);
     }
 
     /**
@@ -399,6 +406,18 @@
         mAnimateCurrentTaskDismissal = animateCurrentTaskDismissal;
     }
 
+    public boolean isDismissingFromSplitPair() {
+        return mDismissingFromSplitPair;
+    }
+
+    public void setDismissingFromSplitPair(boolean dismissingFromSplitPair) {
+        mDismissingFromSplitPair = dismissingFromSplitPair;
+    }
+
+    public SplitAnimationController getSplitAnimationController() {
+        return mSplitAnimationController;
+    }
+
     /**
      * Requires Shell Transitions
      */
@@ -506,6 +525,7 @@
         mItemInfo = null;
         mSplitEvent = null;
         mAnimateCurrentTaskDismissal = false;
+        mDismissingFromSplitPair = false;
     }
 
     /**
@@ -532,6 +552,10 @@
         return mInitialTaskId;
     }
 
+    public int getSecondTaskId() {
+        return mSecondTaskId;
+    }
+
     private boolean isSecondTaskIntentSet() {
         return (mSecondTaskId != INVALID_TASK_ID || mSecondTaskIntent != null);
     }
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index 5414068..ccc2df6 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -496,7 +496,7 @@
     }
 
     @Override
-    void setThumbnailVisibility(int visibility) {
+    void setThumbnailVisibility(int visibility, int taskId) {
         for (int i = 0; i < mSnapshotViewMap.size(); i++) {
             mSnapshotViewMap.valueAt(i).setVisibility(visibility);
         }
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index e9498fd..0e05032 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -1,5 +1,7 @@
 package com.android.quickstep.views;
 
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+
 import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
 
@@ -25,6 +27,7 @@
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.util.CancellableTask;
 import com.android.quickstep.util.RecentsOrientedState;
+import com.android.quickstep.util.SplitSelectStateController;
 import com.android.quickstep.util.TaskViewSimulator;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -267,6 +270,19 @@
 
     @Override
     protected int getLastSelectedChildTaskIndex() {
+        SplitSelectStateController splitSelectController =
+                getRecentsView().getSplitSelectController();
+        if (splitSelectController.isDismissingFromSplitPair()) {
+            // return the container index of the task that wasn't initially selected to split with
+            // because that is the only remaining app that can be selected. The coordinate checks
+            // below aren't reliable since both of those views may be gone/transformed
+            int initSplitTaskId = getThisTaskCurrentlyInSplitSelection();
+            if (initSplitTaskId != INVALID_TASK_ID) {
+                return initSplitTaskId == mTask.key.id ? 1 : 0;
+            }
+        }
+
+        // Check which of the two apps was selected
         if (isCoordInView(mIconView2, mLastTouchDownPosition)
                 || isCoordInView(mSnapshotView2, mLastTouchDownPosition)) {
             return 1;
@@ -296,9 +312,30 @@
         if (mSplitBoundsConfig == null || mSnapshotView == null || mSnapshotView2 == null) {
             return;
         }
-        getPagedOrientationHandler().measureGroupedTaskViewThumbnailBounds(mSnapshotView,
-                mSnapshotView2, widthSize, heightSize, mSplitBoundsConfig,
-                mActivity.getDeviceProfile(), getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+        int initSplitTaskId = getThisTaskCurrentlyInSplitSelection();
+        if (initSplitTaskId == INVALID_TASK_ID) {
+            getPagedOrientationHandler().measureGroupedTaskViewThumbnailBounds(mSnapshotView,
+                    mSnapshotView2, widthSize, heightSize, mSplitBoundsConfig,
+                    mActivity.getDeviceProfile(), getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+            // Should we be having a separate translation step apart from the measuring above?
+            // The following only applies to large screen for now, but for future reference
+            // we'd want to abstract this out in PagedViewHandlers to get the primary/secondary
+            // translation directions
+            mSnapshotView.applySplitSelectTranslateX(mSnapshotView.getTranslationX());
+            mSnapshotView.applySplitSelectTranslateY(mSnapshotView.getTranslationY());
+            mSnapshotView2.applySplitSelectTranslateX(mSnapshotView2.getTranslationX());
+            mSnapshotView2.applySplitSelectTranslateY(mSnapshotView2.getTranslationY());
+        } else {
+            // Currently being split with this taskView, let the non-split selected thumbnail
+            // take up full thumbnail area
+            TaskIdAttributeContainer container =
+                    mTaskIdAttributeContainer[initSplitTaskId == mTask.key.id ? 1 : 0];
+            container.getThumbnailView().measure(widthMeasureSpec,
+                    View.MeasureSpec.makeMeasureSpec(
+                            heightSize -
+                                    mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx,
+                            MeasureSpec.EXACTLY));
+        }
         updateIconPlacement();
     }
 
@@ -379,21 +416,27 @@
         mSnapshotView2.refreshSplashView();
     }
 
+    @Override
+    protected void resetViewTransforms() {
+        super.resetViewTransforms();
+        mSnapshotView2.resetViewTransforms();
+    }
+
     /**
-     *     Sets visibility for thumbnails and associated elements (DWB banners).
-     *     IconView is unaffected.
+     * Sets visibility for thumbnails and associated elements (DWB banners).
+     * IconView is unaffected.
      *
-     *     When setting INVISIBLE, sets the visibility for the last selected child task.
-     *     When setting VISIBLE (as a reset), sets the visibility for both tasks.
+     * When setting INVISIBLE, sets the visibility for the last selected child task.
+     * When setting VISIBLE (as a reset), sets the visibility for both tasks.
      */
     @Override
-    void setThumbnailVisibility(int visibility) {
+    void setThumbnailVisibility(int visibility, int taskId) {
         if (visibility == VISIBLE) {
             mSnapshotView.setVisibility(visibility);
             mDigitalWellBeingToast.setBannerVisibility(visibility);
             mSnapshotView2.setVisibility(visibility);
             mDigitalWellBeingToast2.setBannerVisibility(visibility);
-        } else if (getLastSelectedChildTaskIndex() == 0) {
+        } else if (taskId == getTaskIds()[0]) {
             mSnapshotView.setVisibility(visibility);
             mDigitalWellBeingToast.setBannerVisibility(visibility);
         } else {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 717300e..614ef81 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -184,6 +184,7 @@
 import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.RecentsOrientedState;
+import com.android.quickstep.util.SplitAnimationController.Companion.SplitAnimInitProps;
 import com.android.quickstep.util.SplitAnimationTimings;
 import com.android.quickstep.util.SplitSelectStateController;
 import com.android.quickstep.util.SurfaceTransaction;
@@ -192,6 +193,7 @@
 import com.android.quickstep.util.TaskVisualsChangeListener;
 import com.android.quickstep.util.TransformParams;
 import com.android.quickstep.util.VibrationConstants;
+import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
 import com.android.systemui.plugins.ResourceProvider;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -935,7 +937,7 @@
         if (mHandleTaskStackChanges) {
             TaskView taskView = getTaskViewByTaskId(taskId);
             if (taskView != null) {
-                for (TaskView.TaskIdAttributeContainer container :
+                for (TaskIdAttributeContainer container :
                         taskView.getTaskIdAttributeContainers()) {
                     if (container == null || taskId != container.getTask().key.id) {
                         continue;
@@ -3099,29 +3101,26 @@
 
         RectF startingTaskRect = new RectF();
         safeRemoveDragLayerView(mFirstFloatingTaskView);
+        SplitAnimInitProps splitAnimInitProps =
+                mSplitSelectStateController.getSplitAnimationController().getFirstAnimInitViews(
+                        () -> mSplitHiddenTaskView, () -> mSplitSelectSource);
         if (mSplitSelectStateController.isAnimateCurrentTaskDismissal()) {
             // Create the split select animation from Overview
-            mSplitHiddenTaskView.setThumbnailVisibility(INVISIBLE);
-            anim.setViewAlpha(mSplitHiddenTaskView.getIconView(), 0, clampToProgress(LINEAR,
+            mSplitHiddenTaskView.setThumbnailVisibility(INVISIBLE,
+                    mSplitSelectStateController.getInitialTaskId());
+            anim.setViewAlpha(splitAnimInitProps.getIconView(), 0, clampToProgress(LINEAR,
                     timings.getIconFadeStartOffset(),
                     timings.getIconFadeEndOffset()));
-            mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
-                    mSplitHiddenTaskView.getThumbnail(),
-                    mSplitHiddenTaskView.getThumbnail().getThumbnail(),
-                    mSplitHiddenTaskView.getIconView().getDrawable(), startingTaskRect);
-            mFirstFloatingTaskView.setAlpha(1);
-            mFirstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
-                    true /* fadeWithThumbnail */, true /* isStagedTask */);
-        } else {
-            // Create the split select animation from Home
-            mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
-                    mSplitSelectSource.view, null /* thumbnail */,
-                    mSplitSelectSource.drawable, startingTaskRect);
-            mFirstFloatingTaskView.setAlpha(1);
-            mFirstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
-                    false /* fadeWithThumbnail */, true /* isStagedTask */);
         }
 
+        mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
+                splitAnimInitProps.getOriginalView(),
+                splitAnimInitProps.getOriginalBitmap(),
+                splitAnimInitProps.getIconDrawable(), startingTaskRect);
+        mFirstFloatingTaskView.setAlpha(1);
+        mFirstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
+                splitAnimInitProps.getFadeWithThumbnail(), splitAnimInitProps.isStagedTask());
+
         // Allow user to click staged app to launch into fullscreen
         if (ENABLE_LAUNCH_FROM_STAGED_APP.get()) {
             mFirstFloatingTaskView.setOnClickListener(this::animateToFullscreen);
@@ -4450,7 +4449,7 @@
         mTaskViewsSecondarySplitTranslation = translation;
         for (int i = 0; i < getTaskViewCount(); i++) {
             TaskView taskView = requireTaskViewAt(i);
-            if (taskView == mSplitHiddenTaskView) {
+            if (taskView == mSplitHiddenTaskView && !taskView.containsMultipleTasks()) {
                 continue;
             }
             taskView.getSecondarySplitTranslationProperty().set(taskView, translation);
@@ -4501,6 +4500,10 @@
         mSplitHiddenTaskViewIndex = indexOfChild(mSplitHiddenTaskView);
         mSplitSelectStateController
                 .setAnimateCurrentTaskDismissal(splitSelectSource.animateCurrentTaskDismissal);
+
+        // Prevent dismissing whole task if we're only initiating from one of 2 tasks in split pair
+        mSplitSelectStateController.setDismissingFromSplitPair(mSplitHiddenTaskView != null
+                && mSplitHiddenTaskView.containsMultipleTasks());
         mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent,
                 splitSelectSource.position.stagePosition, splitSelectSource.itemInfo,
                 splitSelectSource.splitEvent, splitSelectSource.alreadyRunningTaskId);
@@ -4519,8 +4522,32 @@
      * Modifies a PendingAnimation with the animations for entering split staging
      */
     public void createSplitSelectInitAnimation(PendingAnimation builder, int duration) {
-        if (mSplitSelectStateController.isAnimateCurrentTaskDismissal()) {
-            // Splitting from Overview
+        boolean isInitiatingSplitFromTaskView =
+                mSplitSelectStateController.isAnimateCurrentTaskDismissal();
+        boolean isInitiatingTaskViewSplitPair =
+                mSplitSelectStateController.isDismissingFromSplitPair();
+        if (isInitiatingSplitFromTaskView && isInitiatingTaskViewSplitPair) {
+            // Splitting from Overview for split pair task
+            createInitialSplitSelectAnimation(builder);
+
+            // Animate pair thumbnail into full thumbnail
+            boolean primaryTaskSelected =
+                    mSplitHiddenTaskView.getTaskIdAttributeContainers()[0].getTask().key.id ==
+                            mSplitSelectStateController.getInitialTaskId();
+            TaskIdAttributeContainer taskIdAttributeContainer = mSplitHiddenTaskView
+                    .getTaskIdAttributeContainers()[primaryTaskSelected ? 1 : 0];
+            TaskThumbnailView thumbnail = taskIdAttributeContainer.getThumbnailView();
+            mSplitSelectStateController.getSplitAnimationController()
+                    .addInitialSplitFromPair(taskIdAttributeContainer, builder,
+                            mActivity.getDeviceProfile(),
+                            mSplitHiddenTaskView.getWidth(), mSplitHiddenTaskView.getHeight(),
+                            primaryTaskSelected);
+            builder.addOnFrameCallback(() ->{
+                thumbnail.refreshSplashView();
+                mSplitHiddenTaskView.updateSnapshotRadius();
+            });
+        } else if (isInitiatingSplitFromTaskView) {
+            // Splitting from Overview for fullscreen task
             createTaskDismissAnimation(builder, mSplitHiddenTaskView, true, false, duration,
                     true /* dismissingForSplitSelection*/);
         } else {
@@ -4608,7 +4635,8 @@
 
         mSecondSplitHiddenView = containerTaskView;
         if (mSecondSplitHiddenView != null) {
-            mSecondSplitHiddenView.setThumbnailVisibility(INVISIBLE);
+            mSecondSplitHiddenView.setThumbnailVisibility(INVISIBLE,
+                    mSplitSelectStateController.getSecondTaskId());
         }
 
         InteractionJankMonitorWrapper.begin(this,
@@ -4634,7 +4662,7 @@
         }
 
         if (mSecondSplitHiddenView != null) {
-            mSecondSplitHiddenView.setThumbnailVisibility(VISIBLE);
+            mSecondSplitHiddenView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID);
             mSecondSplitHiddenView = null;
         }
 
@@ -4660,7 +4688,7 @@
         resetTaskVisuals();
         mSplitHiddenTaskViewIndex = -1;
         if (mSplitHiddenTaskView != null) {
-            mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE);
+            mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID);
             mSplitHiddenTaskView = null;
         }
         if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
@@ -5530,7 +5558,7 @@
         }
 
         taskView.setShowScreenshot(true);
-        for (TaskView.TaskIdAttributeContainer container :
+        for (TaskIdAttributeContainer container :
                 taskView.getTaskIdAttributeContainers()) {
             if (container == null) {
                 continue;
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index 432eadc..62a58f5 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -81,6 +81,47 @@
                 }
             };
 
+    public static final Property<TaskThumbnailView, Float> SPLASH_ALPHA =
+            new FloatProperty<TaskThumbnailView>("splashAlpha") {
+                @Override
+                public void setValue(TaskThumbnailView thumbnail, float splashAlpha) {
+                    thumbnail.setSplashAlpha(splashAlpha);
+                }
+
+                @Override
+                public Float get(TaskThumbnailView thumbnailView) {
+                    return thumbnailView.mSplashAlpha / 255f;
+                }
+            };
+
+    /** Use to animate thumbnail translationX while first app in split selection is initiated */
+    public static final Property<TaskThumbnailView, Float> SPLIT_SELECT_TRANSLATE_X =
+            new FloatProperty<TaskThumbnailView>("splitSelectTranslateX") {
+                @Override
+                public void setValue(TaskThumbnailView thumbnail, float splitSelectTranslateX) {
+                    thumbnail.applySplitSelectTranslateX(splitSelectTranslateX);
+                }
+
+                @Override
+                public Float get(TaskThumbnailView thumbnailView) {
+                    return thumbnailView.mSplitSelectTranslateX;
+                }
+            };
+
+    /** Use to animate thumbnail translationY while first app in split selection is initiated */
+    public static final Property<TaskThumbnailView, Float> SPLIT_SELECT_TRANSLATE_Y =
+            new FloatProperty<TaskThumbnailView>("splitSelectTranslateY") {
+                @Override
+                public void setValue(TaskThumbnailView thumbnail, float splitSelectTranslateY) {
+                    thumbnail.applySplitSelectTranslateY(splitSelectTranslateY);
+                }
+
+                @Override
+                public Float get(TaskThumbnailView thumbnailView) {
+                    return thumbnailView.mSplitSelectTranslateY;
+                }
+            };
+
     private final BaseActivity mActivity;
     @Nullable
     private TaskOverlay mOverlay;
@@ -111,6 +152,10 @@
     private int mSplashAlpha = 0;
 
     private boolean mOverlayEnabled;
+    /** Used as a placeholder when the original thumbnail animates out to. */
+    private boolean mShowSplashForSplitSelection;
+    private float mSplitSelectTranslateX;
+    private float mSplitSelectTranslateY;
 
     public TaskThumbnailView(Context context) {
         this(context, null);
@@ -342,10 +387,17 @@
 
         // Draw splash above thumbnail to hide inconsistencies in rotation and aspect ratios.
         if (shouldShowSplashView()) {
+            float cornerRadiusX = cornerRadius;
+            float cornerRadiusY = cornerRadius;
+            if (mShowSplashForSplitSelection) {
+                cornerRadiusX = cornerRadius / getScaleX();
+                cornerRadiusY = cornerRadius / getScaleY();
+            }
+
             // Always draw background for hiding inconsistencies, even if splash view is not yet
             // loaded (which can happen as task icons are loaded asynchronously in the background)
-            canvas.drawRoundRect(x, y, width + 1, height + 1, cornerRadius,
-                    cornerRadius, mSplashBackgroundPaint);
+            canvas.drawRoundRect(x, y, width + 1, height + 1, cornerRadiusX,
+                    cornerRadiusY, mSplashBackgroundPaint);
             if (mSplashView != null) {
                 mSplashView.layout((int) x, (int) (y + 1), (int) width, (int) height - 1);
                 mSplashView.draw(canvas);
@@ -353,6 +405,31 @@
         }
     }
 
+    /** See {@link #SPLIT_SELECT_TRANSLATE_X} */
+    protected void applySplitSelectTranslateX(float splitSelectTranslateX) {
+        mSplitSelectTranslateX = splitSelectTranslateX;
+        applyTranslateX();
+    }
+
+    /** See {@link #SPLIT_SELECT_TRANSLATE_Y} */
+    protected void applySplitSelectTranslateY(float splitSelectTranslateY) {
+        mSplitSelectTranslateY = splitSelectTranslateY;
+        applyTranslateY();
+    }
+
+    private void applyTranslateX() {
+        setTranslationX(mSplitSelectTranslateX);
+    }
+
+    private void applyTranslateY() {
+        setTranslationY(mSplitSelectTranslateY);
+    }
+
+    protected void resetViewTransforms() {
+        mSplitSelectTranslateX = 0;
+        mSplitSelectTranslateY = 0;
+    }
+
     public TaskView getTaskView() {
         return (TaskView) getParent();
     }
@@ -373,7 +450,12 @@
      */
     public boolean shouldShowSplashView() {
         return isThumbnailAspectRatioDifferentFromThumbnailData()
-                || isThumbnailRotationDifferentFromTask();
+                || isThumbnailRotationDifferentFromTask()
+                || mShowSplashForSplitSelection;
+    }
+
+    public void setShowSplashForSplitSelection(boolean showSplashForSplitSelection) {
+        mShowSplashForSplitSelection = showSplashForSplitSelection;
     }
 
     protected void refreshSplashView() {
@@ -396,7 +478,6 @@
 
         imageView.setScaleType(ImageView.ScaleType.MATRIX);
         Matrix matrix = new Matrix();
-
         float drawableWidth = mSplashViewDrawable.getIntrinsicWidth();
         float drawableHeight = mSplashViewDrawable.getIntrinsicHeight();
         float viewWidth = getMeasuredWidth();
@@ -408,12 +489,13 @@
         float nonGridScale = getTaskView() == null ? 1 : 1 / getTaskView().getNonGridScale();
         float recentsMaxScale = getTaskView() == null || getTaskView().getRecentsView() == null
                 ? 1 : 1 / getTaskView().getRecentsView().getMaxScaleForFullScreen();
-        float scale = nonGridScale * recentsMaxScale;
+        float scaleX = nonGridScale * recentsMaxScale * (1 / getScaleX());
+        float scaleY = nonGridScale * recentsMaxScale * (1 / getScaleY());
 
         // Center the image in the view.
         matrix.setTranslate(centeredDrawableLeft, centeredDrawableTop);
         // Apply scale transformation after translation, pivoting around center of view.
-        matrix.postScale(scale, scale, viewCenterX, viewCenterY);
+        matrix.postScale(scaleX, scaleY, viewCenterX, viewCenterY);
 
         imageView.setImageMatrix(matrix);
         mSplashView = imageView;
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index ab72f2d..a06e1f8 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -16,6 +16,7 @@
 
 package com.android.quickstep.views;
 
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.widget.Toast.LENGTH_SHORT;
 
@@ -41,6 +42,7 @@
 import android.animation.ObjectAnimator;
 import android.annotation.IdRes;
 import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Canvas;
@@ -706,9 +708,12 @@
         }
         SplitSelectStateController splitSelectStateController =
                 recentsView.getSplitSelectController();
-        if (splitSelectStateController.isSplitSelectActive() &&
-                splitSelectStateController.getInitialTaskId() == getTask().key.id) {
-            // Prevent taps on the this taskview if it's being animated into split select state
+        // Disable taps for split selection animation unless we have multiple tasks
+        boolean disableTapsForSplitSelect =
+                splitSelectStateController.isSplitSelectActive()
+                        && splitSelectStateController.getInitialTaskId() == getTask().key.id
+                        && !containsMultipleTasks();
+        if (disableTapsForSplitSelect) {
             return false;
         }
 
@@ -718,6 +723,25 @@
         return super.dispatchTouchEvent(ev);
     }
 
+    /**
+     * @return taskId that split selection was initiated with,
+     *         {@link ActivityTaskManager#INVALID_TASK_ID} if no tasks in this TaskView are part of
+     *         split selection
+     */
+    protected int getThisTaskCurrentlyInSplitSelection() {
+        SplitSelectStateController splitSelectController =
+                getRecentsView().getSplitSelectController();
+        int initSplitTaskId = INVALID_TASK_ID;
+        for (TaskIdAttributeContainer container : getTaskIdAttributeContainers()) {
+            int taskId = container.getTask().key.id;
+            if (taskId == splitSelectController.getInitialTaskId()) {
+                initSplitTaskId = taskId;
+                break;
+            }
+        }
+        return initSplitTaskId;
+    }
+
     private void onClick(View view) {
         if (getTask() == null) {
             return;
@@ -747,6 +771,8 @@
 
     /**
      * Returns the task index of the last selected child task (0 or 1).
+     * If we contain multiple tasks and this TaskView is used as part of split selection, the
+     * selected child task index will be that of the remaining task.
      */
     protected int getLastSelectedChildTaskIndex() {
         return 0;
@@ -1084,6 +1110,8 @@
         DeviceProfile deviceProfile = mActivity.getDeviceProfile();
         int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
 
+        // TODO(b/271468547), we should default to setting trasnlations only on the snapshot instead
+        //  of a hybrid of both margins and translations
         LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams();
         snapshotParams.topMargin = thumbnailTopMargin;
         mSnapshotView.setLayoutParams(snapshotParams);
@@ -1179,6 +1207,7 @@
         setAlpha(mStableAlpha);
         setIconScaleAndDim(1);
         setColorTint(0, 0);
+        mSnapshotView.resetViewTransforms();
     }
 
     public void setStableAlpha(float parentAlpha) {
@@ -1720,10 +1749,12 @@
     }
 
     /**
-     *     Sets visibility for the thumbnail and associated elements (DWB banners and action chips).
-     *     IconView is unaffected.
+     *  Sets visibility for the thumbnail and associated elements (DWB banners and action chips).
+     *  IconView is unaffected.
+     *
+     * @param taskId is only used when setting visibility to a non-{@link View#VISIBLE} value
      */
-    void setThumbnailVisibility(int visibility) {
+    void setThumbnailVisibility(int visibility, int taskId) {
         for (int i = 0; i < getChildCount(); i++) {
             View child = getChildAt(i);
             if (child != mIconView) {
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index eb6d096..9d5b08e 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -2,6 +2,7 @@
 
 import static android.os.Process.myUserHandle;
 
+import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.BroadcastReceiver;
@@ -51,10 +52,10 @@
      */
     @WorkerThread
     public static void restoreAppWidgetIds(Context context, int[] oldWidgetIds, int[] newWidgetIds,
-            @NonNull LauncherWidgetHolder holder) {
+            @NonNull AppWidgetHost host) {
         if (WidgetsModel.GO_DISABLE_WIDGETS) {
             Log.e(TAG, "Skipping widget ID remap as widgets not supported");
-            holder.deleteHost();
+            host.deleteHost();
             return;
         }
         if (!RestoreDbTask.isPending(context)) {
@@ -63,7 +64,7 @@
             Log.e(TAG, "Skipping widget ID remap as DB already in use");
             for (int widgetId : newWidgetIds) {
                 Log.d(TAG, "Deleting widgetId: " + widgetId);
-                holder.deleteAppWidgetId(widgetId);
+                host.deleteAppWidgetId(widgetId);
             }
             return;
         }
@@ -100,7 +101,7 @@
                 try {
                     if (!cursor.moveToFirst()) {
                         // The widget no long exists.
-                        holder.deleteAppWidgetId(newWidgetIds[i]);
+                        host.deleteAppWidgetId(newWidgetIds[i]);
                     }
                 } finally {
                     cursor.close();
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 352163e..801b740 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -832,7 +832,7 @@
                     || info.hasPromiseIconUi()
                     || (info.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0
                     || (ENABLE_DOWNLOAD_APP_UX_V2.get() && icon != null)) {
-                updateProgressBarUi(icon);
+                updateProgressBarUi(info.getProgressLevel() == 100 ? icon : null);
             }
         }
     }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 87ee4f6..d992ee0 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -193,7 +193,7 @@
     private final int mMinHotseatIconSpacePx;
     private final int mMinHotseatQsbWidthPx;
     private final int mMaxHotseatIconSpacePx;
-    private final int mInlineNavButtonsEndSpacingPx;
+    public final int inlineNavButtonsEndSpacingPx;
 
     // Bottom sheets
     public int bottomSheetTopPadding;
@@ -490,7 +490,7 @@
         hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0;
         updateHotseatSizes(pxFromDp(inv.iconSize[INDEX_DEFAULT], mMetrics));
         if (areNavButtonsInline && !isPhone) {
-            mInlineNavButtonsEndSpacingPx =
+            inlineNavButtonsEndSpacingPx =
                     res.getDimensionPixelSize(inv.inlineNavButtonsEndSpacing);
             /*
              * 3 nav buttons +
@@ -499,9 +499,9 @@
              */
             hotseatBarEndOffset = 3 * res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
                     + 2 * res.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween)
-                    + mInlineNavButtonsEndSpacingPx;
+                    + inlineNavButtonsEndSpacingPx;
         } else {
-            mInlineNavButtonsEndSpacingPx = 0;
+            inlineNavButtonsEndSpacingPx = 0;
             hotseatBarEndOffset = 0;
         }
 
@@ -662,7 +662,7 @@
         }
 
         // The side space with inline buttons should be what is defined in InvariantDeviceProfile
-        int sideSpacePx = mInlineNavButtonsEndSpacingPx;
+        int sideSpacePx = inlineNavButtonsEndSpacingPx;
         int maxHotseatWidthPx = availableWidthPx - sideSpacePx - hotseatBarEndOffset;
         int maxHotseatIconsWidthPx = maxHotseatWidthPx - (isQsbInline ? hotseatQsbWidth : 0);
         hotseatBorderSpace = calculateHotseatBorderSpace(maxHotseatIconsWidthPx,
@@ -1320,7 +1320,7 @@
             int endSpacing;
             // Hotseat aligns to the left with nav buttons
             if (hotseatBarEndOffset > 0) {
-                startSpacing = mInlineNavButtonsEndSpacingPx;
+                startSpacing = inlineNavButtonsEndSpacingPx;
                 endSpacing = availableWidthPx - hotseatWidth - startSpacing + hotseatBorderSpace;
             } else {
                 startSpacing = (availableWidthPx - hotseatWidth) / 2;
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 767c3d9..bccb894 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -550,39 +550,42 @@
             Log.d(TAG, "loading default workspace");
 
             LauncherWidgetHolder widgetHolder = mOpenHelper.newLauncherWidgetHolder();
-            AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHolder);
-            if (loader == null) {
-                loader = AutoInstallsLayout.get(getContext(), widgetHolder, mOpenHelper);
-            }
-            if (loader == null) {
-                final Partner partner = Partner.get(getContext().getPackageManager());
-                if (partner != null) {
-                    int workspaceResId = partner.getXmlResId(RES_PARTNER_DEFAULT_LAYOUT);
-                    if (workspaceResId != 0) {
-                        loader = new DefaultLayoutParser(getContext(), widgetHolder,
-                                mOpenHelper, partner.getResources(), workspaceResId);
+            try {
+                AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHolder);
+                if (loader == null) {
+                    loader = AutoInstallsLayout.get(getContext(), widgetHolder, mOpenHelper);
+                }
+                if (loader == null) {
+                    final Partner partner = Partner.get(getContext().getPackageManager());
+                    if (partner != null) {
+                        int workspaceResId = partner.getXmlResId(RES_PARTNER_DEFAULT_LAYOUT);
+                        if (workspaceResId != 0) {
+                            loader = new DefaultLayoutParser(getContext(), widgetHolder,
+                                    mOpenHelper, partner.getResources(), workspaceResId);
+                        }
                     }
                 }
-            }
 
-            final boolean usingExternallyProvidedLayout = loader != null;
-            if (loader == null) {
-                loader = getDefaultLayoutParser(widgetHolder);
-            }
+                final boolean usingExternallyProvidedLayout = loader != null;
+                if (loader == null) {
+                    loader = getDefaultLayoutParser(widgetHolder);
+                }
 
-            // There might be some partially restored DB items, due to buggy restore logic in
-            // previous versions of launcher.
-            mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
-            // Populate favorites table with initial favorites
-            if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
-                    && usingExternallyProvidedLayout) {
-                // Unable to load external layout. Cleanup and load the internal layout.
+                // There might be some partially restored DB items, due to buggy restore logic in
+                // previous versions of launcher.
                 mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
-                mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
-                        getDefaultLayoutParser(widgetHolder));
+                // Populate favorites table with initial favorites
+                if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
+                        && usingExternallyProvidedLayout) {
+                    // Unable to load external layout. Cleanup and load the internal layout.
+                    mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
+                    mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
+                            getDefaultLayoutParser(widgetHolder));
+                }
+                clearFlagEmptyDbCreated();
+            } finally {
+                widgetHolder.destroy();
             }
-            clearFlagEmptyDbCreated();
-            widgetHolder.destroy();
         }
     }
 
@@ -957,8 +960,6 @@
                     allWidgets = holder.getAppWidgetIds();
                 } catch (IncompatibleClassChangeError e) {
                     Log.e(TAG, "getAppWidgetIds not supported", e);
-                    // Necessary to destroy the holder to free up possible activity context
-                    holder.destroy();
                     return;
                 }
                 final IntSet validWidgets = IntSet.wrap(LauncherDbUtils.queryIntArray(false, db,
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 2a452be..3a5ef10 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -21,8 +21,10 @@
 import static com.android.launcher3.LauncherPrefs.OLD_APP_WIDGET_IDS;
 import static com.android.launcher3.LauncherPrefs.RESTORE_DEVICE;
 import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
+import static com.android.launcher3.widget.LauncherWidgetHolder.APPWIDGET_HOST_ID;
 
 import android.app.backup.BackupManager;
+import android.appwidget.AppWidgetHost;
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
@@ -49,7 +51,6 @@
 import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.LogConfig;
-import com.android.launcher3.widget.LauncherWidgetHolder;
 
 import java.io.InvalidObjectException;
 import java.util.Arrays;
@@ -354,12 +355,11 @@
     private void restoreAppWidgetIdsIfExists(Context context) {
         LauncherPrefs lp = LauncherPrefs.get(context);
         if (lp.has(APP_WIDGET_IDS, OLD_APP_WIDGET_IDS)) {
-            LauncherWidgetHolder holder = LauncherWidgetHolder.newInstance(context);
+            AppWidgetHost host = new AppWidgetHost(context, APPWIDGET_HOST_ID);
             AppWidgetsRestoredReceiver.restoreAppWidgetIds(context,
                     IntArray.fromConcatString(lp.get(OLD_APP_WIDGET_IDS)).toArray(),
                     IntArray.fromConcatString(lp.get(APP_WIDGET_IDS)).toArray(),
-                    holder);
-            holder.destroy();
+                    host);
         } else {
             FileLog.d(TAG, "No app widget ids to restore.");
         }
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 628aa9a..295baa3 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -635,6 +635,9 @@
                 primarySnapshot.setTranslationX(0);
             }
             secondarySnapshot.setTranslationY(spaceAboveSnapshot);
+
+            // Reset unused translations
+            primarySnapshot.setTranslationY(0);
         } else {
             int deviceHeightWithoutTaskbar = dp.availableHeightPx - dp.taskbarSize;
             float scale = (float) totalThumbnailHeight / deviceHeightWithoutTaskbar;
@@ -669,6 +672,10 @@
                 View.MeasureSpec.makeMeasureSpec(secondarySnapshotWidth, View.MeasureSpec.EXACTLY),
                 View.MeasureSpec.makeMeasureSpec(secondarySnapshotHeight,
                         View.MeasureSpec.EXACTLY));
+        primarySnapshot.setScaleX(1);
+        secondarySnapshot.setScaleX(1);
+        primarySnapshot.setScaleY(1);
+        secondarySnapshot.setScaleY(1);
     }
 
     @Override
@@ -699,13 +706,13 @@
                     : deviceProfile.getInsets().left;
             int fullscreenMidpointFromBottom = ((deviceProfile.widthPx
                     - fullscreenInsetThickness) / 2);
-            float midpointFromBottomPct = (float) fullscreenMidpointFromBottom
+            float midpointFromEndPct = (float) fullscreenMidpointFromBottom
                     / deviceProfile.widthPx;
             float insetPct = (float) fullscreenInsetThickness / deviceProfile.widthPx;
             int spaceAboveSnapshots = 0;
             int overviewThumbnailAreaThickness = groupedTaskViewWidth - spaceAboveSnapshots;
             int bottomToMidpointOffset = (int) (overviewThumbnailAreaThickness
-                    * midpointFromBottomPct);
+                    * midpointFromEndPct);
             int insetOffset = (int) (overviewThumbnailAreaThickness * insetPct);
 
             if (deviceProfile.isSeascape()) {
diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
index bea7517..8e67eb1 100644
--- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java
+++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
@@ -322,13 +322,6 @@
     }
 
     /**
-     * Delete the host
-     */
-    public void deleteHost() {
-        mWidgetHost.deleteHost();
-    }
-
-    /**
      * @return The app widget ids
      */
     @NonNull