Merging ub-launcher3-qt-dev, build 5508646

Test: Manual

Bug:112934365 [STOP SHIP BUG] Sysui Navigation Bar Prototype Tracking Bug:114136250 Have a more spartan RecentsActivity on android go Bug:118758133 Add logging to track undo box usage Bug:119992316 Widget screen won't close when tapping back or home button Bug:128857973 With dark display and black accent device theme, App list header not visible on all apps screen. Bug:129279637 [Logging] Long press|tap on app icon from Task should be logged Bug:129497226 Remove setInteractionState from the contract Bug:129723135 Broken tests: swipe from killed launcher doesn't open overview Bug:129806241 [a11y][Qt] Remove an app icon in Home screen, the popup "Item removed" with UNDO doesn't follow the settings of Time to take action. Bug:129985827 [Fully Gestural Navigation] Delay Recents animation when swiping up Bug:130851537 Recents and notification shelf both became nonresponsive after I "palmed" the screen while it was on. Bug:131095241 Recents Go should support landscape app => landscape thumbnail transition Bug:131303610 Backedup shortcut icons are not badged Bug:131339235 Don't layout empty task views if we have the real content Bug:131364673 [Failing test] FallbackRecentsTest
Change-Id: I04018e14bd6d9ba77005e72fe20f4a3ede886abf
diff --git a/go/quickstep/res/drawable/default_thumbnail.xml b/go/quickstep/res/drawable/default_thumbnail.xml
index 0a2dbf0..ab22dcf 100644
--- a/go/quickstep/res/drawable/default_thumbnail.xml
+++ b/go/quickstep/res/drawable/default_thumbnail.xml
@@ -18,5 +18,5 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="rectangle">
     <solid android:color="@android:color/darker_gray"/>
-    <corners android:radius="2dp"/>
+    <corners android:radius="@dimen/task_thumbnail_corner_radius"/>
 </shape>
diff --git a/go/quickstep/res/values/dimens.xml b/go/quickstep/res/values/dimens.xml
new file mode 100644
index 0000000..ee154fc
--- /dev/null
+++ b/go/quickstep/res/values/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources>
+    <dimen name="task_thumbnail_corner_radius">3dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java b/go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java
index 1b6f2e3..9282345 100644
--- a/go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java
+++ b/go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java
@@ -161,6 +161,13 @@
 
     private void animateChangeImpl(ViewHolder viewHolder, long startDelay) {
         TaskItemView itemView = (TaskItemView) viewHolder.itemView;
+        if (itemView.getAlpha() == 0) {
+            // View is still not visible, so we can finish the change immediately.
+            CONTENT_TRANSITION_PROGRESS.set(itemView, 1.0f);
+            dispatchChangeFinished(viewHolder, true /* oldItem */);
+            dispatchFinishedWhenDone();
+            return;
+        }
         final ObjectAnimator anim =
                 ObjectAnimator.ofFloat(itemView, CONTENT_TRANSITION_PROGRESS, 0.0f, 1.0f);
         anim.setDuration(ITEM_CHANGE_DURATION).setStartDelay(startDelay);
diff --git a/go/quickstep/src/com/android/quickstep/ThumbnailDrawable.java b/go/quickstep/src/com/android/quickstep/ThumbnailDrawable.java
index 6ef9039..a8cc0a1 100644
--- a/go/quickstep/src/com/android/quickstep/ThumbnailDrawable.java
+++ b/go/quickstep/src/com/android/quickstep/ThumbnailDrawable.java
@@ -16,17 +16,23 @@
 
 package com.android.quickstep;
 
+import static android.graphics.Shader.TileMode.CLAMP;
+
+import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
 import android.graphics.Canvas;
 import android.graphics.ColorFilter;
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 
 import androidx.annotation.NonNull;
 
+import com.android.launcher3.R;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
 /**
@@ -39,11 +45,18 @@
     private final Paint mPaint = new Paint();
     private final Matrix mMatrix = new Matrix();
     private final ThumbnailData mThumbnailData;
+    private final BitmapShader mShader;
+    private final RectF mDestRect = new RectF();
+    private final int mCornerRadius;
     private int mRequestedOrientation;
 
-    public ThumbnailDrawable(@NonNull ThumbnailData thumbnailData, int requestedOrientation) {
+    public ThumbnailDrawable(Resources res, @NonNull ThumbnailData thumbnailData,
+            int requestedOrientation) {
         mThumbnailData = thumbnailData;
         mRequestedOrientation = requestedOrientation;
+        mCornerRadius = (int) res.getDimension(R.dimen.task_thumbnail_corner_radius);
+        mShader = new BitmapShader(mThumbnailData.thumbnail, CLAMP, CLAMP);
+        mPaint.setShader(mShader);
         updateMatrix();
     }
 
@@ -64,12 +77,13 @@
         if (mThumbnailData.thumbnail == null) {
             return;
         }
-        canvas.drawBitmap(mThumbnailData.thumbnail, mMatrix, mPaint);
+        canvas.drawRoundRect(mDestRect, mCornerRadius, mCornerRadius, mPaint);
     }
 
     @Override
     protected void onBoundsChange(Rect bounds) {
         super.onBoundsChange(bounds);
+        mDestRect.set(bounds);
         updateMatrix();
     }
 
@@ -125,5 +139,6 @@
         }
         // Scale to fill.
         mMatrix.postScale(scaleX, scaleY);
+        mShader.setLocalMatrix(mMatrix);
     }
 }
diff --git a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java
index 0b5ed56..9019205 100644
--- a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java
+++ b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java
@@ -177,7 +177,8 @@
             return mDefaultThumbnail;
         }
         int orientation = getResources().getConfiguration().orientation;
-        return new ThumbnailDrawable(thumbnailData,  orientation /* requestedOrientation */);
+        return new ThumbnailDrawable(getResources(), thumbnailData,
+                orientation /* requestedOrientation */);
     }
 
     private @NonNull String getSafeLabel(@Nullable String label) {
diff --git a/go/quickstep/src/com/android/quickstep/views/TaskThumbnailIconView.java b/go/quickstep/src/com/android/quickstep/views/TaskThumbnailIconView.java
index b1c60dd..0bad77b 100644
--- a/go/quickstep/src/com/android/quickstep/views/TaskThumbnailIconView.java
+++ b/go/quickstep/src/com/android/quickstep/views/TaskThumbnailIconView.java
@@ -16,6 +16,9 @@
 
 package com.android.quickstep.views;
 
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.AttributeSet;
@@ -53,15 +56,20 @@
         int width = height;
         setMeasuredDimension(width, height);
 
+
         int subItemSize = (int) (SUBITEM_FRAME_RATIO * height);
         if (mThumbnailView.getVisibility() != GONE) {
-            int thumbnailHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
-            int thumbnailWidthSpec = MeasureSpec.makeMeasureSpec(subItemSize, MeasureSpec.EXACTLY);
+            boolean isPortrait =
+                    (getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT);
+            int thumbnailHeightSpec =
+                    makeMeasureSpec(isPortrait ? height : subItemSize, MeasureSpec.EXACTLY);
+            int thumbnailWidthSpec =
+                    makeMeasureSpec(isPortrait ? subItemSize : width, MeasureSpec.EXACTLY);
             measureChild(mThumbnailView, thumbnailWidthSpec, thumbnailHeightSpec);
         }
         if (mIconView.getVisibility() != GONE) {
-            int iconHeightSpec = MeasureSpec.makeMeasureSpec(subItemSize, MeasureSpec.EXACTLY);
-            int iconWidthSpec = MeasureSpec.makeMeasureSpec(subItemSize, MeasureSpec.EXACTLY);
+            int iconHeightSpec = makeMeasureSpec(subItemSize, MeasureSpec.EXACTLY);
+            int iconWidthSpec = makeMeasureSpec(subItemSize, MeasureSpec.EXACTLY);
             measureChild(mIconView, iconWidthSpec, iconHeightSpec);
         }
     }
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index 4974dcb..02c6b0f 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -71,6 +71,7 @@
   NOTIFICATION = 8;
   TASK = 9;         // Each page of Recents UI (QuickStep)
   WEB_APP = 10;
