Merge "Have separate DeviceProfile instances for taskbar and all apps windows." into tm-qpr-dev
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 21728ad..d676f7d 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -1996,9 +1996,11 @@
      * Applies the transform on the recents animation
      */
     protected void applyScrollAndTransform() {
-        // No need to apply any transform if there is ongoing swipe-pip-to-home animator since
-        // that animator handles the leash solely.
-        boolean notSwipingPipToHome = mRecentsAnimationTargets != null && !mIsSwipingPipToHome;
+        // No need to apply any transform if there is ongoing swipe-to-home animator
+        //    swipe-to-pip handles the leash solely
+        //    swipe-to-icon animation is handled by RectFSpringAnim anim
+        boolean notSwipingToHome = mRecentsAnimationTargets != null
+                && mGestureState.getEndTarget() != HOME;
         boolean setRecentsScroll = mRecentsViewScrollLinked && mRecentsView != null;
         for (RemoteTargetHandle remoteHandle : mRemoteTargetHandles) {
             AnimatorControllerWithResistance playbackController =
@@ -2008,7 +2010,7 @@
                         getScaleProgressDueToScroll()), mDragLengthFactor);
             }
 
-            if (notSwipingPipToHome) {
+            if (notSwipingToHome) {
                 TaskViewSimulator taskViewSimulator = remoteHandle.getTaskViewSimulator();
                 if (setRecentsScroll) {
                     taskViewSimulator.setScroll(mRecentsView.getScrollOffset());
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index ee5bb44..99f7bdd 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -23,16 +23,15 @@
 import static com.android.launcher3.GestureNavContract.EXTRA_ICON_SURFACE;
 import static com.android.launcher3.GestureNavContract.EXTRA_ON_FINISH_CALLBACK;
 import static com.android.launcher3.GestureNavContract.EXTRA_REMOTE_CALLBACK;
-import static com.android.launcher3.Utilities.createHomeIntent;
 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
 
 import android.animation.ObjectAnimator;
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityOptions;
-import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Matrix;
@@ -161,11 +160,7 @@
         if (gestureContractAnimationFactory != null && runningTaskTarget != null) {
             gestureContractAnimationFactory.addGestureContract(intent, runningTaskTarget.taskInfo);
         }
-        try {
-            mContext.startActivity(intent, options.toBundle());
-        } catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
-            mContext.startActivity(createHomeIntent());
-        }
+        startHomeIntentSafely(mContext, intent, options.toBundle());
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 0efe666..9e3173c 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -20,11 +20,11 @@
 import static android.content.Intent.ACTION_PACKAGE_CHANGED;
 import static android.content.Intent.ACTION_PACKAGE_REMOVED;
 
-import static com.android.launcher3.Utilities.createHomeIntent;
 import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
 import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
 import static com.android.systemui.shared.system.PackageManagerWrapper.ACTION_PREFERRED_ACTIVITY_CHANGED;
 
+import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -33,8 +33,12 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.os.Bundle;
 import android.util.SparseIntArray;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.tracing.OverviewComponentObserverProto;
 import com.android.launcher3.tracing.TouchInteractionServiceProto;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
@@ -276,4 +280,34 @@
         overviewComponentObserver.setOverviewActivityResumed(mActivityInterface.isResumed());
         serviceProto.setOverviewComponentObvserver(overviewComponentObserver);
     }
+
+    /**
+     * Starts the intent for the current home activity.
+     */
+    public static void startHomeIntentSafely(@NonNull Context context, @Nullable Bundle options) {
+        RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(context);
+        OverviewComponentObserver observer = new OverviewComponentObserver(context, deviceState);
+        Intent intent = observer.getHomeIntent();
+        observer.onDestroy();
+        deviceState.destroy();
+        startHomeIntentSafely(context, intent, options);
+    }
+
+    /**
+     * Starts the intent for the current home activity.
+     */
+    public static void startHomeIntentSafely(
+            @NonNull Context context, @NonNull Intent homeIntent, @Nullable Bundle options) {
+        try {
+            context.startActivity(homeIntent, options);
+        } catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
+            context.startActivity(createHomeIntent(), options);
+        }
+    }
+
+    private static Intent createHomeIntent() {
+        return new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_HOME)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 4f0b976..67ce606 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -21,11 +21,11 @@
 import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_DURATION;
 import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_PRE_DELAY;
-import static com.android.launcher3.Utilities.createHomeIntent;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.graphics.SysUiScrim.SYSUI_PROGRESS;
 import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
+import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
 import static com.android.quickstep.TaskViewUtils.createRecentsWindowAnimator;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
@@ -428,7 +428,7 @@
         RemoteAnimationAdapterCompat adapterCompat =
                 new RemoteAnimationAdapterCompat(runner, HOME_APPEAR_DURATION, 0,
                         getIApplicationThread());
-        startActivity(createHomeIntent(),
+        startHomeIntentSafely(this,
                 ActivityOptionsCompat.makeRemoteAnimation(adapterCompat).toBundle());
     }
 
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index d7ee3cb..76513a4 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -268,6 +268,16 @@
             MAIN_EXECUTOR.execute(ProxyScreenStatusProvider.INSTANCE::onScreenTurnedOn);
         }
 
+        /**
+         * Preloads the Overview activity.
+         *
+         * This method should only be used when the All Set page of the SUW is reached to safely
+         * preload the Launcher for the SUW first reveal.
+         */
+        public void preloadOverviewForSUWAllSet() {
+            preloadOverview(false, true);
+        }
+
         @Override
         public void onRotationProposal(int rotation, boolean isValid) {
             executeForTaskbarManager(() -> mTaskbarManager.onRotationProposal(rotation, isValid));
@@ -883,6 +893,10 @@
     }
 
     private void preloadOverview(boolean fromInit) {
+        preloadOverview(fromInit, false);
+    }
+
+    private void preloadOverview(boolean fromInit, boolean forSUWAllSet) {
         if (!mDeviceState.isUserUnlocked()) {
             return;
         }
@@ -892,7 +906,8 @@
             return;
         }
 
