Merge "Decouple recents view from window while swiping up" into ub-launcher3-qt-dev
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/TaskActionController.java b/go/quickstep/src/com/android/quickstep/TaskActionController.java
index 09e2367..0e921c0 100644
--- a/go/quickstep/src/com/android/quickstep/TaskActionController.java
+++ b/go/quickstep/src/com/android/quickstep/TaskActionController.java
@@ -20,6 +20,8 @@
 import android.app.ActivityOptions;
 import android.view.View;
 
+import androidx.annotation.NonNull;
+
 import com.android.quickstep.views.TaskItemView;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -39,11 +41,11 @@
     }
 
     /**
-     * Launch the task associated with the task holder, animating into the app.
+     * Launch the task associated with the task holder, animating into the app from the task view.
      *
      * @param viewHolder the task view holder to launch
      */
-    public void launchTask(TaskHolder viewHolder) {
+    public void launchTaskFromView(@NonNull TaskHolder viewHolder) {
         if (!viewHolder.getTask().isPresent()) {
             return;
         }
@@ -61,6 +63,17 @@
     }
 
     /**
+     * Launch the task directly with a basic animation.
+     *
+     * @param task the task to launch
+     */
+    public void launchTask(@NonNull Task task) {
+        ActivityOptions opts = ActivityOptions.makeBasic();
+        ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(task.key, opts,
+                null /* resultCallback */, null /* resultCallbackHandler */);
+    }
+
+    /**
      * Removes the task holder and the task, updating the model and the view.
      *
      * @param viewHolder the task view holder to remove
diff --git a/go/quickstep/src/com/android/quickstep/TaskAdapter.java b/go/quickstep/src/com/android/quickstep/TaskAdapter.java
index 4f2b422..509bf29 100644
--- a/go/quickstep/src/com/android/quickstep/TaskAdapter.java
+++ b/go/quickstep/src/com/android/quickstep/TaskAdapter.java
@@ -83,7 +83,8 @@
                 TaskItemView itemView = (TaskItemView) LayoutInflater.from(parent.getContext())
                         .inflate(R.layout.task_item_view, parent, false);
                 TaskHolder taskHolder = new TaskHolder(itemView);
-                itemView.setOnClickListener(view -> mTaskActionController.launchTask(taskHolder));
+                itemView.setOnClickListener(
+                        view -> mTaskActionController.launchTaskFromView(taskHolder));
                 return taskHolder;
             case ITEM_TYPE_CLEAR_ALL:
                 View clearView = LayoutInflater.from(parent.getContext())
diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
index b8c482d..cf6eb6d 100644
--- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
+++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
@@ -59,6 +59,8 @@
 import com.android.systemui.shared.recents.model.Task;
 
 import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
 import java.util.Optional;
 
 /**
@@ -263,23 +265,33 @@
      * the app. In that case, we launch the next most recent.
      */
     public void handleOverviewCommand() {
-        // TODO(130735711): Need to address case where most recent task is off screen/unattached.
-        ArrayList<TaskItemView> taskViews = getTaskViews();
-        int taskViewsSize = taskViews.size();
-        if (taskViewsSize <= 1) {
+        List<Task> tasks = mTaskLoader.getCurrentTaskList();
+        int tasksSize = tasks.size();
+        if (tasksSize == 0) {
             // Do nothing
             return;
         }
-        TaskHolder taskToLaunch;
-        if (mTransitionedFromApp && taskViewsSize > 1) {
+        Task taskToLaunch;
+        if (mTransitionedFromApp && tasksSize > 1) {
             // Launch the next most recent app
-            TaskItemView itemView = taskViews.get(1);
-            taskToLaunch = (TaskHolder) mTaskRecyclerView.getChildViewHolder(itemView);
+            taskToLaunch = tasks.get(1);
         } else {
             // Launch the most recent app
-            TaskItemView itemView = taskViews.get(0);
-            taskToLaunch = (TaskHolder) mTaskRecyclerView.getChildViewHolder(itemView);
+            taskToLaunch = tasks.get(0);
         }
+
+        // See if view for this task is attached, and if so, animate launch from that view.
+        ArrayList<TaskItemView> itemViews = getTaskViews();
+        for (int i = 0, size = itemViews.size(); i < size; i++) {
+            TaskItemView taskView = itemViews.get(i);
+            TaskHolder holder = (TaskHolder) mTaskRecyclerView.getChildViewHolder(taskView);
+            if (Objects.equals(holder.getTask(), Optional.of(taskToLaunch))) {
+                mTaskActionController.launchTaskFromView(holder);
+                return;
+            }
+        }
+
+        // Otherwise, just use a basic launch animation.
         mTaskActionController.launchTask(taskToLaunch);
     }
 
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/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
index e5b927d..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;
     }
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 2e76ec6..afc4fcb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -285,6 +285,9 @@
 
         mMode = SysUINavigationMode.getMode(context);
         initStateCallbacks();
+
+        DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext);
+        initTransitionEndpoints(dp);
     }
 
     private void initStateCallbacks() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index ce8b9cd..08a7616 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -26,6 +26,8 @@
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
 import static com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CLEAR_ALL_BUTTON;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
 import static com.android.quickstep.util.ClipAnimationHelper.TransformParams;