+  TASK_ICON = 11;
 }
 
 // Used to define what type of container a Target would represent.
@@ -115,6 +116,7 @@
   REMOTE_ACTION_SHORTCUT = 17;
   APP_USAGE_SETTINGS = 18;
   BACK_GESTURE = 19;
+  UNDO = 20;
 }
 
 enum TipType {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java
index a76ecd5..5e7faf7 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java
@@ -45,7 +45,7 @@
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.system.InputMonitorCompat;
-import com.android.systemui.shared.system.NavigationBarCompat;
+import com.android.systemui.shared.system.QuickStepContract;
 
 /**
  * Touch consumer for handling events to launch assistant from launcher
@@ -103,7 +103,7 @@
         mDistThreshold = res.getDimension(R.dimen.gestures_assistant_drag_threshold);
         mTimeThreshold = res.getInteger(R.integer.assistant_gesture_min_time_threshold);
         mAngleThreshold = res.getInteger(R.integer.assistant_gesture_corner_deg_threshold);
-        mSlop = NavigationBarCompat.getQuickStepDragSlopPx();
+        mSlop = QuickStepContract.getQuickStepDragSlopPx();
         mInputMonitorCompat = inputMonitorCompat;
         mActivityControlHelper = activityControlHelper;
         mState = STATE_INACTIVE;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
index 35783b5..a033402 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -16,14 +16,15 @@
 package com.android.quickstep;
 
 import static android.view.View.TRANSLATION_Y;
-
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_DAMPING_RATIO;
 import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_STIFFNESS;
+import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.quickstep.WindowTransformSwipeHandler.RECENTS_ATTACH_DURATION;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -177,6 +178,8 @@
         return new AnimationFactory() {
             private Animator mShelfAnim;
             private ShelfAnimState mShelfState;
+            private Animator mAttachToWindowAnim;
+            private boolean mIsAttachedToWindow;
 
             @Override
             public void createActivityController(long transitionLength) {
@@ -221,6 +224,28 @@
                 mShelfAnim.setDuration(duration);
                 mShelfAnim.start();
             }
+
+            @Override
+            public void setRecentsAttachedToAppWindow(boolean attached, boolean animate) {
+                if (mIsAttachedToWindow == attached && animate) {
+                    return;
+                }
+                mIsAttachedToWindow = attached;
+                if (mAttachToWindowAnim != null) {
+                    mAttachToWindowAnim.cancel();
+                }
+                mAttachToWindowAnim = ObjectAnimator.ofFloat(activity.getOverviewPanel(),
+                        RecentsView.CONTENT_ALPHA, attached ? 1 : 0);
+                mAttachToWindowAnim.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mAttachToWindowAnim = null;
+                    }
+                });
+                mAttachToWindowAnim.setInterpolator(ACCEL_DEACCEL);
+                mAttachToWindowAnim.setDuration(animate ? RECENTS_ATTACH_DURATION : 0);
+                mAttachToWindowAnim.start();
+            }
         };
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
index 44ba515..5dc641f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
@@ -60,7 +60,7 @@
 import com.android.systemui.shared.system.BackgroundExecutor;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.InputMonitorCompat;
-import com.android.systemui.shared.system.NavigationBarCompat;
+import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
 import java.util.function.Consumer;
@@ -152,8 +152,8 @@
         mDisplayRotation = display.getRotation();
         WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
 
-        mDragSlop = NavigationBarCompat.getQuickStepDragSlopPx();
-        mTouchSlop = NavigationBarCompat.getQuickStepTouchSlopPx();
+        mDragSlop = QuickStepContract.getQuickStepDragSlopPx();
+        mTouchSlop = QuickStepContract.getQuickStepTouchSlopPx();
 
         mPassedTouchSlop = mPassedDragSlop = continuingPreviousGesture;
     }
@@ -267,6 +267,7 @@
                         mMotionPauseDetector.setDisallowPause(upDist < mMotionPauseMinDisplacement
                                 || isLikelyToStartNewTask);
                         mMotionPauseDetector.addPosition(displacement, ev.getEventTime());
+                        mInteractionHandler.setIsLikelyToStartNewTask(isLikelyToStartNewTask);
                     }
                 }
                 break;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
index e1b38b3..afc4fcb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -43,6 +43,7 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
@@ -111,6 +112,8 @@
         implements SwipeAnimationListener, OnApplyWindowInsetsListener {
     private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
 
+    private static final Rect TEMP_RECT = new Rect();
+
     private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null;
 
     private static int getFlagForIndex(int index, String name) {
@@ -162,22 +165,23 @@
             STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
 
     enum GestureEndTarget {
-        HOME(1, STATE_SCALED_CONTROLLER_HOME, true, false, ContainerType.WORKSPACE),
+        HOME(1, STATE_SCALED_CONTROLLER_HOME, true, false, ContainerType.WORKSPACE, false),
 
         RECENTS(1, STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
-                | STATE_SCREENSHOT_VIEW_SHOWN, true, false, ContainerType.TASKSWITCHER),
+                | STATE_SCREENSHOT_VIEW_SHOWN, true, false, ContainerType.TASKSWITCHER, true),
 
-        NEW_TASK(0, STATE_START_NEW_TASK, false, true, ContainerType.APP),
+        NEW_TASK(0, STATE_START_NEW_TASK, false, true, ContainerType.APP, true),
 
-        LAST_TASK(0, STATE_RESUME_LAST_TASK, false, true, ContainerType.APP);
+        LAST_TASK(0, STATE_RESUME_LAST_TASK, false, true, ContainerType.APP, false);
 
         GestureEndTarget(float endShift, int endState, boolean isLauncher, boolean canBeContinued,
-                int containerType) {
+                int containerType, boolean recentsAttachedToAppWindow) {
             this.endShift = endShift;
             this.endState = endState;
             this.isLauncher = isLauncher;
             this.canBeContinued = canBeContinued;
             this.containerType = containerType;
+            this.recentsAttachedToAppWindow = recentsAttachedToAppWindow;
         }
 
         /** 0 is app, 1 is overview */
@@ -190,6 +194,8 @@
         public final boolean canBeContinued;
         /** Used to log where the user ended up after the gesture ends */
         public final int containerType;
+        /** Whether RecentsView should be attached to the window as we animate to this target */
+        public final boolean recentsAttachedToAppWindow;
     }
 
     public static final long MAX_SWIPE_DURATION = 350;