-        if (RestoreDbTask.isPending(this) || !mDeviceState.isUserSetupComplete()) {
+        if ((RestoreDbTask.isPending(this) && !forSUWAllSet)
+                || !mDeviceState.isUserSetupComplete()) {
             // Preloading while a restore is pending may cause launcher to start the restore
             // too early.
             return;
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index 3d737ca..3c0da01 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -19,12 +19,12 @@
 import static android.view.MotionEvent.ACTION_POINTER_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
 
-import static com.android.launcher3.Utilities.createHomeIntent;
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.Utilities.squaredTouchSlop;
 import static com.android.launcher3.util.VelocityUtils.PX_PER_MS;
 import static com.android.quickstep.AbsSwipeUpHandler.MIN_PROGRESS_FOR_OVERVIEW;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
 import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
 
 import android.animation.Animator;
@@ -205,8 +205,9 @@
                 @Override
                 public void onAnimationEnd(Animator animation) {
                     if (dismissTask) {
-                        // For now, just start the home intent so user is prompted to unlock the device.
-                        mContext.startActivity(createHomeIntent());
+                        // For now, just start the home intent so user is prompted to
+                        // unlock the device.
+                        startHomeIntentSafely(mContext, mGestureState.getHomeIntent(), null);
                         mHomeLaunched = true;
                     }
                     mStateCallback.setState(STATE_HANDLER_INVALIDATED);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index 864e08d..a730183 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -15,12 +15,11 @@
  */
 package com.android.quickstep.inputconsumers;
 
-import static com.android.launcher3.Utilities.createHomeIntent;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
+import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
 
-import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.graphics.PointF;
 import android.view.MotionEvent;
@@ -79,11 +78,7 @@
 
     @Override
     public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
-        try {
-            mContext.startActivity(mGestureState.getHomeIntent());
-        } catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
-            mContext.startActivity(createHomeIntent());
-        }
+        startHomeIntentSafely(mContext, mGestureState.getHomeIntent(), null);
         ActiveGestureLog.INSTANCE.addLog("startQuickstep");
         BaseActivity activity = BaseDraggingActivity.fromContext(mContext);
         int state = (mGestureState != null && mGestureState.getEndTarget() != null)
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index a379aad..66ed056 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.Utilities.mapRange;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
 
 import android.animation.Animator;
 import android.app.Activity;
@@ -148,45 +149,50 @@
     }
 
     private void startBackgroundAnimation() {
-        if (Utilities.ATLEAST_S && mVibrator != null && mVibrator.areAllPrimitivesSupported(
-                VibrationEffect.Composition.PRIMITIVE_THUD)) {
-            if (mBackgroundAnimatorListener == null) {
-                mBackgroundAnimatorListener =
-                        new Animator.AnimatorListener() {
-                            @Override
-                            public void onAnimationStart(Animator animation) {
-                                runOnUiHelperThread(() -> mVibrator.vibrate(getVibrationEffect()));
-                            }
-
-                            @Override
-                            public void onAnimationRepeat(Animator animation) {
-                                runOnUiHelperThread(() -> mVibrator.vibrate(getVibrationEffect()));
-                            }
-
-                            @Override
-                            public void onAnimationEnd(Animator animation) {
-                                runOnUiHelperThread(mVibrator::cancel);
-                            }
-
-                            @Override
-                            public void onAnimationCancel(Animator animation) {
-                                runOnUiHelperThread(mVibrator::cancel);
-                            }
-                        };
-            }
-            mAnimatedBackground.addAnimatorListener(mBackgroundAnimatorListener);
+        if (!Utilities.ATLEAST_S || mVibrator == null) {
+            return;
         }
-        mAnimatedBackground.playAnimation();
-    }
+        boolean supportsThud = mVibrator.areAllPrimitivesSupported(
+                VibrationEffect.Composition.PRIMITIVE_THUD);
 
-    /**
-     * Sets up the vibration effect for the next round of animation. The parameters vary between
-     * different illustrations.
-     */
-    private VibrationEffect getVibrationEffect() {
-        return VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD, 1.0f, 50)
-                .compose();
+        if (!supportsThud && !mVibrator.areAllPrimitivesSupported(
+                VibrationEffect.Composition.PRIMITIVE_TICK)) {
+            return;
+        }
+        if (mBackgroundAnimatorListener == null) {
+            VibrationEffect vibrationEffect = VibrationEffect.startComposition()
+                    .addPrimitive(supportsThud
+                                    ? VibrationEffect.Composition.PRIMITIVE_THUD
+                                    : VibrationEffect.Composition.PRIMITIVE_TICK,
+                            /* scale= */ 1.0f,
+                            /* delay= */ 50)
+                    .compose();
+
+            mBackgroundAnimatorListener =
+                    new Animator.AnimatorListener() {
+                        @Override
+                        public void onAnimationStart(Animator animation) {
+                            runOnUiHelperThread(() -> mVibrator.vibrate(vibrationEffect));
+                        }
+
+                        @Override
+                        public void onAnimationRepeat(Animator animation) {
+                            runOnUiHelperThread(() -> mVibrator.vibrate(vibrationEffect));
+                        }
+
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            runOnUiHelperThread(mVibrator::cancel);
+                        }
+
+                        @Override
+                        public void onAnimationCancel(Animator animation) {
+                            runOnUiHelperThread(mVibrator::cancel);
+                        }
+                    };
+        }
+        mAnimatedBackground.addAnimatorListener(mBackgroundAnimatorListener);
+        mAnimatedBackground.playAnimation();
     }
 
     @Override
@@ -202,6 +208,7 @@
         mBinder = binder;
         mBinder.getTaskbarManager().setSetupUIVisible(isResumed());
         mBinder.setSwipeUpProxy(isResumed() ? this::createSwipeUpProxy : null);
+        mBinder.preloadOverviewForSUWAllSet();
     }
 
     @Override