@@ -1160,6 +1162,7 @@
     @SuppressWarnings("unused")
     private void dismissAllTasks(View view) {
         runDismissAnimation(createAllTasksDismissAnimation(DISMISS_TASK_DURATION));
+        mActivity.getUserEventDispatcher().logActionOnControl(TAP, CLEAR_ALL_BUTTON);
     }
 
     private void dismissCurrentTask() {
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 05fe2d0..848c214 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
@@ -49,6 +49,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;
@@ -366,9 +368,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);
         }
@@ -378,10 +382,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/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
index 286ddc0..6c7bc77 100644
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
@@ -15,46 +15,32 @@
  */
 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;
 import android.os.RemoteException;
 import android.util.Log;
 
-import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 
 import androidx.annotation.WorkerThread;
+import com.android.systemui.shared.system.QuickStepContract;
 
 /**
- * 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 {
 
     private static final String TAG = "OverviewFlags";
 
-    private static final String HAS_ENABLED_QUICKSTEP_ONCE = "launcher.has_enabled_quickstep_once";
-
     // 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));
 
     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 +48,6 @@
 
     // These are updated on the background thread
     private ISystemUiProxy mISystemUiProxy;
-    private boolean mSwipeUpEnabled;
     private float mBackButtonAlpha = 1;
 
     private OverviewInteractionState(Context context) {
@@ -73,9 +58,6 @@
         // For example, send back alpha on uihandler to avoid flickering when setting its visibility
         mUiHandler = new Handler(this::handleUiMessage);
         mBgHandler = new Handler(UiThreadHelper.getBackgroundLooper(), this::handleBgMessage);
-
-        onNavigationModeChanged(SysUINavigationMode.INSTANCE.get(context)
-                .addModeChangeListener(this::onNavigationModeChanged));
     }
 
     public float getBackButtonAlpha() {
@@ -83,7 +65,7 @@
     }
 
     public void setBackButtonAlpha(float alpha, boolean animate) {
-        if (!mSwipeUpEnabled) {
+        if (QuickStepContract.isLegacyMode(SysUINavigationMode.getMode(mContext).resValue)) {
             alpha = 1;
         }
         mUiHandler.removeMessages(MSG_SET_BACK_BUTTON_ALPHA);
@@ -111,31 +93,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;
@@ -146,20 +108,4 @@
             Log.w(TAG, "Unable to update overview back button alpha", e);
         }
     }
-
-    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(
-                HAS_ENABLED_QUICKSTEP_ONCE, true)) {
-            Utilities.getPrefs(mContext).edit()
-                    .putBoolean(HAS_ENABLED_QUICKSTEP_ONCE, true)
-                    .putBoolean(DiscoveryBounce.HOME_BOUNCE_SEEN, false)
-                    .apply();
-        }
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index 4b24a2f..f58f0d4 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -75,7 +75,7 @@
      */
     public void setDisallowPause(boolean disallowPause) {
         mDisallowPause = disallowPause;
-        updatePaused(false);
+        updatePaused(mIsPaused);
     }
 
     /**
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/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