@@ -202,6 +208,7 @@
     private static final String SCREENSHOT_CAPTURED_EVT = "ScreenshotCaptured";
 
     private static final long SHELF_ANIM_DURATION = 120;
+    public static final long RECENTS_ATTACH_DURATION = 300;
 
     /**
      * Used as the page index for logging when we return to the last task at the end of the gesture.
@@ -254,6 +261,7 @@
     private int mLogAction = Touch.SWIPE;
     private int mLogDirection = Direction.UP;
     private PointF mDownPos;
+    private boolean mIsLikelyToStartNewTask;
 
     private final RecentsAnimationWrapper mRecentsAnimationWrapper;
 
@@ -277,6 +285,9 @@
 
         mMode = SysUINavigationMode.getMode(context);
         initStateCallbacks();
+
+        DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext);
+        initTransitionEndpoints(dp);
     }
 
     private void initStateCallbacks() {
@@ -434,6 +445,7 @@
                 mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity,
                         mWasLauncherAlreadyVisible, true,
                         this::onAnimatorPlaybackControllerCreated);
+                maybeUpdateRecentsAttachedState(false /* animate */);
             };
             if (mWasLauncherAlreadyVisible) {
                 // Launcher is visible, but might be about to stop. Thus, if we prepare recents
@@ -538,10 +550,65 @@
         setShelfState(isPaused ? PEEK : HIDE, FAST_OUT_SLOW_IN, SHELF_ANIM_DURATION);
     }
 