@@ -281,7 +288,7 @@
         @Override
         public boolean performAccessibilityAction(View host, int action, Bundle args) {
             if (action == AccessibilityAction.ACTION_CLICK.getId()) {
-                startActivity(Utilities.createHomeIntent());
+                startHomeIntentSafely(AllSetActivity.this, null);
                 finish();
                 return true;
             }
diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
index c980d1e..45fc3af 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -1,5 +1,6 @@
 package com.android.quickstep.views;
 
+import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -19,6 +20,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.LauncherAnimUtils;
@@ -110,13 +112,19 @@
      */
     public static FloatingTaskView getFloatingTaskView(StatefulActivity launcher,
             View originalView, @Nullable Bitmap thumbnail, Drawable icon, RectF positionOut) {
-        final BaseDragLayer dragLayer = launcher.getDragLayer();
-        ViewGroup parent = (ViewGroup) dragLayer.getParent();
+        final ViewGroup dragLayer = launcher.getDragLayer();
         final FloatingTaskView floatingView = (FloatingTaskView) launcher.getLayoutInflater()
-                .inflate(R.layout.floating_split_select_view, parent, false);
+                .inflate(R.layout.floating_split_select_view, dragLayer, false);
 
         floatingView.init(launcher, originalView, thumbnail, icon, positionOut);
-        parent.addView(floatingView);
+        // Add this animating view underneath the existing open task menu view (if there is one)
+        View openTaskView = AbstractFloatingView.getOpenView(launcher, TYPE_TASK_MENU);
+        int openTaskViewIndex = dragLayer.indexOfChild(openTaskView);
+        if (openTaskViewIndex == -1) {
+            // Add to top if not
+            openTaskViewIndex = dragLayer.getChildCount();
+        }
+        dragLayer.addView(floatingView, openTaskViewIndex - 1);
         return floatingView;
     }
 
@@ -125,7 +133,7 @@
         Utilities.getBoundsForViewInDragLayer(mActivity.getDragLayer(), originalView, viewBounds,
                 false /* ignoreTransform */, null /* recycle */,
                 mStartingPosition);
-        final InsettableFrameLayout.LayoutParams lp = new InsettableFrameLayout.LayoutParams(
+        final BaseDragLayer.LayoutParams lp = new BaseDragLayer.LayoutParams(
                 Math.round(mStartingPosition.width()),
                 Math.round(mStartingPosition.height()));
         initPosition(mStartingPosition, lp);
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index a2f6792..364bd59 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -34,6 +34,7 @@
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -4097,6 +4098,10 @@
         mSecondSplitHiddenView.setVisibility(INVISIBLE);
         InteractionJankMonitorWrapper.begin(this,
                 InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "Second tile selected");
+
+        // Fade out all other views underneath placeholders
+        ObjectAnimator tvFade = ObjectAnimator.ofFloat(this, RecentsView.CONTENT_ALPHA,1, 0);
+        pendingAnimation.add(tvFade, DEACCEL_2, SpringProperty.DEFAULT);
         pendingAnimation.buildAnim().start();
         return true;
     }
@@ -4110,11 +4115,11 @@
                 mSplitInstructionsView = null;
             }
             if (mFirstFloatingTaskView != null) {
-                mActivity.getRootView().removeView(mFirstFloatingTaskView);
+                mActivity.getDragLayer().removeView(mFirstFloatingTaskView);
                 mFirstFloatingTaskView = null;
             }
             if (mSecondFloatingTaskView != null) {
-                mActivity.getRootView().removeView(mSecondFloatingTaskView);
+                mActivity.getDragLayer().removeView(mSecondFloatingTaskView);
                 mSecondFloatingTaskView = null;
                 mSecondSplitHiddenView.setVisibility(VISIBLE);
                 mSecondSplitHiddenView = null;
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index d58bb7c..f0ca46a 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -1537,7 +1537,6 @@
     }
 
     public void initiateSplitSelect(SplitPositionOption splitPositionOption) {
-        AbstractFloatingView.closeOpenViews(mActivity, false, TYPE_TASK_MENU);
         getRecentsView().initiateSplitSelect(this, splitPositionOption.stagePosition);
     }
 
diff --git a/res/values-sw720dp/dimens.xml b/res/values-sw720dp/dimens.xml
index c16792a..7b2ed8b 100644
--- a/res/values-sw720dp/dimens.xml
+++ b/res/values-sw720dp/dimens.xml
@@ -31,6 +31,7 @@
     <dimen name="drop_target_button_drawable_horizontal_padding">24dp</dimen>
     <dimen name="drop_target_button_drawable_vertical_padding">20dp</dimen>
     <dimen name="drop_target_button_gap">32dp</dimen>
+    <dimen name="drop_target_button_workspace_edge_gap">32dp</dimen>
     <dimen name="drop_target_top_margin">110dp</dimen>
     <dimen name="drop_target_bottom_margin">48dp</dimen>
 
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index e7b3375..39f0a2b 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -241,6 +241,7 @@
     <dimen name="drop_target_button_drawable_horizontal_padding">16dp</dimen>
     <dimen name="drop_target_button_drawable_vertical_padding">8dp</dimen>
     <dimen name="drop_target_button_gap">28dp</dimen>
+    <dimen name="drop_target_button_workspace_edge_gap">0dp</dimen>
 
     <!-- the distance an icon must be dragged before button drop targets accept it -->
     <dimen name="drag_distanceThreshold">30dp</dimen>
diff --git a/res/values/id.xml b/res/values/id.xml
index 7ad1412..af21b27 100644
--- a/res/values/id.xml
+++ b/res/values/id.xml
@@ -37,4 +37,5 @@
 
     <item type="id" name="quick_settings_button" />
     <item type="id" name="notifications_button" />
+    <item type="id" name="cache_entry_tag_id" />
 </resources>
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 90869c2..21dbc5f 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -93,6 +93,7 @@
     public static final int TYPE_WIDGETS_EDUCATION_DIALOG = 1 << 15;
     public static final int TYPE_TASKBAR_EDUCATION_DIALOG = 1 << 16;
     public static final int TYPE_TASKBAR_ALL_APPS = 1 << 17;
+    public static final int TYPE_ADD_TO_HOME_CONFIRMATION = 1 << 18;
 
     public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
             | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
@@ -100,7 +101,7 @@
             | TYPE_OPTIONS_POPUP | TYPE_SNACKBAR | TYPE_LISTENER | TYPE_ALL_APPS_EDU
             | TYPE_ICON_SURFACE | TYPE_DRAG_DROP_POPUP | TYPE_PIN_WIDGET_FROM_EXTERNAL_POPUP
             | TYPE_WIDGETS_EDUCATION_DIALOG | TYPE_TASKBAR_EDUCATION_DIALOG | TYPE_TASKBAR_ALL_APPS
-            | TYPE_OPTIONS_POPUP_DIALOG;
+            | TYPE_OPTIONS_POPUP_DIALOG | TYPE_ADD_TO_HOME_CONFIRMATION;
 
     // Type of popups which should be kept open during launcher rebind
     public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 8da4f05..3b24df2 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -179,7 +179,12 @@
 
     @Override
     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
-        mActive = !options.isKeyboardDrag && supportsDrop(dragObject.dragInfo);
+        if (options.isKeyboardDrag) {
+            mActive = false;
+        } else {
+            setupItemInfo(dragObject.dragInfo);
+            mActive = supportsDrop(dragObject.dragInfo);
+        }
         setVisibility(mActive ? View.VISIBLE : View.GONE);
 
         mAccessibleDrag = options.isAccessibleDrag;
@@ -191,6 +196,11 @@
         return supportsDrop(dragObject.dragInfo);
     }
 
+    /**
+     * Setups button for the specified ItemInfo.
+     */
+    protected abstract void setupItemInfo(ItemInfo info);
+
     protected abstract boolean supportsDrop(ItemInfo info);
 
     public abstract boolean supportsAccessibilityDrop(ItemInfo info, View view);
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 371bb80..95d3ad9 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -85,6 +85,9 @@
     }
 
     @Override
+    protected void setupItemInfo(ItemInfo info) {}
+
+    @Override
     protected boolean supportsDrop(ItemInfo info) {
         return true;
     }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 827c5b8..8a82aeb 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -217,6 +217,7 @@
     public int dropTargetHorizontalPaddingPx;
     public int dropTargetVerticalPaddingPx;
     public int dropTargetGapPx;
+    public int dropTargetButtonWorkspaceEdgeGapPx;
 
     // Insets
     private final Rect mInsets = new Rect();
@@ -348,6 +349,8 @@
         dropTargetVerticalPaddingPx = res.getDimensionPixelSize(
                 R.dimen.drop_target_button_drawable_vertical_padding);
         dropTargetGapPx = res.getDimensionPixelSize(R.dimen.drop_target_button_gap);
+        dropTargetButtonWorkspaceEdgeGapPx = res.getDimensionPixelSize(
+                R.dimen.drop_target_button_workspace_edge_gap);
 
         workspaceSpringLoadedBottomSpace =
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_min_spring_loaded_space);
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index dbddb26..d908440 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -186,14 +186,13 @@
                 availableWidth = scaledPanelWidth - halfButtonGap / 2;
             } else {
                 // Both buttons plus the button gap do not display past the edge of the scaled
-                // workspace.
-                availableWidth = (scaledPanelWidth - dp.dropTargetGapPx) / 2;
+                // workspace, less a pre-defined gap from the edge of the workspace.
+                availableWidth = scaledPanelWidth - dp.dropTargetGapPx
+                        - 2 * dp.dropTargetButtonWorkspaceEdgeGapPx;
             }
 
             int widthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST);
             firstButton.measure(widthSpec, heightSpec);
-            secondButton.measure(widthSpec, heightSpec);
-
             if (!mIsVertical) {
                 // Remove icons and put the button's text on two lines if text is truncated.
                 if (firstButton.isTextTruncated(availableWidth)) {
@@ -202,6 +201,14 @@
                     firstButton.setPadding(horizontalPadding, verticalPadding / 2,
                             horizontalPadding, verticalPadding / 2);
                 }
+            }
+
+            if (!dp.isTwoPanels) {
+                availableWidth -= firstButton.getMeasuredWidth() + dp.dropTargetGapPx;
+                widthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST);
+            }
+            secondButton.measure(widthSpec, heightSpec);
+            if (!mIsVertical) {
                 if (secondButton.isTextTruncated(availableWidth)) {
                     secondButton.setIconVisible(false);
                     secondButton.setTextMultiLine(true);
@@ -243,13 +250,30 @@
             int buttonGap = dp.dropTargetGapPx;
 
             ButtonDropTarget leftButton = mTempTargets[0];
-            leftButton.layout(barCenter - leftButton.getMeasuredWidth() - (buttonGap / 2), 0,
-                    barCenter - (buttonGap / 2), leftButton.getMeasuredHeight());
-
             ButtonDropTarget rightButton = mTempTargets[1];
-            rightButton.layout(barCenter + (buttonGap / 2), 0,
-                    barCenter + (buttonGap / 2) + rightButton.getMeasuredWidth(),
-                    rightButton.getMeasuredHeight());
+            if (dp.isTwoPanels) {
+                leftButton.layout(barCenter - leftButton.getMeasuredWidth() - (buttonGap / 2), 0,
+                        barCenter - (buttonGap / 2), leftButton.getMeasuredHeight());
+                rightButton.layout(barCenter + (buttonGap / 2), 0,
+                        barCenter + (buttonGap / 2) + rightButton.getMeasuredWidth(),
+                        rightButton.getMeasuredHeight());
+            } else {
+                int scaledPanelWidth = (int) (dp.getCellLayoutWidth() * scale);
+
+                int leftButtonWidth = leftButton.getMeasuredWidth();
+                int rightButtonWidth = rightButton.getMeasuredWidth();
+                int extraSpace = scaledPanelWidth - leftButtonWidth - rightButtonWidth - buttonGap;
+
+                int leftButtonStart = barCenter - (scaledPanelWidth / 2) + extraSpace / 2;
+                int leftButtonEnd = leftButtonStart + leftButtonWidth;
+                int rightButtonStart = leftButtonEnd + buttonGap;
+                int rightButtonEnd = rightButtonStart + rightButtonWidth;
+
+                leftButton.layout(leftButtonStart, 0, leftButtonEnd,
+                        leftButton.getMeasuredHeight());
+                rightButton.layout(rightButtonStart, 0, rightButtonEnd,
+                        rightButton.getMeasuredHeight());
+            }
         }
     }
 
@@ -318,7 +342,7 @@
     }
 
     public ButtonDropTarget[] getDropTargets() {
-        return mDropTargets;
+        return getVisibility() == View.VISIBLE ? mDropTargets : new ButtonDropTarget[0];
     }
 
     @Override
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 31c1eff..1846383 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -536,6 +536,7 @@
         if (Utilities.ATLEAST_R) {
             getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
         }