+    public void maybeUpdateRecentsAttachedState() {
+        maybeUpdateRecentsAttachedState(true /* animate */);
+    }
+
+    /**
+     * Determines whether to show or hide RecentsView. The window is always
+     * synchronized with its corresponding TaskView in RecentsView, so if
+     * RecentsView is shown, it will appear to be attached to the window.
+     *
+     * Note this method has no effect unless the navigation mode is NO_BUTTON.
+     */
+    private void maybeUpdateRecentsAttachedState(boolean animate) {
+        if (mMode != Mode.NO_BUTTON || mRecentsView == null) {
+            return;
+        }
+        RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationWrapper.targetSet == null
+                ? null
+                : mRecentsAnimationWrapper.targetSet.findTask(mRunningTaskId);
+        final boolean recentsAttachedToAppWindow;
+        int runningTaskIndex = mRecentsView.getRunningTaskIndex();
+        if (mContinuingLastGesture) {
+            recentsAttachedToAppWindow = true;
+            animate = false;
+        } else if (runningTaskTarget != null && isNotInRecents(runningTaskTarget)) {
+            // The window is going away so make sure recents is always visible in this case.
+            recentsAttachedToAppWindow = true;
+            animate = false;
+        } else {
+            if (mGestureEndTarget != null) {
+                recentsAttachedToAppWindow = mGestureEndTarget.recentsAttachedToAppWindow;
+            } else {
+                recentsAttachedToAppWindow = mIsShelfPeeking || mIsLikelyToStartNewTask;
+            }
+            if (animate) {
+                // Only animate if an adjacent task view is visible on screen.
+                TaskView adjacentTask1 = mRecentsView.getTaskViewAt(runningTaskIndex + 1);
+                TaskView adjacentTask2 = mRecentsView.getTaskViewAt(runningTaskIndex - 1);
+                animate = (adjacentTask1 != null && adjacentTask1.getGlobalVisibleRect(TEMP_RECT))
+                        || (adjacentTask2 != null && adjacentTask2.getGlobalVisibleRect(TEMP_RECT));
+            }
+        }
+        mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
+    }
+
+    public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) {
+        if (mIsLikelyToStartNewTask != isLikelyToStartNewTask) {
+            mIsLikelyToStartNewTask = isLikelyToStartNewTask;
+            maybeUpdateRecentsAttachedState();
+        }
+    }
+
     @UiThread
     public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration) {
         mAnimationFactory.setShelfState(shelfState, interpolator, duration);
+        boolean wasShelfPeeking = mIsShelfPeeking;
         mIsShelfPeeking = shelfState == PEEK;
+        if (mIsShelfPeeking != wasShelfPeeking) {
+            maybeUpdateRecentsAttachedState();
+        }
         if (mRecentsView != null && shelfState.shouldPreformHaptic) {
             mRecentsView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
                     HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
@@ -869,6 +936,8 @@
             Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
         mGestureEndTarget = target;
 
+        maybeUpdateRecentsAttachedState();
+
         if (mGestureEndTarget == HOME) {
             HomeAnimationFactory homeAnimFactory;
             if (mActivity != null) {
@@ -902,8 +971,15 @@
             windowAnim.start(velocityPxPerMs);
             mLauncherTransitionController = null;
         } else {
-            Animator windowAnim = mCurrentShift.animateToValue(start, end);
+            ValueAnimator windowAnim = mCurrentShift.animateToValue(start, end);
             windowAnim.setDuration(duration).setInterpolator(interpolator);
+            windowAnim.addUpdateListener(valueAnimator -> {
+                if (mRecentsView != null && mRecentsView.getVisibility() != View.VISIBLE) {
+                    // Views typically don't compute scroll when invisible as an optimization,
+                    // but in our case we need to since the window offset depends on the scroll.
+                    mRecentsView.computeScroll();
+                }
+            });
             windowAnim.addListener(new AnimationSuccessListener() {
                 @Override
                 public void onAnimationSuccess(Animator animator) {
@@ -1177,10 +1253,14 @@
     }
 
     public static float getHiddenTargetAlpha(RemoteAnimationTargetCompat app, Float expectedAlpha) {
-        if (!(app.isNotInRecents
-                || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME)) {
+        if (!isNotInRecents(app)) {
             return 0;
         }
         return expectedAlpha;
     }
+
+    private static boolean isNotInRecents(RemoteAnimationTargetCompat app) {
+        return app.isNotInRecents
+                || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 3ca8b18..2f739c8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -48,6 +48,8 @@
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.PendingAnimation;
@@ -365,9 +367,11 @@
         }
     }
 
-    private boolean showTaskMenu() {
+    private boolean showTaskMenu(int action) {
         getRecentsView().snapToPage(getRecentsView().indexOfChild(this));
         mMenuView = TaskMenuView.showForTask(this);
+        UserEventDispatcher.newInstance(getContext()).logActionOnItem(action, Direction.NONE,
+                LauncherLogProto.ItemType.TASK_ICON);
         if (mMenuView != null) {
             mMenuView.addOnAttachStateChangeListener(mTaskMenuStateListener);
         }
@@ -377,10 +381,10 @@
     private void setIcon(Drawable icon) {
         if (icon != null) {
             mIconView.setDrawable(icon);
-            mIconView.setOnClickListener(v -> showTaskMenu());
+            mIconView.setOnClickListener(v -> showTaskMenu(Touch.TAP));
             mIconView.setOnLongClickListener(v -> {
                 requestDisallowInterceptTouchEvent(true);
-                return showTaskMenu();
+                return showTaskMenu(Touch.LONGPRESS);
             });
         } else {
             mIconView.setDrawable(null);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
index 12e6f12..fee1820 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
@@ -32,6 +32,8 @@
 import com.android.quickstep.RecentsModel;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 
+import java.io.PrintWriter;
+
 /**
  * TouchController for handling touch events that get sent to the StatusBar. Once the
  * Once the event delta y passes the touch slop, the events start getting forwarded.
@@ -45,6 +47,7 @@
     protected final TouchEventTranslator mTranslator;
     private final float mTouchSlop;
     private ISystemUiProxy mSysUiProxy;
+    private int mLastAction;
 
     /* If {@code false}, this controller should not handle the input {@link MotionEvent}.*/
     private boolean mCanIntercept;
@@ -56,9 +59,18 @@
         mTranslator = new TouchEventTranslator((MotionEvent ev)-> dispatchTouchEvent(ev));
     }
 
+    @Override
+    public void dump(String prefix, PrintWriter writer) {
+        writer.println(prefix + "mCanIntercept:" + mCanIntercept);
+        writer.println(prefix + "mLastAction:" + MotionEvent.actionToString(mLastAction));
+        writer.println(prefix + "mSysUiProxy available:" + (mSysUiProxy != null));
+
+    }
+
     private void dispatchTouchEvent(MotionEvent ev) {
         try {
             if (mSysUiProxy != null) {
+                mLastAction = ev.getActionMasked();
                 mSysUiProxy.onStatusBarMotionEvent(ev);
             }
         } catch (RemoteException e) {
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index a71b7bb..17f88c9 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -27,6 +27,10 @@
 import android.view.View;
 import android.view.animation.Interpolator;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -37,10 +41,6 @@
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-
 /**
  * Utility class which abstracts out the logical differences between Launcher and RecentsActivity.
  */
@@ -122,6 +122,13 @@
 
         default void setShelfState(ShelfAnimState animState, Interpolator interpolator,
                 long duration) { }
+
+        /**
+         * @param attached Whether to show RecentsView alongside the app window. If false, recents
+         *                 will be hidden by some property we can animate, e.g. alpha.
+         * @param animate Whether to animate recents to/from its new attached state.
+         */
+        default void setRecentsAttachedToAppWindow(boolean attached, boolean animate) { }
     }
 
     interface HomeAnimationFactory {
diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
index 286ddc0..79d922c 100644
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
@@ -15,10 +15,6 @@
  */
 package com.android.quickstep;
 
-import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_QUICK_SCRUB;
-import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_SWIPE_UP;
-import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON;
-
 import android.content.Context;
 import android.os.Handler;
 import android.os.Message;
@@ -34,13 +30,7 @@
 import androidx.annotation.WorkerThread;
 
 /**
- * Sets overview interaction flags, such as:
- *
- *   - FLAG_DISABLE_QUICK_SCRUB
- *   - FLAG_DISABLE_SWIPE_UP
- *   - FLAG_SHOW_OVERVIEW_BUTTON
- *
- * @see com.android.systemui.shared.system.NavigationBarCompat.InteractionType and associated flags.
+ * Sets alpha for the back button
  */
 public class OverviewInteractionState {
 
@@ -50,11 +40,10 @@
 
     // We do not need any synchronization for this variable as its only written on UI thread.
     public static final MainThreadInitializedObject<OverviewInteractionState> INSTANCE =
-            new MainThreadInitializedObject<>((c) -> new OverviewInteractionState(c));
+            new MainThreadInitializedObject<>(OverviewInteractionState::new);
 
     private static final int MSG_SET_PROXY = 200;
     private static final int MSG_SET_BACK_BUTTON_ALPHA = 201;
-    private static final int MSG_APPLY_FLAGS = 202;
 
     private final Context mContext;
     private final Handler mUiHandler;
@@ -62,7 +51,6 @@
 
     // These are updated on the background thread
     private ISystemUiProxy mISystemUiProxy;
-    private boolean mSwipeUpEnabled;
     private float mBackButtonAlpha = 1;
 
     private OverviewInteractionState(Context context) {
@@ -83,7 +71,7 @@
     }
 
     public void setBackButtonAlpha(float alpha, boolean animate) {
-        if (!mSwipeUpEnabled) {
+        if (!modeSupportsGestures()) {
             alpha = 1;
         }
         mUiHandler.removeMessages(MSG_SET_BACK_BUTTON_ALPHA);
@@ -111,31 +99,11 @@
             case MSG_SET_BACK_BUTTON_ALPHA:
                 applyBackButtonAlpha((float) msg.obj, msg.arg1 == 1);
                 return true;
-            case MSG_APPLY_FLAGS:
-                break;
         }
-        applyFlags();
         return true;
     }
 
     @WorkerThread
-    private void applyFlags() {
-        if (mISystemUiProxy == null) {
-            return;
-        }
-
-        int flags = FLAG_DISABLE_QUICK_SCRUB;
-        if (!mSwipeUpEnabled) {
-            flags = FLAG_DISABLE_SWIPE_UP | FLAG_DISABLE_QUICK_SCRUB | FLAG_SHOW_OVERVIEW_BUTTON;
-        }
-        try {
-            mISystemUiProxy.setInteractionState(flags);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Unable to update overview interaction flags", e);
-        }
-    }
-
-    @WorkerThread
     private void applyBackButtonAlpha(float alpha, boolean animate) {
         if (mISystemUiProxy == null) {
             return;
@@ -148,18 +116,20 @@
     }
 
     private void onNavigationModeChanged(SysUINavigationMode.Mode mode) {
-        mSwipeUpEnabled = mode.hasGestures;
         resetHomeBounceSeenOnQuickstepEnabledFirstTime();
-        mBgHandler.obtainMessage(MSG_APPLY_FLAGS).sendToTarget();
     }
 
     private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
-        if (mSwipeUpEnabled && !Utilities.getPrefs(mContext).getBoolean(
+        if (modeSupportsGestures() && !Utilities.getPrefs(mContext).getBoolean(
                 HAS_ENABLED_QUICKSTEP_ONCE, true)) {
             Utilities.getPrefs(mContext).edit()
-                    .putBoolean(HAS_ENABLED_QUICKSTEP_ONCE, true)
-                    .putBoolean(DiscoveryBounce.HOME_BOUNCE_SEEN, false)
-                    .apply();
+                .putBoolean(HAS_ENABLED_QUICKSTEP_ONCE, true)
+                .putBoolean(DiscoveryBounce.HOME_BOUNCE_SEEN, false)
+                .apply();
         }
     }
+
+    private boolean modeSupportsGestures() {
+        return SysUINavigationMode.getMode(mContext).hasGestures;
+    }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
index 8798157..70f9c90 100644
--- a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
+++ b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
@@ -12,21 +12,29 @@
 import android.app.usage.UsageStatsManager;
 import android.content.Intent;
 
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
 import com.android.launcher3.Launcher;
 import com.android.quickstep.views.DigitalWellBeingToast;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.time.Duration;
 
+@LargeTest
+@RunWith(AndroidJUnit4.class)
 public class DigitalWellBeingToastTest extends AbstractQuickStepTest {
     private static final String CALCULATOR_PACKAGE =
             resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR);
 
     @Test
     public void testToast() throws Exception {
+        startAppFast(CALCULATOR_PACKAGE);
+
         final UsageStatsManager usageStatsManager =
                 mTargetContext.getSystemService(UsageStatsManager.class);
         final int observerId = 0;
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index f436831..20fdff2 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -32,6 +32,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.os.RemoteException;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -65,10 +66,11 @@
 
     @Rule public final TestRule mSetLauncherCommand;
 
-    public FallbackRecentsTest() {
+    public FallbackRecentsTest() throws RemoteException {
         Instrumentation instrumentation = getInstrumentation();
         Context context = instrumentation.getContext();
         mDevice = UiDevice.getInstance(instrumentation);
+        mDevice.setOrientationNatural();
         mLauncher = new LauncherInstrumentation(instrumentation);
 
         mQuickstepOnOffExecutor = new NavigationModeSwitchRule(mLauncher);
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index a47c8e7..8bdb90d 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -88,7 +88,6 @@
     }
 
     @Test
-    @Ignore // b/129723135
     @NavigationModeSwitch
     public void testStressSwipeToOverview() {
         for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 9b84cc9..7932c6d 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -19,12 +19,13 @@
 
 <resources>
     <!-- Launcher theme -->
-    <style name="BaseLauncherTheme" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar">
-        <item name="android:windowBackground">@android:color/transparent</item>
+    <style name="BaseLauncherTheme" parent="@android:style/Theme.DeviceDefault.DayNight">
         <item name="android:colorBackgroundCacheHint">@null</item>
-        <item name="android:windowShowWallpaper">true</item>
-        <item name="android:windowNoTitle">true</item>
         <item name="android:colorEdgeEffect">#FF757575</item>
+        <item name="android:windowActionBar">false</item>
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowShowWallpaper">true</item>
     </style>
 
     <style name="LauncherTheme" parent="@style/BaseLauncherTheme">
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 8cbad20..3347b2a 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.UNDO;
+
 import android.content.Context;
 import android.text.TextUtils;
 import android.util.AttributeSet;
@@ -121,8 +124,12 @@
             int itemPage = mLauncher.getWorkspace().getCurrentPage();
             onAccessibilityDrop(null, item);
             ModelWriter modelWriter = mLauncher.getModelWriter();
+            Runnable onUndoClicked = () -> {
+                modelWriter.abortDelete(itemPage);
+                mLauncher.getUserEventDispatcher().logActionOnControl(TAP, UNDO);
+            };
             Snackbar.show(mLauncher, R.string.item_removed, R.string.undo,
-                    modelWriter::commitDelete, () -> modelWriter.abortDelete(itemPage));
+                    modelWriter::commitDelete, onUndoClicked);
         }
     }
 
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index d790c04..fda674f 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -75,6 +75,8 @@
 import android.view.animation.OvershootInterpolator;
 import android.widget.Toast;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.allapps.AllAppsContainerView;
@@ -150,9 +152,6 @@
 import java.util.List;
 import java.util.function.Predicate;
 
-import androidx.annotation.IdRes;
-import androidx.annotation.Nullable;
-
 /**
  * Default launcher application.
  */
@@ -2198,7 +2197,9 @@
                 InstallShortcutReceiver.FLAG_LOADER_RUNNING, this);
 
         // When undoing the removal of the last item on a page, return to that page.
-        mWorkspace.setCurrentPage(pageBoundFirst);
+        // Since we are just resetting the current page without user interaction,
+        // override the previous page so we don't log the page switch.
+        mWorkspace.setCurrentPage(pageBoundFirst, pageBoundFirst /* overridePrevPage */);
 
         TraceHelper.endSection("finishBindingItems");
     }
@@ -2345,7 +2346,8 @@
         writer.println(" mPendingActivityResult=" + mPendingActivityResult);
         writer.println(" mRotationHelper: " + mRotationHelper);
         // Extra logging for b/116853349
-        mDragLayer.dumpAlpha(writer);
+        mDragLayer.dump(prefix, writer);
+        mStateManager.dump(prefix, writer);
         dumpMisc(writer);
 
         try {
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 209578d..b24f660 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -50,6 +50,7 @@
 import com.android.launcher3.anim.PropertySetter.AnimatedPropertySetter;
 import com.android.launcher3.uioverrides.UiFactory;
 
+import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -144,6 +145,15 @@
         return mCurrentStableState;
     }
 
+    public void dump(String prefix, PrintWriter writer) {
+        writer.println(prefix + "LauncherState");
+        writer.println(prefix + "\tmLastStableState:" + mLastStableState);
+        writer.println(prefix + "\tmCurrentStableState:" + mCurrentStableState);
+        writer.println(prefix + "\tmState:" + mState);
+        writer.println(prefix + "\tmRestState:" + mRestState);
+        writer.println(prefix + "\tisInTransition:" + (mConfig.mCurrentAnimation != null));
+    }
+
     public StateHandler[] getStateHandlers() {
         if (mStateHandlers == null) {
             mStateHandlers = UiFactory.getStateHandler(mLauncher);
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index abb45e5..3a02b07 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -300,10 +300,14 @@
         return page;
     }
 
+    public void setCurrentPage(int currentPage) {
+        setCurrentPage(currentPage, INVALID_PAGE);
+    }
+
     /**
      * Sets the current page.
      */
-    public void setCurrentPage(int currentPage) {
+    public void setCurrentPage(int currentPage, int overridePrevPage) {
         if (!mScroller.isFinished()) {
             abortScrollerAnimation(true);
         }
@@ -312,7 +316,7 @@
         if (getChildCount() == 0) {
             return;
         }
-        int prevPage = mCurrentPage;
+        int prevPage = overridePrevPage != INVALID_PAGE ? overridePrevPage : mCurrentPage;
         mCurrentPage = validateNewPage(currentPage);
         updateCurrentPageScroll();
         notifyPageSwitchListener(prevPage);
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 51e914c..b4d0c54 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -106,4 +106,11 @@
         }
         return false;
     }
+
+    public static int getRecommendedTimeoutMillis(Context context, int originalTimeout, int flags) {
+        if (Utilities.ATLEAST_Q) {
+            return getManager(context).getRecommendedTimeoutMillis(originalTimeout, flags);
+        }
+        return originalTimeout;
+    }
 }
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 4d45ba9..7210759 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -696,5 +696,4 @@
     public void removeDropTarget(DropTarget target) {
         mDropTargets.remove(target);
     }
-
 }
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index 40c914b..4ce2f4b 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -47,6 +47,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.Executor;
+import java.util.function.Supplier;
 
 /**
  * Class for handling model updates.
@@ -151,15 +152,13 @@
     public void moveItemInDatabase(final ItemInfo item,
             int container, int screenId, int cellX, int cellY) {
         updateItemInfoProps(item, container, screenId, cellX, cellY);
-
-        final ContentWriter writer = new ContentWriter(mContext)
-                .put(Favorites.CONTAINER, item.container)
-                .put(Favorites.CELLX, item.cellX)
-                .put(Favorites.CELLY, item.cellY)
-                .put(Favorites.RANK, item.rank)
-                .put(Favorites.SCREEN, item.screenId);
-
-        enqueueDeleteRunnable(new UpdateItemRunnable(item, writer));
+        enqueueDeleteRunnable(new UpdateItemRunnable(item, () ->
+                new ContentWriter(mContext)
+                        .put(Favorites.CONTAINER, item.container)
+                        .put(Favorites.CELLX, item.cellX)
+                        .put(Favorites.CELLY, item.cellY)
+                        .put(Favorites.RANK, item.rank)
+                        .put(Favorites.SCREEN, item.screenId)));
     }
 
     /**
@@ -195,25 +194,26 @@
         item.spanX = spanX;
         item.spanY = spanY;
 
-        final ContentWriter writer = new ContentWriter(mContext)
-                .put(Favorites.CONTAINER, item.container)
-                .put(Favorites.CELLX, item.cellX)
-                .put(Favorites.CELLY, item.cellY)
-                .put(Favorites.RANK, item.rank)
-                .put(Favorites.SPANX, item.spanX)
-                .put(Favorites.SPANY, item.spanY)
-                .put(Favorites.SCREEN, item.screenId);
-
-        mWorkerExecutor.execute(new UpdateItemRunnable(item, writer));
+        mWorkerExecutor.execute(new UpdateItemRunnable(item, () ->
+                new ContentWriter(mContext)
+                        .put(Favorites.CONTAINER, item.container)
+                        .put(Favorites.CELLX, item.cellX)
+                        .put(Favorites.CELLY, item.cellY)
+                        .put(Favorites.RANK, item.rank)
+                        .put(Favorites.SPANX, item.spanX)
+                        .put(Favorites.SPANY, item.spanY)
+                        .put(Favorites.SCREEN, item.screenId)));
     }
 
     /**
      * Update an item to the database in a specified container.
      */
     public void updateItemInDatabase(ItemInfo item) {
-        ContentWriter writer = new ContentWriter(mContext);
-        item.onAddToDatabase(writer);
-        mWorkerExecutor.execute(new UpdateItemRunnable(item, writer));
+        mWorkerExecutor.execute(new UpdateItemRunnable(item, () -> {
+            ContentWriter writer = new ContentWriter(mContext);
+            item.onAddToDatabase(writer);
+            return writer;
+        }));
     }
 
     /**
@@ -224,17 +224,18 @@
             int container, int screenId, int cellX, int cellY) {
         updateItemInfoProps(item, container, screenId, cellX, cellY);
 
-        final ContentWriter writer = new ContentWriter(mContext);
         final ContentResolver cr = mContext.getContentResolver();
-        item.onAddToDatabase(writer);
-
         item.id = Settings.call(cr, Settings.METHOD_NEW_ITEM_ID).getInt(Settings.EXTRA_VALUE);
-        writer.put(Favorites._ID, item.id);
 
         ModelVerifier verifier = new ModelVerifier();
-
         final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
         mWorkerExecutor.execute(() -> {
+            // Write the item on background thread, as some properties might have been updated in
+            // the background.
+            final ContentWriter writer = new ContentWriter(mContext);
+            item.onAddToDatabase(writer);
+            writer.put(Favorites._ID, item.id);
+
             cr.insert(Favorites.CONTENT_URI, writer.getValues(mContext));
 
             synchronized (mBgDataModel) {
@@ -354,10 +355,10 @@
 
     private class UpdateItemRunnable extends UpdateItemBaseRunnable {
         private final ItemInfo mItem;
-        private final ContentWriter mWriter;
+        private final Supplier<ContentWriter> mWriter;
         private final int mItemId;
 
-        UpdateItemRunnable(ItemInfo item, ContentWriter writer) {
+        UpdateItemRunnable(ItemInfo item, Supplier<ContentWriter> writer) {
             mItem = item;
             mWriter = writer;
             mItemId = item.id;
@@ -366,7 +367,8 @@
         @Override
         public void run() {
             Uri uri = Favorites.getContentUri(mItemId);
-            mContext.getContentResolver().update(uri, mWriter.getValues(mContext), null, null);
+            mContext.getContentResolver().update(uri, mWriter.get().getValues(mContext),
+                    null, null);
             updateItemArrays(mItem, mItemId);
         }
     }
diff --git a/src/com/android/launcher3/util/IOUtils.java b/src/com/android/launcher3/util/IOUtils.java
index 77c21fe..f95f74d 100644
--- a/src/com/android/launcher3/util/IOUtils.java
+++ b/src/com/android/launcher3/util/IOUtils.java
@@ -16,12 +16,18 @@
 
 package com.android.launcher3.util;
 
+import android.content.Context;
+
+import com.android.launcher3.config.FeatureFlags;
+
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.UUID;
 
 /**
  * Supports various IO utility functions
@@ -52,4 +58,23 @@
         }
         return total;
     }
+
+    /**
+     * Utility method to debug binary data
+     */
+    public static String createTempFile(Context context, byte[] data) {
+        if (!FeatureFlags.IS_DOGFOOD_BUILD) {
+            throw new IllegalStateException("Method only allowed in development mode");
+        }
+
+        String name = UUID.randomUUID().toString();
+        File file = new File(context.getCacheDir(), name);
+        try (FileOutputStream fo = new FileOutputStream(file)) {
+            fo.write(data);
+            fo.flush();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return file.getAbsolutePath();
+    }
 }
diff --git a/src/com/android/launcher3/util/TouchController.java b/src/com/android/launcher3/util/TouchController.java
index 3cca215..fc1d819 100644
--- a/src/com/android/launcher3/util/TouchController.java
+++ b/src/com/android/launcher3/util/TouchController.java
@@ -18,6 +18,8 @@
 
 import android.view.MotionEvent;
 
+import java.io.PrintWriter;
+
 public interface TouchController {
 
     /**
@@ -29,4 +31,6 @@
      * Called when the draglayer receives a intercept touch event.
      */
     boolean onControllerInterceptTouchEvent(MotionEvent ev);
+
+    default void dump(String prefix, PrintWriter writer) { }
 }
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index ab72bbe..66cd536 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -383,8 +383,13 @@
         return mMultiValueAlpha.getProperty(index);
     }
 
-    public void dumpAlpha(PrintWriter writer) {
-        writer.println(" dragLayerAlpha : " + mMultiValueAlpha );
+    public void dump(String prefix, PrintWriter writer) {
+        writer.println(prefix + "DragLayer");
+        if (mActiveController != null) {
+            writer.println(prefix + "\tactiveController: " + mActiveController);
+            mActiveController.dump(prefix + "\t", writer);
+        }
+        writer.println(prefix + "\tdragLayerAlpha : " + mMultiValueAlpha );
     }
 
     public static class LayoutParams extends InsettableFrameLayout.LayoutParams {
diff --git a/src/com/android/launcher3/views/Snackbar.java b/src/com/android/launcher3/views/Snackbar.java
index 04b637b..dc0e2e0 100644
--- a/src/com/android/launcher3/views/Snackbar.java
+++ b/src/com/android/launcher3/views/Snackbar.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3.views;
 
+import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
+import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT;
+
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Rect;
@@ -29,6 +32,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.dragndrop.DragLayer;
 
 /**
@@ -38,7 +42,7 @@
 
     private static final long SHOW_DURATION_MS = 180;
     private static final long HIDE_DURATION_MS = 180;
-    private static final long TIMEOUT_DURATION_MS = 4000;
+    private static final int TIMEOUT_DURATION_MS = 4000;
 
     private final Launcher mLauncher;
     private Runnable mOnDismissed;
@@ -131,7 +135,9 @@
                 .setDuration(SHOW_DURATION_MS)
                 .setInterpolator(Interpolators.ACCEL_DEACCEL)
                 .start();
-        snackbar.postDelayed(() -> snackbar.close(true), TIMEOUT_DURATION_MS);
+        int timeout = AccessibilityManagerCompat.getRecommendedTimeoutMillis(launcher,
+                TIMEOUT_DURATION_MS, FLAG_CONTENT_TEXT | FLAG_CONTENT_CONTROLS);
+        snackbar.postDelayed(() -> snackbar.close(true), timeout);
     }
 
     @Override