+        setTitle(R.string.home_screen);
     }
 
     protected LauncherOverlayManager getDefaultOverlay() {
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 5b037e4..f8bc1f4 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -6,6 +6,7 @@
 import static com.android.launcher3.Launcher.REQUEST_RECONFIGURE_APPWIDGET;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.DISMISS_PREDICTION;
+import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.INVALID;
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.RECONFIGURE;
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.UNINSTALL;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DISMISS_PREDICTION_UNDO;
@@ -69,6 +70,7 @@
     private boolean mHadPendingAlarm;
 
     protected int mCurrentAccessibilityAction = -1;
+
     public SecondaryDropTarget(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
@@ -134,24 +136,33 @@
     }
 
     @Override
+    protected void setupItemInfo(ItemInfo info) {
+        int buttonType = getButtonType(info, getViewUnderDrag(info));
+        if (buttonType != INVALID) {
+            setupUi(buttonType);
+        }
+    }
+
+    @Override
     protected boolean supportsDrop(ItemInfo info) {
-        return supportsAccessibilityDrop(info, getViewUnderDrag(info));
+        return getButtonType(info, getViewUnderDrag(info)) != INVALID;
     }
 
     @Override
     public boolean supportsAccessibilityDrop(ItemInfo info, View view) {
+        return getButtonType(info, view) != INVALID;
+    }
+
+    private int getButtonType(ItemInfo info, View view) {
         if (view instanceof AppWidgetHostView) {
             if (getReconfigurableWidgetId(view) != INVALID_APPWIDGET_ID) {
-                setupUi(RECONFIGURE);
-                return true;
+                return RECONFIGURE;
             }
-            return false;
+            return INVALID;
         } else if (FeatureFlags.ENABLE_PREDICTION_DISMISS.get() && info.isPredictedItem()) {
-            setupUi(DISMISS_PREDICTION);
-            return true;
+            return DISMISS_PREDICTION;
         }
 
-        setupUi(UNINSTALL);
         Boolean uninstallDisabled = mUninstallDisabledCache.get(info.user);
         if (uninstallDisabled == null) {
             UserManager userManager =
@@ -165,16 +176,20 @@
         mCacheExpireAlarm.setAlarm(CACHE_EXPIRE_TIMEOUT);
         mCacheExpireAlarm.setOnAlarmListener(this);
         if (uninstallDisabled) {
-            return false;
+            return INVALID;
         }
 
         if (info instanceof ItemInfoWithIcon) {
             ItemInfoWithIcon iconInfo = (ItemInfoWithIcon) info;
-            if ((iconInfo.runtimeStatusFlags & FLAG_SYSTEM_MASK) != 0) {
-                return (iconInfo.runtimeStatusFlags & FLAG_SYSTEM_NO) != 0;
+            if ((iconInfo.runtimeStatusFlags & FLAG_SYSTEM_MASK) != 0
+                    && (iconInfo.runtimeStatusFlags & FLAG_SYSTEM_NO) == 0) {
+                return INVALID;
             }
         }
-        return getUninstallTarget(info) != null;
+        if (getUninstallTarget(info) == null) {
+            return INVALID;
+        }
+        return UNINSTALL;
     }
 
     /**
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 7b96838..dc8c739 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -587,15 +587,6 @@
     }
 
     /**
-     * Returns an intent for starting the default home activity
-     */
-    public static Intent createHomeIntent() {
-        return new Intent(Intent.ACTION_MAIN)
-                .addCategory(Intent.CATEGORY_HOME)
-                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-    }
-
-    /**
      * Wraps a message with a TTS span, so that a different message is spoken than
      * what is getting displayed.
      * @param msg original message
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 44d57d7..79214e8 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -60,6 +60,7 @@
     public static final int DISMISS_PREDICTION = R.id.action_dismiss_prediction;
     public static final int PIN_PREDICTION = R.id.action_pin_prediction;
     public static final int RECONFIGURE = R.id.action_reconfigure;
+    public static final int INVALID = -1;
     protected static final int ADD_TO_WORKSPACE = R.id.action_add_to_workspace;
     protected static final int MOVE = R.id.action_move;
     protected static final int MOVE_TO_WORKSPACE = R.id.action_move_to_workspace;
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
index fc52797..dd0b503 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
@@ -246,7 +246,7 @@
      */
     public boolean shouldContainerScroll(MotionEvent ev) {
         // Scroll if not within the container view (e.g. over large-screen scrim).
-        if (!mActivityContext.getDragLayer().isEventOverView(this, ev)) {
+        if (!mActivityContext.getDragLayer().isEventOverView(getVisibleContainerView(), ev)) {
             return true;
         }
         if (mActivityContext.getDragLayer().isEventOverView(mBottomSheetHandleArea, ev)) {
diff --git a/src/com/android/launcher3/anim/AnimatedPropertySetter.java b/src/com/android/launcher3/anim/AnimatedPropertySetter.java
new file mode 100644
index 0000000..e5f5e7c
--- /dev/null
+++ b/src/com/android/launcher3/anim/AnimatedPropertySetter.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.anim;
+
+import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.graphics.drawable.ColorDrawable;
+import android.util.FloatProperty;
+import android.util.IntProperty;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import java.util.function.Consumer;
+
+/**
+ * Extension of {@link PropertySetter} which applies the property through an animation
+ */
+public class AnimatedPropertySetter extends PropertySetter {
+
+    protected final AnimatorSet mAnim = new AnimatorSet();
+    protected ValueAnimator mProgressAnimator;
+
+    @Override
+    public Animator setViewAlpha(View view, float alpha, TimeInterpolator interpolator) {
+        if (view == null || view.getAlpha() == alpha) {
+            return NO_OP;
+        }
+        ObjectAnimator anim = ObjectAnimator.ofFloat(view, View.ALPHA, alpha);
+        anim.addListener(new AlphaUpdateListener(view));
+        anim.setInterpolator(interpolator);
+        add(anim);
+        return anim;
+    }
+
+    @Override
+    public Animator setViewBackgroundColor(View view, int color, TimeInterpolator interpolator) {
+        if (view == null || (view.getBackground() instanceof ColorDrawable
+                && ((ColorDrawable) view.getBackground()).getColor() == color)) {
+            return NO_OP;
+        }
+        ObjectAnimator anim = ObjectAnimator.ofArgb(view, VIEW_BACKGROUND_COLOR, color);
+        anim.setInterpolator(interpolator);
+        add(anim);
+        return anim;
+    }
+
+    @Override
+    public <T> Animator setFloat(T target, FloatProperty<T> property, float value,
+            TimeInterpolator interpolator) {
+        if (property.get(target) == value) {
+            return NO_OP;
+        }
+        Animator anim = ObjectAnimator.ofFloat(target, property, value);
+        anim.setInterpolator(interpolator);
+        add(anim);
+        return anim;
+    }
+
+    @Override
+    public <T> Animator setInt(T target, IntProperty<T> property, int value,
+            TimeInterpolator interpolator) {
+        if (property.get(target) == value) {
+            return NO_OP;
+        }
+        Animator anim = ObjectAnimator.ofInt(target, property, value);
+        anim.setInterpolator(interpolator);
+        add(anim);
+        return anim;
+    }
+
+
+    /**
+     * Adds a callback to be run on every frame of the animation
+     */
+    public void addOnFrameCallback(Runnable runnable) {
+        addOnFrameListener(anim -> runnable.run());
+    }
+
+    /**
+     * Adds a listener to be run on every frame of the animation
+     */
+    public void addOnFrameListener(ValueAnimator.AnimatorUpdateListener listener) {
+        if (mProgressAnimator == null) {
+            mProgressAnimator = ValueAnimator.ofFloat(0, 1);
+        }
+
+        mProgressAnimator.addUpdateListener(listener);
+    }
+
+    @Override
+    public void addEndListener(Consumer<Boolean> listener) {
+        if (mProgressAnimator == null) {
+            mProgressAnimator = ValueAnimator.ofFloat(0, 1);
+        }
+        mProgressAnimator.addListener(AnimatorListeners.forEndCallback(listener));
+    }
+
+    /**
+     * @see AnimatorSet#addListener(AnimatorListener)
+     */
+    public void addListener(Animator.AnimatorListener listener) {
+        mAnim.addListener(listener);
+    }
+
+    @Override
+    public void add(Animator a) {
+        mAnim.play(a);
+    }
+
+    /**
+     * Creates and returns the underlying AnimatorSet
+     */
+    @NonNull
+    public AnimatorSet buildAnim() {
+        // Add progress animation to the end, so that frame callback is called after all the other
+        // animation update.
+        if (mProgressAnimator != null) {
+            add(mProgressAnimator);
+            mProgressAnimator = null;
+        }
+        return mAnim;
+    }
+}
diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java
index 1300ce7..7316420 100644
--- a/src/com/android/launcher3/anim/PendingAnimation.java
+++ b/src/com/android/launcher3/anim/PendingAnimation.java
@@ -15,24 +15,18 @@
  */
 package com.android.launcher3.anim;
 
-import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR;
 import static com.android.launcher3.anim.AnimatorPlaybackController.addAnimationHoldersRecur;
 
 import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
-import android.graphics.drawable.ColorDrawable;
 import android.util.FloatProperty;
-import android.util.IntProperty;
-import android.view.View;
 
 import com.android.launcher3.anim.AnimatorPlaybackController.Holder;
 
 import java.util.ArrayList;
-import java.util.function.Consumer;
 
 /**
  * Utility class to keep track of a running animation.
@@ -43,17 +37,13 @@
  *
  * TODO: Find a better name
  */
-public class PendingAnimation implements PropertySetter {
+public class PendingAnimation extends AnimatedPropertySetter {
 
     private final ArrayList<Holder> mAnimHolders = new ArrayList<>();
-    private final AnimatorSet mAnim;
     private final long mDuration;
 
-    private ValueAnimator mProgressAnimator;
-
     public PendingAnimation(long  duration) {
         mDuration = duration;
-        mAnim = new AnimatorSet();
     }
 
     public long getDuration() {
@@ -68,6 +58,7 @@
         add(anim, springProperty);
     }
 
+    @Override
     public void add(Animator anim) {
         add(anim, SpringProperty.DEFAULT);
     }
@@ -84,39 +75,6 @@
         mAnim.setInterpolator(interpolator);
     }
 
-    @Override
-    public void setViewAlpha(View view, float alpha, TimeInterpolator interpolator) {
-        if (view == null || view.getAlpha() == alpha) {
-            return;
-        }
-        ObjectAnimator anim = ObjectAnimator.ofFloat(view, View.ALPHA, alpha);
-        anim.addListener(new AlphaUpdateListener(view));
-        anim.setInterpolator(interpolator);
-        add(anim);
-    }
-
-    @Override
-    public void setViewBackgroundColor(View view, int color, TimeInterpolator interpolator) {
-        if (view == null || (view.getBackground() instanceof ColorDrawable
-                && ((ColorDrawable) view.getBackground()).getColor() == color)) {
-            return;
-        }
-        ObjectAnimator anim = ObjectAnimator.ofArgb(view, VIEW_BACKGROUND_COLOR, color);
-        anim.setInterpolator(interpolator);
-        add(anim);
-    }
-
-    @Override
-    public <T> void setFloat(T target, FloatProperty<T> property, float value,
-            TimeInterpolator interpolator) {
-        if (property.get(target) == value) {
-            return;
-        }
-        Animator anim = ObjectAnimator.ofFloat(target, property, value);
-        anim.setDuration(mDuration).setInterpolator(interpolator);
-        add(anim);
-    }
-
     public <T> void addFloat(T target, FloatProperty<T> property, float from, float to,
             TimeInterpolator interpolator) {
         Animator anim = ObjectAnimator.ofFloat(target, property, from, to);
@@ -124,57 +82,16 @@
         add(anim);
     }
 
-    @Override
-    public <T> void setInt(T target, IntProperty<T> property, int value,
-            TimeInterpolator interpolator) {
-        if (property.get(target) == value) {
-            return;
-        }
-        Animator anim = ObjectAnimator.ofInt(target, property, value);
-        anim.setInterpolator(interpolator);
-        add(anim);
-    }
-
-    /**
-     * Adds a callback to be run on every frame of the animation
-     */
-    public void addOnFrameCallback(Runnable runnable) {
-        addOnFrameListener(anim -> runnable.run());
-    }
-
-    /**
-     * Adds a listener to be run on every frame of the animation
-     */
-    public void addOnFrameListener(ValueAnimator.AnimatorUpdateListener listener) {
-        if (mProgressAnimator == null) {
-            mProgressAnimator = ValueAnimator.ofFloat(0, 1);
-        }
-
-        mProgressAnimator.addUpdateListener(listener);
-    }
-
-    /**
-     * @see AnimatorSet#addListener(AnimatorListener)
-     */
-    public void addListener(Animator.AnimatorListener listener) {
-        mAnim.addListener(listener);
-    }
-
     /**
      * Creates and returns the underlying AnimatorSet
      */
+    @Override
     public AnimatorSet buildAnim() {
-        // Add progress animation to the end, so that frame callback is called after all the other
-        // animation update.
-        if (mProgressAnimator != null) {
-            add(mProgressAnimator);
-            mProgressAnimator = null;
-        }
         if (mAnimHolders.isEmpty()) {
             // Add a placeholder animation to that the duration is respected
             add(ValueAnimator.ofFloat(0, 1).setDuration(mDuration));
         }
-        return mAnim;
+        return super.buildAnim();
     }
 
     /**
@@ -183,14 +100,4 @@
     public AnimatorPlaybackController createPlaybackController() {
         return new AnimatorPlaybackController(buildAnim(), mDuration, mAnimHolders);
     }
-
-    /**
-     * Add a listener of receiving the success/failure callback in the end.
-     */
-    public void addEndListener(Consumer<Boolean> listener) {
-        if (mProgressAnimator == null) {
-            mProgressAnimator = ValueAnimator.ofFloat(0, 1);
-        }
-        mProgressAnimator.addListener(AnimatorListeners.forEndCallback(listener));
-    }
 }
diff --git a/src/com/android/launcher3/anim/PropertySetter.java b/src/com/android/launcher3/anim/PropertySetter.java
index 8d77b4b..d2207f6 100644
--- a/src/com/android/launcher3/anim/PropertySetter.java
+++ b/src/com/android/launcher3/anim/PropertySetter.java
@@ -17,57 +17,94 @@
 package com.android.launcher3.anim;
 
 import android.animation.Animator;
+import android.animation.AnimatorSet;
 import android.animation.TimeInterpolator;
 import android.util.FloatProperty;
 import android.util.IntProperty;
 import android.view.View;
 
+import androidx.annotation.NonNull;
+
+import java.util.function.Consumer;
+
 /**
  * Utility class for setting a property with or without animation
  */
-public interface PropertySetter {
+public abstract class PropertySetter {
 
-    PropertySetter NO_ANIM_PROPERTY_SETTER = new PropertySetter() { };
+    public static final PropertySetter NO_ANIM_PROPERTY_SETTER = new PropertySetter() {
+
+        @Override
+        public void add(Animator animatorSet) {
+            animatorSet.setDuration(0);
+            animatorSet.start();
+        }
+    };
+
+    protected static final AnimatorSet NO_OP = new AnimatorSet();
 
     /**
      * Sets the view alpha using the provided interpolator.
      * Unlike {@link #setFloat}, this also updates the visibility of the view as alpha changes
      * between zero and non-zero.
      */
-    default void setViewAlpha(View view, float alpha, TimeInterpolator interpolator) {
+    @NonNull
+    public Animator setViewAlpha(View view, float alpha, TimeInterpolator interpolator) {
         if (view != null) {
             view.setAlpha(alpha);
             AlphaUpdateListener.updateVisibility(view);
         }
+        return NO_OP;
     }
 
     /**
      * Sets the background color of the provided view using the provided interpolator.
      */
-    default void setViewBackgroundColor(View view, int color, TimeInterpolator interpolator) {
+    @NonNull
+    public Animator setViewBackgroundColor(View view, int color, TimeInterpolator interpolator) {
         if (view != null) {
             view.setBackgroundColor(color);
         }
+        return NO_OP;
     }
 
     /**
      * Updates the float property of the target using the provided interpolator
      */
-    default <T> void setFloat(T target, FloatProperty<T> property, float value,
+    @NonNull
+    public <T> Animator setFloat(T target, FloatProperty<T> property, float value,
             TimeInterpolator interpolator) {
         property.setValue(target, value);
+        return NO_OP;
     }
 
     /**
      * Updates the int property of the target using the provided interpolator
      */
-    default <T> void setInt(T target, IntProperty<T> property, int value,
+    @NonNull
+    public <T> Animator setInt(T target, IntProperty<T> property, int value,
             TimeInterpolator interpolator) {
         property.setValue(target, value);
+        return NO_OP;
     }
 
-    default void add(Animator animatorSet) {
-        animatorSet.setDuration(0);
-        animatorSet.start();
+    /**
+     * Runs the animation as part of setting the property
+     */
+    public abstract void add(Animator animatorSet);
+
+    /**
+     * Add a listener of receiving the success/failure callback in the end.
+     */
+    public void addEndListener(Consumer<Boolean> listener) {
+        listener.accept(true);
+    }
+
+    /**
+     * Creates and returns the AnimatorSet that can be run to apply the properties
+     */
+    @NonNull
+    public AnimatorSet buildAnim() {
+        return NO_OP;
     }
 }
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcast.java b/src/com/android/launcher3/model/FirstScreenBroadcast.java
index 5fac7cf..9e91b9d 100644
--- a/src/com/android/launcher3/model/FirstScreenBroadcast.java
+++ b/src/com/android/launcher3/model/FirstScreenBroadcast.java
@@ -20,6 +20,7 @@
 import static android.os.Process.myUserHandle;
 
 import static com.android.launcher3.pm.InstallSessionHelper.getUserHandle;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import static java.util.stream.Collectors.groupingBy;
 import static java.util.stream.Collectors.mapping;
@@ -31,13 +32,18 @@
 import android.os.UserHandle;
 import android.util.Log;
 
+import androidx.annotation.AnyThread;
+import androidx.annotation.WorkerThread;
+
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -78,6 +84,7 @@
      * Sends a broadcast to all package installers that have items with active sessions on the users
      * first screen.
      */
+    @WorkerThread
     public void sendBroadcasts(Context context, List<ItemInfo> firstScreenItems) {
         UserHandle myUser = myUserHandle();
         mSessionInfoForPackage
@@ -95,6 +102,7 @@
      * @param packages List of packages with active sessions for this package installer.
      * @param firstScreenItems List of items on the first screen.
      */
+    @WorkerThread
     private void sendBroadcastToInstaller(Context context, String installerPackageName,
             Set<String> packages, List<ItemInfo> firstScreenItems) {
         Set<String> folderItems = new HashSet<>();
@@ -106,7 +114,7 @@
             if (info instanceof FolderInfo) {
                 FolderInfo folderInfo = (FolderInfo) info;
                 String folderItemInfoPackage;
-                for (ItemInfo folderItemInfo : folderInfo.contents) {
+                for (ItemInfo folderItemInfo : cloneOnMainThread(folderInfo.contents)) {
                     folderItemInfoPackage = getPackageName(folderItemInfo);
                     if (folderItemInfoPackage != null
                             && packages.contains(folderItemInfoPackage)) {
@@ -170,4 +178,17 @@
             Log.d(TAG, packageInstaller + ":" + label + ":" + pkg);
         }
     }
+
+    /**
+     * Clone the provided list on UI thread. This is used for {@link FolderInfo#contents} which
+     * is always modified on UI thread.
+     */
+    @AnyThread
+    private static List<WorkspaceItemInfo> cloneOnMainThread(ArrayList<WorkspaceItemInfo> list) {
+        try {
+            return MAIN_EXECUTOR.submit(() -> new ArrayList(list)).get();
+        } catch (Exception e) {
+            return Collections.emptyList();
+        }
+    }
 }
diff --git a/src/com/android/launcher3/util/ViewCache.java b/src/com/android/launcher3/util/ViewCache.java
index 08b8744..98e6822 100644
--- a/src/com/android/launcher3/util/ViewCache.java
+++ b/src/com/android/launcher3/util/ViewCache.java
@@ -21,6 +21,8 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.launcher3.R;
+
 /**
  * Utility class to cache views at an activity level
  */
@@ -39,18 +41,26 @@
             mCache.put(layoutId, entry);
         }
 
+        T result;
         if (entry.mCurrentSize > 0) {
             entry.mCurrentSize --;
-            T result = (T) entry.mViews[entry.mCurrentSize];
+            result = (T) entry.mViews[entry.mCurrentSize];
             entry.mViews[entry.mCurrentSize] = null;
-            return result;
+        } else {
+            result = (T) LayoutInflater.from(context).inflate(layoutId, parent, false);
+            result.setTag(R.id.cache_entry_tag_id, entry);
         }
-
-        return (T) LayoutInflater.from(context).inflate(layoutId, parent, false);
+        return result;
     }
 
     public void recycleView(int layoutId, View view) {
         CacheEntry entry = mCache.get(layoutId);
+        if (entry != view.getTag(R.id.cache_entry_tag_id)) {
+            // Since this view was created, the cache has been reset. The view should not be
+            // recycled since this means the environment could also have changed, requiring new
+            // view setup.
+            return;
+        }
         if (entry != null && entry.mCurrentSize < entry.mMaxSize) {
             entry.mViews[entry.mCurrentSize] = view;
             entry.mCurrentSize++;
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index 7a8e9d5..2d6112f 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -287,7 +287,6 @@
             case MotionEvent.ACTION_UP:
                 hideKeyboardAsync(ActivityContext.lookupContext(getContext()),
                         getApplicationWindowToken());
-                break;
             case MotionEvent.ACTION_CANCEL:
                 mRv.onFastScrollCompleted();
                 mTouchOffsetY = 0;
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 1bcba14..8962c4f 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -198,7 +198,9 @@
     /** Returns the number of cells that can fit horizontally in a given {@code content}. */
     protected int computeMaxHorizontalSpans(View content, int contentHorizontalPaddingPx) {
         DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
-        int availableWidth = content.getMeasuredWidth() - contentHorizontalPaddingPx;
+        int availableWidth = content.getMeasuredWidth()
+                - contentHorizontalPaddingPx
+                - (2 * mContentHorizontalMarginInPx);
         Point cellSize = deviceProfile.getCellSize();
         if (cellSize.x > 0) {
             return availableWidth / cellSize.x;
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 8a97c6b..c93be8b 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -53,7 +53,6 @@
 import com.android.launcher3.widget.picker.WidgetsRecyclerView;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -199,8 +198,8 @@
         // Test that ensureWorkspaceIsScrollable adds a page by dragging an icon there.
         executeOnLauncher(launcher -> assertFalse("Initial workspace state is scrollable",
                 isWorkspaceScrollable(launcher)));
-        assertNull("Chrome app was found on empty workspace",
-                workspace.tryGetWorkspaceAppIcon("Chrome"));
+        workspace.verifyWorkspaceAppIconIsGone(
+                "Chrome app was found on empty workspace", "Chrome");
 
         workspace.ensureWorkspaceIsScrollable();
 
@@ -388,10 +387,10 @@
         folder.getAppIcon(GMAIL_APP_NAME);
         Workspace workspace = folder.close();
 
-        assertNull(STORE_APP_NAME + " should be moved to a folder.",
-                workspace.tryGetWorkspaceAppIcon(STORE_APP_NAME));
-        assertNull(GMAIL_APP_NAME + " should be moved to a folder.",
-                workspace.tryGetWorkspaceAppIcon(GMAIL_APP_NAME));
+        workspace.verifyWorkspaceAppIconIsGone(STORE_APP_NAME + " should be moved to a folder.",
+                STORE_APP_NAME);
+        workspace.verifyWorkspaceAppIconIsGone(GMAIL_APP_NAME + " should be moved to a folder.",
+                GMAIL_APP_NAME);
 
         final HomeAppIcon mapIcon = createShortcutInCenterIfNotExist(MAPS_APP_NAME);
         folderIcon = mapIcon.dragToIcon(folderIcon);
@@ -399,8 +398,8 @@
         folder.getAppIcon(MAPS_APP_NAME);
         workspace = folder.close();
 
-        assertNull(MAPS_APP_NAME + " should be moved to a folder.",
-                workspace.tryGetWorkspaceAppIcon(MAPS_APP_NAME));
+        workspace.verifyWorkspaceAppIconIsGone(MAPS_APP_NAME + " should be moved to a folder.",
+                MAPS_APP_NAME);
     }
 
     @Test
@@ -424,8 +423,9 @@
         for (String appName : new String[]{"Gmail", "Play Store", APP_NAME}) {
             final HomeAppIcon homeAppIcon = createShortcutInCenterIfNotExist(appName);
             Workspace workspace = mLauncher.getWorkspace().deleteAppIcon(homeAppIcon);
-            assertNull(appName + " app was found after being deleted from workspace",
-                    workspace.tryGetWorkspaceAppIcon(appName));
+            workspace.verifyWorkspaceAppIconIsGone(
+                    appName + " app was found after being deleted from workspace",
+                    appName);
         }
     }
 
@@ -509,10 +509,8 @@
                     .containsAtLeast(DUMMY_APP_NAME, MAPS_APP_NAME, STORE_APP_NAME);
 
             mLauncher.getWorkspace().getWorkspaceAppIcon(DUMMY_APP_NAME).uninstall();
-
-            assertNull(
-                    DUMMY_APP_NAME + " app was found after being uninstalled",
-                    mLauncher.getWorkspace().tryGetWorkspaceAppIcon(DUMMY_APP_NAME));
+            mLauncher.getWorkspace().verifyWorkspaceAppIconIsGone(
+                    DUMMY_APP_NAME + " was expected to disappear after uninstall.", DUMMY_APP_NAME);
 
             Map<String, Point> finalPositions =
                     mLauncher.getWorkspace().getWorkspaceIconsPositions();
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index eb7f05b..e919740 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -140,6 +140,20 @@
         }
     }
 
+    /**
+     * Waits for an app icon to be gone (e.g. after uninstall). Fails if it remains.
+     *
+     * @param errorMessage error message thrown then the icon doesn't disappear.
+     * @param appName      app that should be gone.
+     */
+    public void verifyWorkspaceAppIconIsGone(String errorMessage, String appName) {
+        final UiObject2 workspace = verifyActiveContainer();
+        assertTrue(errorMessage,
+                workspace.wait(
+                        Until.gone(AppIcon.getAppIconSelector(appName, mLauncher)),
+                        LauncherInstrumentation.WAIT_TIME_MS));
+    }
+
 
     /**
      * Returns an icon for the app; fails if the icon doesn't exist.