Merge "Revert "Revert "Temporarily make TouchInteractionService direct boot unaware""" into ub-launcher3-master
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index afc8e47..47e0e61 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -19,14 +19,12 @@
 import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
 
-import android.graphics.Rect;
-
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.quickstep.RecentsModel;
+import com.android.quickstep.views.IconRecentsView;
 
 /**
  * Definition for overview state
@@ -50,6 +48,12 @@
     }
 
     @Override
+    public void onStateEnabled(Launcher launcher) {
+        IconRecentsView recentsView = launcher.getOverviewPanel();
+        recentsView.onBeginTransitionToOverview();
+    }
+
+    @Override
     public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
         return new PageAlphaProvider(DEACCEL_2) {
             @Override
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
index 12f5360..d7cba39 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
@@ -35,7 +35,8 @@
  * Provides recents-related {@link UiFactory} logic and classes.
  */
 public abstract class RecentsUiFactory {
-
+    
+    public static final boolean GO_LOW_RAM_RECENTS_ENABLED = true;
     // Scale recents takes before animating in
     private static final float RECENTS_PREPARE_SCALE = 1.33f;
 
diff --git a/go/quickstep/src/com/android/quickstep/TaskAdapter.java b/go/quickstep/src/com/android/quickstep/TaskAdapter.java
index 77c3f33..57cd60a 100644
--- a/go/quickstep/src/com/android/quickstep/TaskAdapter.java
+++ b/go/quickstep/src/com/android/quickstep/TaskAdapter.java
@@ -33,10 +33,10 @@
 
     private static final int MAX_TASKS_TO_DISPLAY = 6;
     private static final String TAG = "TaskAdapter";
-    private final ArrayList<Task> mTaskList;
+    private final TaskListLoader mLoader;
 
-    public TaskAdapter(@NonNull ArrayList<Task> taskList) {
-        mTaskList = taskList;
+    public TaskAdapter(@NonNull TaskListLoader loader) {
+        mLoader = loader;
     }
 
     @Override
@@ -48,11 +48,16 @@
 
     @Override
     public void onBindViewHolder(TaskHolder holder, int position) {
-        holder.bindTask(mTaskList.get(position));
+        ArrayList<Task> tasks = mLoader.getCurrentTaskList();
+        if (position >= tasks.size()) {
+            // Task list has updated.
+            return;
+        }
+        holder.bindTask(tasks.get(position));
     }
 
     @Override
     public int getItemCount() {
-        return Math.min(mTaskList.size(), MAX_TASKS_TO_DISPLAY);
+        return Math.min(mLoader.getCurrentTaskList().size(), MAX_TASKS_TO_DISPLAY);
     }
 }
diff --git a/go/quickstep/src/com/android/quickstep/TaskListLoader.java b/go/quickstep/src/com/android/quickstep/TaskListLoader.java
new file mode 100644
index 0000000..9f359b4
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/TaskListLoader.java
@@ -0,0 +1,118 @@
+/*
+ * 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.
+ */
+
+package com.android.quickstep;
+
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+
+import com.android.systemui.shared.recents.model.Task;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+/**
+ * This class is responsible for maintaining the list of tasks and the task content. The list must
+ * be updated explicitly with {@link #loadTaskList} whenever the list needs to be
+ * up-to-date.
+ */
+public final class TaskListLoader {
+
+    private final RecentsModel mRecentsModel;
+
+    private ArrayList<Task> mTaskList = new ArrayList<>();
+    private int mTaskListChangeId;
+
+    public TaskListLoader(Context context) {
+        mRecentsModel = RecentsModel.INSTANCE.get(context);
+    }
+
+    /**
+     * Returns the current task list as of the last completed load (see
+     * {@link #loadTaskList}). This list of tasks is guaranteed to always have all its task
+     * content loaded.
+     *
+     * @return the current list of tasks w/ all content loaded
+     */
+    public ArrayList<Task> getCurrentTaskList() {
+        return mTaskList;
+    }
+
+    /**
+     * Fetches the most recent tasks and updates the task list asynchronously. In addition it
+     * loads the content for each task (icon and label). The callback and task list being updated
+     * only occur when all task content is fully loaded and up-to-date.
+     *
+     * @param onTasksLoadedCallback callback for when the tasks are fully loaded. Done on the UI
+     *                              thread
+     */
+    public void loadTaskList(@Nullable Consumer<ArrayList<Task>> onTasksLoadedCallback) {
+        if (mRecentsModel.isTaskListValid(mTaskListChangeId)) {
+            // Current task list is already up to date. No need to update.
+            if (onTasksLoadedCallback != null) {
+                onTasksLoadedCallback.accept(mTaskList);
+            }
+            return;
+        }
+        // TODO: Look into error checking / more robust handling for when things go wrong.
+        mTaskListChangeId = mRecentsModel.getTasks(tasks -> {
+            // Reverse tasks to put most recent at the bottom of the view
+            Collections.reverse(tasks);
+            // Load task content
+            loadTaskContents(tasks, () -> {
+                mTaskList = tasks;
+                if (onTasksLoadedCallback != null) {
+                    onTasksLoadedCallback.accept(mTaskList);
+                }
+            });
+        });
+    }
+
+    /**
+     * Loads task content for a list of tasks, including the label and the icon. Uses the list of
+     * tasks since the last load as a cache for loaded content.
+     *
+     * @param tasksToLoad list of tasks that need to load their content
+     * @param onLoadedCallback runnable to run after all tasks have loaded their content
+     */
+    private void loadTaskContents(ArrayList<Task> tasksToLoad,
+            @Nullable Runnable onLoadedCallback) {
+        AtomicInteger loadRequestsCount = new AtomicInteger(0);
+        for (Task task : tasksToLoad) {
+            int index = mTaskList.indexOf(task);
+            if (index >= 0) {
+                // If we've already loaded the task and have its content then just copy it over.
+                Task loadedTask = mTaskList.get(index);
+                task.titleDescription = loadedTask.titleDescription;
+                task.icon = loadedTask.icon;
+            } else {
+                // Otherwise, load the content in the background.
+                loadRequestsCount.getAndIncrement();
+                mRecentsModel.getIconCache().updateIconInBackground(task, loadedTask -> {
+                    if (loadRequestsCount.decrementAndGet() == 0 && onLoadedCallback != null) {
+                        onLoadedCallback.run();
+                    }
+                });
+            }
+        }
+        if (loadRequestsCount.get() == 0 && onLoadedCallback != null) {
+            onLoadedCallback.run();
+        }
+    }
+}
diff --git a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 2858deb..89a8454 100644
--- a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -52,6 +52,7 @@
             ISystemUiProxy iSystemUiProxy = ISystemUiProxy.Stub
                     .asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
             mRecentsModel.setSystemUiProxy(iSystemUiProxy);
+            mRecentsModel.onInitializeSystemUI(bundle);
         }
 
         @Override
@@ -77,6 +78,11 @@
             mOverviewCommandHelper.onTip(actionType, viewType);
         }
 
+        @Override
+        public void onAssistantAvailable(boolean available) {
+            // TODO handle assistant
+        }
+
         /** Deprecated methods **/
         public void onQuickStep(MotionEvent motionEvent) { }
 
diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
index 15da10c..afb0540 100644
--- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
+++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
@@ -27,11 +27,8 @@
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.R;
-import com.android.quickstep.RecentsModel;
 import com.android.quickstep.TaskAdapter;
-import com.android.systemui.shared.recents.model.Task;
-
-import java.util.ArrayList;
+import com.android.quickstep.TaskListLoader;
 
 /**
  * Root view for the icon recents view. Acts as the main interface to the rest of the Launcher code
@@ -77,13 +74,12 @@
      */
     @ViewDebug.ExportedProperty(category = "launcher")
 
-    // TODO: Write a recents task list observer that creates/updates tasks and signals task adapter.
-    private static final ArrayList<Task> DUMMY_TASK_LIST = new ArrayList<>();
     private final Context mContext;
 
     private float mTranslationYFactor;
     private TaskAdapter mTaskAdapter;
     private RecyclerView mTaskRecyclerView;
+    private TaskListLoader mTaskLoader;
 
     public IconRecentsView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -93,13 +89,30 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mTaskAdapter = new TaskAdapter(DUMMY_TASK_LIST);
+        mTaskLoader = new TaskListLoader(mContext);
+        mTaskAdapter = new TaskAdapter(mTaskLoader);
         mTaskRecyclerView = findViewById(R.id.recent_task_recycler_view);
         mTaskRecyclerView.setAdapter(mTaskAdapter);
         mTaskRecyclerView.setLayoutManager(
                 new LinearLayoutManager(mContext, VERTICAL, true /* reverseLayout */));
     }
 
+    /**
+     * Logic for when we know we are going to overview/recents and will be putting up the recents
+     * view. This should be used to prepare recents (e.g. load any task data, etc.) before it
+     * becomes visible.
+     *
+     * TODO: Hook this up for fallback recents activity as well
+     */
+    public void onBeginTransitionToOverview() {
+        // Load any task changes
+        mTaskLoader.loadTaskList(tasks -> {
+            // TODO: Put up some loading UI while task content is loading. May have to do something
+            // smarter when animating from app to overview.
+            mTaskAdapter.notifyDataSetChanged();
+        });
+    }
+
     public void setTranslationYFactor(float translationFactor) {
         mTranslationYFactor = translationFactor;
         setTranslationY(computeTranslationYForFactor(mTranslationYFactor));
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
index 76abe8d..96b0a9e 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
@@ -48,7 +48,7 @@
     private ShadowGenerator mShadowGenerator;
 
     private Drawable mWrapperIcon;
-    private int mWrapperBackgroundColor;
+    private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
 
     protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) {
         mContext = context.getApplicationContext();
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index 8c58e3e..4b64ad4 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/recents_ui_overrides/res/layout/hint.xml b/quickstep/recents_ui_overrides/res/layout/hint.xml
deleted file mode 100644
index 7e2d6af..0000000
--- a/quickstep/recents_ui_overrides/res/layout/hint.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<?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.
--->
-<com.android.quickstep.hints.HintView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="@dimen/chip_hint_height"
-    android:layout_gravity="center_horizontal|bottom"
-    android:paddingStart="@dimen/chip_hint_start_padding"
-    android:paddingEnd="@dimen/chip_hint_end_padding"
-    android:background="@drawable/chip_hint_background_light"
-    android:gravity="center"
-    android:layout_marginHorizontal="@dimen/chip_hint_horizontal_margin"
-    android:orientation="horizontal"
-    android:elevation="@dimen/chip_hint_elevation"
-    android:layoutDirection="ltr">
-
-    <ImageView
-        android:id="@+id/icon"
-        android:layout_width="@dimen/chip_icon_size"
-        android:layout_height="@dimen/chip_icon_size"
-        android:visibility="gone"
-        android:scaleType="fitCenter"
-        android:adjustViewBounds="true"
-        android:contentDescription="@null"/>
-
-    <TextView
-        android:id="@+id/label"
-        android:layout_width="wrap_content"
-        android:layout_height="@dimen/chip_text_height"
-        android:paddingTop="@dimen/chip_text_top_padding"
-        android:paddingStart="@dimen/chip_text_start_padding"
-        android:fontFamily="google-sans-medium"
-        android:textAlignment="textStart"
-        android:singleLine="true"
-        android:textColor="@color/chip_hint_foreground_color"
-        android:textSize="@dimen/chip_text_size"
-        android:ellipsize="none"
-        android:includeFontPadding="true"/>
-
-
-</com.android.quickstep.hints.HintView>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/layout/hint_container.xml b/quickstep/recents_ui_overrides/res/layout/hint_container.xml
index 336f63e..f8723fc 100644
--- a/quickstep/recents_ui_overrides/res/layout/hint_container.xml
+++ b/quickstep/recents_ui_overrides/res/layout/hint_container.xml
@@ -14,9 +14,10 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.quickstep.hints.HintsContainer
+<com.android.quickstep.hints.ChipsContainer
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
+    android:layout_height="@dimen/chip_hint_height"
+    android:layout_gravity="bottom"
     android:gravity="center_horizontal"
     android:orientation="horizontal"/>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
index 027fd91..81ff0c7 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
@@ -44,6 +44,7 @@
  */
 public abstract class RecentsUiFactory {
 
+    public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
     private static final AsyncCommand SET_SHELF_HEIGHT_CMD = (visible, height) ->
             WindowManagerWrapper.getInstance().setShelfHeight(visible != 0, height);
 
@@ -61,7 +62,6 @@
 
         if (swipeUpToHome) {
             list.add(new FlingAndHoldTouchController(launcher));
-            list.add(new OverviewToAllAppsTouchController(launcher));
         } else {
             if (launcher.getDeviceProfile().isVerticalBarLayout()) {
                 list.add(new OverviewToAllAppsTouchController(launcher));
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java
new file mode 100644
index 0000000..109a4c5
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java
@@ -0,0 +1,200 @@
+/*
+ * 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.
+ */
+
+package com.android.quickstep;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_POINTER_UP;
+import static android.view.MotionEvent.ACTION_UP;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.Display;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+import com.android.systemui.shared.recents.ISystemUiProxy;
+import com.android.systemui.shared.system.NavigationBarCompat;
+import com.android.systemui.shared.system.WindowManagerWrapper;
+import com.android.launcher3.R;
+
+/**
+ * Touch consumer for handling events to launch assistant from launcher
+ */
+public class AssistantTouchConsumer implements InputConsumer {
+    private static final String TAG = "AssistantTouchConsumer";
+
+    private final PointF mDownPos = new PointF();
+    private final PointF mLastPos = new PointF();
+    private int mActivePointerId = -1;
+
+    private final int mDisplayRotation;
+    private final Rect mStableInsets = new Rect();
+
+    private final float mDragSlop;
+    private final float mTouchSlop;
+    private final float mThreshold;
+
+    private float mStartDisplacement;
+    private boolean mPassedDragSlop;
+    private boolean mPassedTouchSlop;
+    private long mPassedTouchSlopTime;
+    private boolean mLaunchedAssistant;
+    private float mLastProgress;
+
+    private final ISystemUiProxy mSysUiProxy;
+
+    public AssistantTouchConsumer(Context context, ISystemUiProxy systemUiProxy) {
+        mSysUiProxy = systemUiProxy;
+
+        mDragSlop = NavigationBarCompat.getQuickStepDragSlopPx();
+        mTouchSlop = NavigationBarCompat.getQuickStepTouchSlopPx();
+        mThreshold = context.getResources().getDimension(R.dimen.gestures_assistant_threshold);
+
+        Display display = context.getSystemService(WindowManager.class).getDefaultDisplay();
+        mDisplayRotation = display.getRotation();
+        WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
+    }
+
+    @Override
+    public int getType() {
+        return TYPE_ASSISTANT;
+    }
+
+    @Override
+    public void onMotionEvent(MotionEvent ev) {
+        // TODO add logging
+        switch (ev.getActionMasked()) {
+            case ACTION_DOWN: {
+                mActivePointerId = ev.getPointerId(0);
+                mDownPos.set(ev.getX(), ev.getY());
+                mLastPos.set(mDownPos);
+                mLastProgress = -1;
+                break;
+            }
+            case ACTION_POINTER_UP: {
+                int ptrIdx = ev.getActionIndex();
+                int ptrId = ev.getPointerId(ptrIdx);
+                if (ptrId == mActivePointerId) {
+                    final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
+                    mDownPos.set(
+                        ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
+                        ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
+                    mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
+                    mActivePointerId = ev.getPointerId(newPointerIdx);
+                }
+                break;
+            }
+            case ACTION_MOVE: {
+                int pointerIndex = ev.findPointerIndex(mActivePointerId);
+                if (pointerIndex == -1) {
+                    break;
+                }
+                mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
+                float displacement = getDisplacement(ev);
+
+                if (!mPassedDragSlop) {
+                    // Normal gesture, ensure we pass the drag slop before we start tracking
+                    // the gesture
+                    if (Math.abs(displacement) > mDragSlop) {
+                        mPassedDragSlop = true;
+                        mStartDisplacement = displacement;
+                        mPassedTouchSlopTime = SystemClock.uptimeMillis();
+                    }
+                }
+
+                if (!mPassedTouchSlop) {
+                    if (Math.hypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y) >=
+                        mTouchSlop) {
+                        mPassedTouchSlop = true;
+                        if (!mPassedDragSlop) {
+                            mPassedDragSlop = true;
+                            mStartDisplacement = displacement;
+                            mPassedTouchSlopTime = SystemClock.uptimeMillis();
+                        }
+                    }
+                }
+
+                if (mPassedDragSlop) {
+                    // Move
+                    float distance = mStartDisplacement - displacement;
+                    if (distance >= 0) {
+                        onAssistantProgress(distance / mThreshold);
+                    }
+                }
+                break;
+            }
+            case ACTION_CANCEL:
+                break;
+            case ACTION_UP: {
+                if (ev.getEventTime() - mPassedTouchSlopTime < ViewConfiguration.getTapTimeout()) {
+                    onAssistantProgress(1);
+                }
+
+                break;
+            }
+        }
+    }
+
+    private void onAssistantProgress(float progress) {
+        if (mLastProgress == progress) {
+            return;
+        }
+        try {
+            mSysUiProxy.onAssistantProgress(Math.max(0, Math.min(1, progress)));
+            if (progress >= 1 && !mLaunchedAssistant) {
+                mSysUiProxy.startAssistant(new Bundle());
+                mLaunchedAssistant = true;
+            }
+            mLastProgress = progress;
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to notify SysUI to start/send assistant progress: " + progress, e);
+        }
+    }
+
+    private boolean isNavBarOnRight() {
+        return mDisplayRotation == Surface.ROTATION_90 && mStableInsets.right > 0;
+    }
+
+    private boolean isNavBarOnLeft() {
+        return mDisplayRotation == Surface.ROTATION_270 && mStableInsets.left > 0;
+    }
+
+    private float getDisplacement(MotionEvent ev) {
+        float eventX = ev.getX();
+        float eventY = ev.getY();
+        float displacement = eventY - mDownPos.y;
+        if (isNavBarOnRight()) {
+            displacement = eventX - mDownPos.x;
+        } else if (isNavBarOnLeft()) {
+            displacement = mDownPos.x - eventX;
+        }
+        return displacement;
+    }
+
+    static boolean withinTouchRegion(Context context, float x) {
+        return x > context.getResources().getDisplayMetrics().widthPixels
+                - context.getResources().getDimension(R.dimen.gestures_assistant_width);
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java
index 047f34c..d919a3e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java
@@ -31,6 +31,11 @@
     }
 
     @Override
+    public int getType() {
+        return TYPE_DEVICE_LOCKED;
+    }
+
+    @Override
     public void onMotionEvent(MotionEvent ev) {
         // For now, just start the home intent so user is prompted to unlock the device.
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/InputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/InputConsumer.java
index 8dfb9ab..ad9fe78 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/InputConsumer.java
@@ -23,7 +23,16 @@
 
 @TargetApi(Build.VERSION_CODES.O)
 public interface InputConsumer {
-    InputConsumer NO_OP = new InputConsumer() { };
+
+    int TYPE_NO_OP = 0;
+    int TYPE_OVERVIEW = 1;
+    int TYPE_OTHER_ACTIVITY = 2;
+    int TYPE_ASSISTANT = 3;
+    int TYPE_DEVICE_LOCKED = 4;
+
+    InputConsumer NO_OP = () -> TYPE_NO_OP;
+
+    int getType();
 
     default boolean isActive() {
         return false;
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 ffd3b4b..971987a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -29,11 +29,11 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
-import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
+import android.os.UserHandle;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.animation.Interpolator;
@@ -100,17 +100,20 @@
     public HomeAnimationFactory prepareHomeUI(Launcher activity) {
         final DeviceProfile dp = activity.getDeviceProfile();
         final RecentsView recentsView = activity.getOverviewPanel();
-        final ComponentName component = recentsView.getRunningTaskView().getTask().key
-                .sourceComponent;
-
-        final View workspaceView = activity.getWorkspace().getFirstMatchForAppClose(component);
-        final FloatingIconView floatingView = workspaceView == null ? null
-                : new FloatingIconView(activity);
-        final Rect iconLocation = new Rect();
-        if (floatingView != null) {
-            floatingView.matchPositionOf(activity, workspaceView, true /* hideOriginal */,
-                    iconLocation);
+        final TaskView runningTaskView = recentsView.getRunningTaskView();
+        final View workspaceView;
+        if (runningTaskView != null && runningTaskView.getTask().key.getComponent() != null) {
+            workspaceView = activity.getWorkspace().getFirstMatchForAppClose(
+                    runningTaskView.getTask().key.getComponent().getPackageName(),
+                    UserHandle.of(runningTaskView.getTask().key.userId));
+        } else {
+            workspaceView = null;
         }
+        final Rect iconLocation = new Rect();
+        final FloatingIconView floatingView = workspaceView == null ? null
+                : FloatingIconView.getFloatingIconView(activity, workspaceView,
+                true /* hideOriginal */, false /* useDrawableAsIs */,
+                activity.getDeviceProfile().getAspectRatioWithInsets(), iconLocation, null);
 
         return new HomeAnimationFactory() {
             @Nullable
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 67bfeaa..e3afb92 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
@@ -148,6 +148,11 @@
     }
 
     @Override
+    public int getType() {
+        return TYPE_OTHER_ACTIVITY;
+    }
+
+    @Override
     public void onMotionEvent(MotionEvent ev) {
         if (mVelocityTracker == null) {
             return;
@@ -225,7 +230,7 @@
                             mTouchSlop) {
                         mPassedTouchSlop = true;
 
-                        TOUCH_INTERACTION_LOG.startQuickStep();
+                        TOUCH_INTERACTION_LOG.addLog("startQuickstep");
                         if (mIsDeferredDownTarget) {
                             // Deferred gesture, start the animation and gesture tracking once
                             // we pass the actual touch slop
@@ -288,7 +293,7 @@
     }
 
     private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
-        TOUCH_INTERACTION_LOG.startRecentsAnimation();
+        TOUCH_INTERACTION_LOG.addLog("startRecentsAnimation");
 
         RecentsAnimationListenerSet listenerSet = mSwipeSharedState.getActiveListener();
         final WindowTransformSwipeHandler handler = new WindowTransformSwipeHandler(
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java
index 28c4db4..cce5cb3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java
@@ -60,6 +60,11 @@
     }
 
     @Override
+    public int getType() {
+        return TYPE_OVERVIEW;
+    }
+
+    @Override
     public void onMotionEvent(MotionEvent ev) {
         if (mInvalidated) {
             return;
@@ -120,7 +125,7 @@
             OverviewCallbacks.get(mActivity).closeAllWindows();
             ActivityManagerWrapper.getInstance()
                     .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-            TOUCH_INTERACTION_LOG.startQuickStep();
+            TOUCH_INTERACTION_LOG.addLog("startQuickstep");
         }
 
         mTrackingStarted = true;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionLog.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionLog.java
deleted file mode 100644
index 4b660d4..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionLog.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import android.view.MotionEvent;
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.LinkedList;
-
-/**
- * Keeps track of debugging logs for a particular quickstep gesture.
- */
-public class TouchInteractionLog {
-
-    // The number of gestures to log
-    private static final int MAX_NUM_LOG_GESTURES = 5;
-
-    private final Calendar mCalendar = Calendar.getInstance();
-    private final SimpleDateFormat mDateFormat = new SimpleDateFormat("MMM dd - kk:mm:ss:SSS");
-    private final LinkedList<ArrayList<String>> mGestureLogs = new LinkedList<>();
-
-    public void prepareForNewGesture() {
-        mGestureLogs.add(new ArrayList<>());
-        while (mGestureLogs.size() > MAX_NUM_LOG_GESTURES) {
-            mGestureLogs.pop();
-        }
-        getCurrentLog().add("[" + mDateFormat.format(mCalendar.getTime()) + "]");
-    }
-
-    public void setInputConsumer(InputConsumer consumer) {
-        getCurrentLog().add("tc=" + consumer.getClass().getSimpleName());
-    }
-
-    public void addMotionEvent(MotionEvent event) {
-        getCurrentLog().add("ev=" + event.getActionMasked());
-    }
-
-    public void startQuickStep() {
-        getCurrentLog().add("qstStart");
-    }
-
-    public void startRecentsAnimation() {
-        getCurrentLog().add("raStart");
-    }
-
-    public void startRecentsAnimationCallback(int numTargets) {
-        getCurrentLog().add("raStartCb=" + numTargets);
-    }
-
-    public void cancelRecentsAnimation() {
-        getCurrentLog().add("raCancel");
-    }
-
-    public void finishRecentsAnimation(boolean toHome) {
-        getCurrentLog().add("raFinish=" + toHome);
-    }
-
-    public void dump(PrintWriter pw) {
-        pw.println("TouchInteractionLog {");
-        for (ArrayList<String> gesture : mGestureLogs) {
-            pw.print("    ");
-            for (String log : gesture) {
-                pw.print(log + " ");
-            }
-            pw.println();
-        }
-        pw.println("}");
-    }
-
-    private ArrayList<String> getCurrentLog() {
-        return mGestureLogs.getLast();
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index cf50fc1..6d608ee 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -44,6 +44,8 @@
 import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.logging.EventLogArray;
 import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.systemui.shared.recents.IOverviewProxy;
@@ -69,7 +71,8 @@
     public static final LooperExecutor BACKGROUND_EXECUTOR =
             new LooperExecutor(UiThreadHelper.getBackgroundLooper());
 
-    public static final TouchInteractionLog TOUCH_INTERACTION_LOG = new TouchInteractionLog();
+    public static final EventLogArray TOUCH_INTERACTION_LOG =
+            new EventLogArray("touch_interaction_log", 40);
 
     public static final int EDGE_NAV_BAR = 1 << 8;
 
@@ -86,6 +89,7 @@
                     .asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
             runWhenUserUnlocked(() -> {
                 mRecentsModel.setSystemUiProxy(mISystemUiProxy);
+                mRecentsModel.onInitializeSystemUI(bundle);
                 mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy);
             });
 
@@ -118,6 +122,11 @@
             mOverviewCommandHelper.onTip(actionType, viewType);
         }
 
+        @Override
+        public void onAssistantAvailable(boolean available) {
+            mAssistantAvailable = available;
+        }
+
         /** Deprecated methods **/
         public void onQuickStep(MotionEvent motionEvent) { }
 
@@ -171,6 +180,7 @@
     private TaskOverlayFactory mTaskOverlayFactory;
     private InputConsumerController mInputConsumer;
     private SwipeSharedState mSwipeSharedState;
+    private boolean mAssistantAvailable;
 
     private boolean mIsUserUnlocked;
     private List<Runnable> mOnUserUnlockedCallbacks;
@@ -280,15 +290,13 @@
             return;
         }
         MotionEvent event = (MotionEvent) ev;
+        TOUCH_INTERACTION_LOG.addLog("onMotionEvent", event.getActionMasked());
         if (event.getAction() == ACTION_DOWN) {
-            TOUCH_INTERACTION_LOG.prepareForNewGesture();
             boolean useSharedState = mConsumer.isActive();
             mConsumer.onConsumerAboutToBeSwitched();
             mConsumer = newConsumer(useSharedState, event);
-            TOUCH_INTERACTION_LOG.setInputConsumer(mConsumer);
+            TOUCH_INTERACTION_LOG.addLog("setInputConsumer", mConsumer.getType());
         }
-        TOUCH_INTERACTION_LOG.addMotionEvent(event);
-
         mConsumer.onMotionEvent(event);
     }
 
@@ -307,6 +315,10 @@
 
         if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher) {
             return InputConsumer.NO_OP;
+        } else if (mAssistantAvailable && mOverviewInteractionState.isSwipeUpGestureEnabled()
+                && FeatureFlags.ENABLE_ASSISTANT_GESTURE.get()
+                && AssistantTouchConsumer.withinTouchRegion(this, event.getX())) {
+            return new AssistantTouchConsumer(this, mRecentsModel.getSystemUiProxy());
         } else if (mSwipeSharedState.goingToLauncher ||
                 mOverviewComponentObserver.getActivityControlHelper().isResumed()) {
             return OverviewInputConsumer.newInstance(
@@ -337,6 +349,6 @@
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        TOUCH_INTERACTION_LOG.dump(pw);
+        TOUCH_INTERACTION_LOG.dump("", pw);
     }
 }
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 4d0136e..7dc58a5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -671,7 +671,7 @@
         initTransitionEndpoints(dp);
 
         mRecentsAnimationWrapper.setController(targetSet);
-        TOUCH_INTERACTION_LOG.startRecentsAnimationCallback(targetSet.apps.length);
+        TOUCH_INTERACTION_LOG.addLog("startRecentsAnimationCallback", targetSet.apps.length);
         setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
 
         mPassedOverviewThreshold = false;
@@ -682,7 +682,7 @@
         mRecentsAnimationWrapper.setController(null);
         mActivityInitListener.unregister();
         setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
-        TOUCH_INTERACTION_LOG.cancelRecentsAnimation();
+        TOUCH_INTERACTION_LOG.addLog("cancelRecentsAnimation");
     }
 
     @UiThread
@@ -972,27 +972,36 @@
         final RectF currentRect = new RectF();
 
         final View floatingView = homeAnimationFactory.getFloatingView();
+        final boolean isFloatingIconView = floatingView instanceof FloatingIconView;
+
         ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
-        if (floatingView instanceof FloatingIconView) {
+        if (isFloatingIconView) {
             anim.addListener((FloatingIconView) floatingView);
         }
+
+        // We want the window alpha to be 0 once this threshold is met, so that the
+        // FolderIconView can be seen morphing into the icon shape.
+        final float windowAlphaThreshold = isFloatingIconView ? 0.75f : 1f;
         anim.addUpdateListener(animation -> {
             float progress = animation.getAnimatedFraction();
-            float interpolatedProgress = Interpolators.ACCEL_2.getInterpolation(progress);
+            float interpolatedProgress = Interpolators.ACCEL_1_5.getInterpolation(progress);
             // Initially go towards original target (task view in recents),
             // but accelerate towards the final target.
             // TODO: This is technically not correct. Instead, motion should continue at
             // the released velocity but accelerate towards the target.
             targetRect.set(rectFEvaluator.evaluate(interpolatedProgress,
                     originalTarget, finalTarget));
-            currentRect.set(rectFEvaluator.evaluate(progress, startRect, targetRect));
-            float alpha = 1 - interpolatedProgress;
-            mTransformParams.setCurrentRectAndTargetAlpha(currentRect, alpha)
+            currentRect.set(rectFEvaluator.evaluate(interpolatedProgress, startRect, targetRect));
+
+            float iconAlpha = Utilities.mapToRange(interpolatedProgress, 0,
+                    windowAlphaThreshold, 0f, 1f, Interpolators.LINEAR);
+            mTransformParams.setCurrentRectAndTargetAlpha(currentRect, 1f - iconAlpha)
                     .setSyncTransactionApplier(mSyncTransactionApplier);
             mClipAnimationHelper.applyTransform(targetSet, mTransformParams);
 
-            if (floatingView instanceof FloatingIconView) {
-                ((FloatingIconView) floatingView).update(currentRect, 1f - alpha);
+            if (isFloatingIconView) {
+                ((FloatingIconView) floatingView).update(currentRect, iconAlpha, progress,
+                        windowAlphaThreshold);
             }
         });
         anim.addListener(new AnimationSuccessListener() {
@@ -1016,7 +1025,7 @@
     @UiThread
     private void resumeLastTask() {
         mRecentsAnimationWrapper.finish(false /* toRecents */, null);
-        TOUCH_INTERACTION_LOG.finishRecentsAnimation(false);
+        TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", false);
     }
 
     @UiThread
@@ -1034,7 +1043,7 @@
                         mMainThreadHandler);
             });
         }
-        TOUCH_INTERACTION_LOG.finishRecentsAnimation(false);
+        TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", false);
         doLogGesture(NEW_TASK);
     }
 
@@ -1143,7 +1152,7 @@
                         () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
             }
         }
-        TOUCH_INTERACTION_LOG.finishRecentsAnimation(true);
+        TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
     }
 
     private void finishCurrentTransitionToHome() {
@@ -1151,7 +1160,7 @@
             mRecentsAnimationWrapper.finish(true /* toRecents */,
                     () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
         }
-        TOUCH_INTERACTION_LOG.finishRecentsAnimation(true);
+        TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
         doLogGesture(HOME);
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/hints/ChipsContainer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/hints/ChipsContainer.java
new file mode 100644
index 0000000..8fc89f2
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/hints/ChipsContainer.java
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+package com.android.quickstep.hints;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.view.View;
+import android.widget.FrameLayout;
+
+public class ChipsContainer extends FrameLayout {
+
+    private static final String TAG = "ChipsContainer";
+
+    public static final FloatProperty<ChipsContainer> HINT_VISIBILITY =
+            new FloatProperty<ChipsContainer>("hint_visibility") {
+                @Override
+                public void setValue(ChipsContainer chipsContainer, float v) {
+                    chipsContainer.setHintVisibility(v);
+                }
+
+                @Override
+                public Float get(ChipsContainer chipsContainer) {
+                    return chipsContainer.mHintVisibility;
+                }
+            };
+
+    private float mHintVisibility;
+
+    public ChipsContainer(Context context) {
+        super(context);
+    }
+
+    public ChipsContainer(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public ChipsContainer(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public ChipsContainer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    public void setView(View v) {
+        removeAllViews();
+        addView(v);
+    }
+
+    public void setHintVisibility(float v) {
+        if (v == 1) {
+            setVisibility(VISIBLE);
+        } else {
+            setVisibility(GONE);
+        }
+        mHintVisibility = v;
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/hints/HintsContainer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/hints/HintsContainer.java
deleted file mode 100644
index 22b1217..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/hints/HintsContainer.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * 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.
- */
-package com.android.quickstep.hints;
-
-import static com.android.quickstep.hints.UiHintListenerConstants.HINTS_KEY;
-import static com.android.quickstep.hints.UiHintListenerConstants.ON_HINTS_RETURNED_CODE;
-import static com.android.quickstep.hints.UiInterfaceConstants.REQUEST_HINTS_CODE;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
-import android.util.AttributeSet;
-import android.util.FloatProperty;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.widget.LinearLayout;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.R;
-
-import java.util.ArrayList;
-
-public class HintsContainer extends LinearLayout {
-
-    private static final String TAG = "HintsView";
-
-    public static final FloatProperty<HintsContainer> HINT_VISIBILITY =
-            new FloatProperty<HintsContainer>("hint_visibility") {
-                @Override
-                public void setValue(HintsContainer hintsContainer, float v) {
-                    hintsContainer.setHintVisibility(v);
-                }
-
-                @Override
-                public Float get(HintsContainer hintsContainer) {
-                    return hintsContainer.mHintVisibility;
-                }
-            };
-
-    private static Intent mServiceIntent =
-            new Intent("com.android.systemui.action.UI_PULL_INTERFACE")
-                    .setClassName(
-                            "com.android.systemui.navbarhint",
-                            "com.android.systemui.navbarhint.service.HintService");
-
-    @Nullable
-    private Messenger mHintServiceInterface;
-    private UiHintListener mUiHintListener;
-    private boolean mBound = false;
-    private float mHintVisibility;
-
-    private final ServiceConnection mServiceConnection = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
-            mHintServiceInterface = new Messenger(iBinder);
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName componentName) {
-            mHintServiceInterface = null;
-            attemptBinding();
-        }
-
-        @Override
-        public void onBindingDied(ComponentName componentName) {
-            mHintServiceInterface = null;
-            attemptBinding();
-        }
-    };
-
-    public HintsContainer(Context context) {
-        super(context);
-    }
-
-    public HintsContainer(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public HintsContainer(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    public HintsContainer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        if (mUiHintListener == null) {
-            mUiHintListener = new UiHintListener(this);
-        }
-        if (!mBound) {
-            attemptBinding();
-        }
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        if (mBound) {
-            getContext().unbindService(mServiceConnection);
-            mBound = false;
-        }
-        super.onDetachedFromWindow();
-    }
-
-    public void setHintVisibility(float v) {
-        if (v == 1) {
-            getHints();
-            setVisibility(VISIBLE);
-        } else {
-            setVisibility(GONE);
-        }
-        mHintVisibility = v;
-    }
-
-    private void attemptBinding() {
-        if (mBound) {
-            getContext().unbindService(mServiceConnection);
-            mBound = false;
-        }
-        boolean success = getContext().bindService(mServiceIntent,
-                mServiceConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
-        if (success) {
-            mBound = true;
-        } else {
-            Log.w(TAG, "Binding to hint supplier failed");
-        }
-    }
-    
-    private void sendOnHintTap(Bundle hint) {
-        if (mHintServiceInterface != null) {
-            Message msg = Message.obtain(null, UiInterfaceConstants.ON_HINT_TAP_CODE);
-            Bundle data = new Bundle();
-            data.putString(UiInterfaceConstants.HINT_ID_KEY, HintUtil.getId(hint));
-            data.putInt(UiInterfaceConstants.WIDTH_PX_KEY, getWidth());
-            data.putInt(UiInterfaceConstants.HEIGHT_PX_KEY, getHeight());
-            data.putInt(UiInterfaceConstants.HINT_SPACE_WIDTH_PX_KEY, 0);
-            data.putInt(UiInterfaceConstants.HINT_SPACE_HEIGHT_PX_KEY, 0);
-            msg.setData(data);
-            try {
-                mHintServiceInterface.send(msg);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Failed to send hint tap", e);
-            }
-        }
-    }
-
-    private void getHints() {
-        if (mHintServiceInterface != null) {
-            try {
-                Message m = Message.obtain(null, REQUEST_HINTS_CODE);
-                m.replyTo = new Messenger(mUiHintListener);
-                mHintServiceInterface.send(m);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Failed to send message", e);
-            }
-        }
-    }
-
-    private static class UiHintListener extends Handler {
-        private HintsContainer mView;
-
-        UiHintListener(HintsContainer v) {
-            mView = v;
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case ON_HINTS_RETURNED_CODE:
-                    handleHints(msg);
-                    break;
-                default:
-                    Log.e(TAG, "UiPullInterface got unrecognized code: " + msg.what);
-                    break;
-            }
-        }
-
-        private void handleHints(Message msg) {
-            Bundle bundle = msg.getData();
-            ArrayList<Bundle> hints = bundle.getParcelableArrayList(HINTS_KEY);
-
-            if (hints != null) {
-                mView.removeAllViews();
-
-                for (Bundle hint : hints) {
-                    HintView h = (HintView) LayoutInflater.from(mView.getContext()).inflate(
-                            R.layout.hint, mView, false);
-                    h.setHint(hint);
-                    h.setOnClickListener((v) -> mView.sendOnHintTap(hint));
-                    mView.addView(h);
-                }
-            }
-        }
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 5fe92d5..0aa1beb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -50,6 +50,7 @@
 
 public final class DigitalWellBeingToast extends LinearLayout {
     static final Intent OPEN_APP_USAGE_SETTINGS_TEMPLATE = new Intent(ACTION_APP_USAGE_SETTINGS);
+    static final int MINUTE_MS = 60000;
 
     public interface InitializeCallback {
         void call(float saturation, String contentDescription);
@@ -185,7 +186,11 @@
                 /* forceFormatWidth= */ false);
     }
 
-    private String getShorterReadableDuration(Duration duration) {
+    private String getRoundedUpToMinuteReadableDuration(long remainingTime) {
+        final Duration duration = Duration.ofMillis(
+                remainingTime > MINUTE_MS ?
+                        (remainingTime + MINUTE_MS - 1) / MINUTE_MS * MINUTE_MS :
+                        remainingTime);
         return getReadableDuration(
                 duration, FormatWidth.NARROW, R.string.shorter_duration_less_than_one_minute);
     }
@@ -196,7 +201,7 @@
                 resources.getString(R.string.app_in_grayscale) :
                 resources.getString(
                         R.string.time_left_for_app,
-                        getShorterReadableDuration(Duration.ofMillis(remainingTime)));
+                        getRoundedUpToMinuteReadableDuration(remainingTime));
     }
 
     public void openAppUsageSettings() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index 88fe2ee..97bce5e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -42,9 +42,10 @@
 import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.util.PendingAnimation;
+import com.android.launcher3.views.BaseDragLayer;
 import com.android.launcher3.views.ScrimView;
 import com.android.quickstep.OverviewInteractionState;
-import com.android.quickstep.hints.HintsContainer;
+import com.android.quickstep.hints.ChipsContainer;
 import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
 import com.android.quickstep.util.LayoutUtils;
@@ -77,7 +78,7 @@
     private float mTranslationYFactor;
 
     private final TransformParams mTransformParams = new TransformParams();
-    private HintsContainer mHintsContainer;
+    private ChipsContainer mChipsContainer;
 
     public LauncherRecentsView(Context context) {
         this(context, null);
@@ -111,8 +112,9 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        mHintsContainer = mActivity.findViewById(R.id.hints);
-        mHintsContainer.setPadding(0, 0, 0, mActivity.getDeviceProfile().chipHintBottomMarginPx);
+        mChipsContainer = mActivity.findViewById(R.id.hints);
+        BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) mChipsContainer.getLayoutParams();
+        params.bottomMargin = mActivity.getDeviceProfile().chipHintBottomMarginPx;
     }
 
     public void setTranslationYFactor(float translationFactor) {
@@ -131,11 +133,15 @@
     }
 
     public void setHintVisibility(float v) {
-        if (mHintsContainer != null && ENABLE_HINTS_IN_OVERVIEW.get()) {
-            mHintsContainer.setHintVisibility(v);
+        if (mChipsContainer != null && ENABLE_HINTS_IN_OVERVIEW.get()) {
+            mChipsContainer.setHintVisibility(v);
         }
     }
 
+    public ChipsContainer getChipsContainer() {
+        return mChipsContainer;
+    }
+
     @Override
     public void draw(Canvas canvas) {
         maybeDrawEmptyMessage(canvas);
@@ -192,7 +198,7 @@
 
         if (ENABLE_HINTS_IN_OVERVIEW.get()) {
             anim.anim.play(ObjectAnimator.ofFloat(
-                    mHintsContainer, HintsContainer.HINT_VISIBILITY, 0));
+                    mChipsContainer, ChipsContainer.HINT_VISIBILITY, 0));
         }
 
         return anim;
@@ -206,10 +212,10 @@
 
         if (ENABLE_HINTS_IN_OVERVIEW.get()) {
             anim.anim.play(ObjectAnimator.ofFloat(
-                    mHintsContainer, HintsContainer.HINT_VISIBILITY, 0));
+                    mChipsContainer, ChipsContainer.HINT_VISIBILITY, 0));
             anim.addEndListener(onEndListener -> {
                 if (!onEndListener.isSuccess) {
-                    mHintsContainer.setHintVisibility(1);
+                    mChipsContainer.setHintVisibility(1);
                 }
             });
         }
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 a7bf2c3..3e0e8ae 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
@@ -773,7 +773,7 @@
         setCurrentTask(runningTaskId);
     }
 
-    public TaskView getRunningTaskView() {
+    public @Nullable TaskView getRunningTaskView() {
         return getTaskView(mRunningTaskId);
     }
 
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 fb58c24..419a666 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
@@ -19,6 +19,7 @@
 import static android.widget.Toast.LENGTH_SHORT;
 
 import static com.android.launcher3.BaseActivity.fromContext;
+import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
@@ -91,7 +92,6 @@
 
     public static final long SCALE_ICON_DURATION = 120;
     private static final long DIM_ANIM_DURATION = 700;
-    private static final long TASK_LAUNCH_ANIM_DURATION = 200;
 
     public static final Property<TaskView, Float> ZOOM_SCALE =
             new FloatProperty<TaskView>("zoomScale") {
@@ -237,10 +237,10 @@
 
     public AnimatorPlaybackController createLaunchAnimationForRunningTask() {
         final PendingAnimation pendingAnimation =
-                getRecentsView().createTaskLauncherAnimation(this, TASK_LAUNCH_ANIM_DURATION);
-        pendingAnimation.anim.setInterpolator(Interpolators.ZOOM_IN);
+                getRecentsView().createTaskLauncherAnimation(this, RECENTS_LAUNCH_DURATION);
+        pendingAnimation.anim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
         AnimatorPlaybackController currentAnimation = AnimatorPlaybackController
-                .wrap(pendingAnimation.anim, TASK_LAUNCH_ANIM_DURATION, null);
+                .wrap(pendingAnimation.anim, RECENTS_LAUNCH_DURATION, null);
         currentAnimation.setEndAction(() -> {
             pendingAnimation.finish(true, Touch.SWIPE);
             launchTask(false);
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 2626481..f5e5dd3 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -39,7 +39,6 @@
     <dimen name="motion_pause_detector_speed_somewhat_fast">0.285dp</dimen>
     <dimen name="motion_pause_detector_speed_fast">0.5dp</dimen>
     <dimen name="motion_pause_detector_min_displacement">48dp</dimen>
-    <dimen name="motion_pause_detector_max_orthogonal_displacement">48dp</dimen>
 
     <!-- Launcher app transition -->
     <dimen name="content_trans_y">50dp</dimen>
@@ -65,4 +64,8 @@
     <dimen name="shelf_surface_radius">16dp</dimen>
     <!-- same as vertical_drag_handle_size -->
     <dimen name="shelf_surface_offset">24dp</dimen>
+
+    <!-- Assistant Gestures -->
+    <dimen name="gestures_assistant_width">70dp</dimen>
+    <dimen name="gestures_assistant_threshold">200dp</dimen>
 </resources>
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index ab5f479..f8b167b 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -403,9 +403,7 @@
     private void playIconAnimators(AnimatorSet appOpenAnimator, View v, Rect windowTargetBounds,
             boolean toggleVisibility) {
         final boolean isBubbleTextView = v instanceof BubbleTextView;
-        if (mFloatingView == null) {
-            mFloatingView = new FloatingIconView(mLauncher);
-        } else {
+        if (mFloatingView != null) {
             mFloatingView.setTranslationX(0);
             mFloatingView.setTranslationY(0);
             mFloatingView.setScaleX(1);
@@ -414,7 +412,8 @@
             mFloatingView.setBackground(null);
         }
         Rect rect = new Rect();
-        mFloatingView.matchPositionOf(mLauncher, v, toggleVisibility, rect);
+        mFloatingView = FloatingIconView.getFloatingIconView(mLauncher, v, toggleVisibility,
+                true /* useDrawableAsIs */, -1 /* aspectRatio */, rect, mFloatingView);
 
         int viewLocationStart = mIsRtl ? windowTargetBounds.width() - rect.right : rect.left;
         LayoutParams lp = (LayoutParams) mFloatingView.getLayoutParams();
@@ -615,11 +614,6 @@
                     WindowManagerWrapper.ACTIVITY_TYPE_STANDARD,
                     new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(false /* fromUnlock */),
                             CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
-
-            definition.addRemoteAnimation(
-                    WindowManagerWrapper.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
-                    new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(true /* fromUnlock */),
-                            CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
             new ActivityCompat(mLauncher).registerRemoteAnimations(definition);
         }
     }
diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
index 411e593..a0ab301 100644
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
@@ -175,7 +175,7 @@
         }
     }
 
-    public void notifySwipeUpSettingChanged(boolean swipeUpEnabled) {
+    private void notifySwipeUpSettingChanged(boolean swipeUpEnabled) {
         mUiHandler.removeMessages(MSG_SET_SWIPE_UP_ENABLED);
         mUiHandler.obtainMessage(MSG_SET_SWIPE_UP_ENABLED, swipeUpEnabled ? 1 : 0, 0).
                 sendToTarget();
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 81a22a1..56bc8570 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -16,12 +16,15 @@
 package com.android.quickstep;
 
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS;
 
 import android.annotation.TargetApi;
 import android.app.ActivityManager;
 import android.content.ComponentCallbacks2;
 import android.content.Context;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.HandlerThread;
 import android.os.Process;
 import android.os.RemoteException;
@@ -59,8 +62,8 @@
     private final TaskIconCache mIconCache;
     private final TaskThumbnailCache mThumbnailCache;
 
-    private float mWindowCornerRadius = -1;
-    private Boolean mSupportsRoundedCornersOnWindows;
+    private float mWindowCornerRadius = 0;
+    private boolean mSupportsRoundedCornersOnWindows;
 
     private RecentsModel(Context context) {
         mContext = context;
@@ -73,6 +76,12 @@
         ActivityManagerWrapper.getInstance().registerTaskStackListener(this);
     }
 
+    public void onInitializeSystemUI(Bundle params) {
+        mWindowCornerRadius = params.getFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, 0);
+        mSupportsRoundedCornersOnWindows =
+                params.getBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, false);
+    }
+
     public TaskIconCache getIconCache() {
         return mIconCache;
     }
@@ -182,42 +191,10 @@
     }
 
     public float getWindowCornerRadius() {
-        // The window corner radius is expressed in pixels and won't change if the
-        // display density changes. It's safe to cache the value.
-        if (mWindowCornerRadius == -1) {
-            if (mSystemUiProxy != null) {
-                try {
-                    mWindowCornerRadius = mSystemUiProxy.getWindowCornerRadius();
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Connection to ISystemUIProxy was lost, ignoring window corner "
-                            + "radius");
-                    return 0;
-                }
-            } else {
-                Log.w(TAG, "ISystemUIProxy is null, ignoring window corner radius");
-                return 0;
-            }
-        }
         return mWindowCornerRadius;
     }
 
     public boolean supportsRoundedCornersOnWindows() {
-        if (mSupportsRoundedCornersOnWindows == null) {
-            if (mSystemUiProxy != null) {
-                try {
-                    mSupportsRoundedCornersOnWindows =
-                            mSystemUiProxy.supportsRoundedCornersOnWindows();
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Connection to ISystemUIProxy was lost, ignoring window corner "
-                            + "radius");
-                    return false;
-                }
-            } else {
-                Log.w(TAG, "ISystemUIProxy is null, ignoring window corner radius");
-                return false;
-            }
-        }
-
         return mSupportsRoundedCornersOnWindows;
     }
 
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index 612c00e..07af9b3 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -15,6 +15,8 @@
  */
 package com.android.quickstep;
 
+import static com.android.launcher3.uioverrides.RecentsUiFactory.GO_LOW_RAM_RECENTS_ENABLED;
+
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
@@ -24,14 +26,17 @@
 import android.os.Looper;
 import android.util.LruCache;
 import android.view.accessibility.AccessibilityManager;
+
 import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.icons.cache.HandlerRunnable;
+import com.android.launcher3.uioverrides.RecentsUiFactory;
 import com.android.launcher3.util.Preconditions;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.TaskKeyLruCache;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+
 import java.util.function.Consumer;
 
 /**
@@ -125,8 +130,9 @@
             return label;
         }
 
-        // Skip loading content descriptions if accessibility is not enabled
-        if (!mAccessibilityManager.isEnabled()) {
+        // Skip loading content descriptions if accessibility is disabled unless low RAM recents
+        // is enabled.
+        if (!GO_LOW_RAM_RECENTS_ENABLED && !mAccessibilityManager.isEnabled()) {
             return "";
         }
 
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index 21d8144..8a117c8 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -40,7 +40,6 @@
     private final float mSpeedSomewhatFast;
     private final float mSpeedFast;
     private final float mMinDisplacementForPause;
-    private final float mMaxOrthogonalDisplacementForPause;
     private final Alarm mForcePauseTimeout;
 
     private Long mPreviousTime = null;
@@ -62,8 +61,6 @@
         mSpeedSomewhatFast = res.getDimension(R.dimen.motion_pause_detector_speed_somewhat_fast);
         mSpeedFast = res.getDimension(R.dimen.motion_pause_detector_speed_fast);
         mMinDisplacementForPause = res.getDimension(R.dimen.motion_pause_detector_min_displacement);
-        mMaxOrthogonalDisplacementForPause = res.getDimension(
-                R.dimen.motion_pause_detector_max_orthogonal_displacement);
         mForcePauseTimeout = new Alarm();
         mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */));
     }
@@ -77,7 +74,6 @@
         if (mOnMotionPauseListener != null) {
             mOnMotionPauseListener.onMotionPauseChanged(mIsPaused);
         }
-        mForcePauseTimeout.setAlarm(FORCE_PAUSE_TIMEOUT);
     }
 
     /**
@@ -94,6 +90,7 @@
         if (mFirstOrthogonalPosition == null) {
             mFirstOrthogonalPosition = orthogonalPosition;
         }
+        mForcePauseTimeout.setAlarm(FORCE_PAUSE_TIMEOUT);
         long time = SystemClock.uptimeMillis();
         if (mPreviousTime != null && mPreviousPosition != null) {
             long changeInTime = Math.max(1, time - mPreviousTime);
@@ -108,7 +105,6 @@
         }
         mPreviousTime = time;
         mPreviousPosition = position;
-        mForcePauseTimeout.setAlarm(FORCE_PAUSE_TIMEOUT);
     }
 
     private void checkMotionPaused(float velocity, float prevVelocity,
@@ -135,9 +131,11 @@
             }
         }
         boolean passedMinDisplacement = totalDisplacement.primary >= mMinDisplacementForPause;
-        boolean passedMaxOrthogonalDisplacement =
-                totalDisplacement.orthogonal >= mMaxOrthogonalDisplacementForPause;
-        isPaused &= passedMinDisplacement && !passedMaxOrthogonalDisplacement;
+        boolean isDisplacementOrthogonal = totalDisplacement.orthogonal > totalDisplacement.primary;
+        if (!passedMinDisplacement || isDisplacementOrthogonal) {
+            mForcePauseTimeout.cancelAlarm();
+            isPaused = false;
+        }
         updatePaused(isPaused);
     }
 
diff --git a/quickstep/tests/src/com/android/quickstep/QuickStepOnOffRule.java b/quickstep/tests/src/com/android/quickstep/QuickStepOnOffRule.java
index 7274090..f89842a 100644
--- a/quickstep/tests/src/com/android/quickstep/QuickStepOnOffRule.java
+++ b/quickstep/tests/src/com/android/quickstep/QuickStepOnOffRule.java
@@ -19,6 +19,12 @@
 import static com.android.quickstep.QuickStepOnOffRule.Mode.BOTH;
 import static com.android.quickstep.QuickStepOnOffRule.Mode.OFF;
 import static com.android.quickstep.QuickStepOnOffRule.Mode.ON;
+import static com.android.systemui.shared.system.SettingsCompat.SWIPE_UP_SETTING_NAME;
+
+import static org.junit.Assert.assertTrue;
+
+import android.provider.Settings;
+import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -41,6 +47,8 @@
  */
 public class QuickStepOnOffRule implements TestRule {
 
+    static final String TAG = "QuickStepOnOffRule";
+
     public enum Mode {
         ON, OFF, BOTH
     }
@@ -68,39 +76,53 @@
             return new Statement() {
                 @Override
                 public void evaluate() throws Throwable {
-                    try {
-                        if (mode == ON || mode == BOTH) {
-                            evaluateWithQuickstepOn();
+                    if (SwipeUpSetting.isSwipeUpSettingAvailable()) {
+                        try {
+                            if (mode == ON || mode == BOTH) {
+                                evaluateWithQuickstepOn();
+                            }
+                            if (mode == OFF || mode == BOTH) {
+                                evaluateWithQuickstepOff();
+                            }
+                        } finally {
+                            setSwipeUpSetting(null);
                         }
-                        if (mode == OFF || mode == BOTH) {
-                            evaluateWithQuickstepOff();
+                    } else {
+                        // Execute without changing the setting, if the requested mode is
+                        // compatible.
+                        final boolean swipeUpEnabledDefaultValue =
+                                SwipeUpSetting.isSwipeUpEnabledDefaultValue();
+                        if (mode == BOTH ||
+                                mode == ON && swipeUpEnabledDefaultValue ||
+                                mode == OFF && !swipeUpEnabledDefaultValue) {
+                            evaluateWithoutChangingSetting(base);
                         }
-                    } finally {
-                        overrideSwipeUpEnabled(null);
                     }
                 }
 
-                private void evaluateWithQuickstepOff() throws Throwable {
-                    overrideSwipeUpEnabled(false);
+                public void setSwipeUpSetting(String value) {
+                    Log.d(TAG, "setSwipeUpSetting: " + value);
+                    assertTrue("Couldn't change Quickstep mode",
+                            Settings.Secure.putString(
+                                    InstrumentationRegistry.getInstrumentation().getTargetContext().
+                                            getContentResolver(),
+                                    SWIPE_UP_SETTING_NAME,
+                                    value));
+                }
+
+                public void evaluateWithoutChangingSetting(Statement base) throws Throwable {
                     base.evaluate();
                 }
 
+                private void evaluateWithQuickstepOff() throws Throwable {
+                    setSwipeUpSetting("0");
+                    evaluateWithoutChangingSetting(base);
+                }
+
                 private void evaluateWithQuickstepOn() throws Throwable {
-                    overrideSwipeUpEnabled(true);
+                    setSwipeUpSetting("1");
                     base.evaluate();
                 }
-
-                private void overrideSwipeUpEnabled(Boolean swipeUpEnabledOverride)
-                        throws Throwable {
-                    mLauncher.overrideSwipeUpEnabled(swipeUpEnabledOverride);
-                    mMainThreadExecutor.execute(() -> OverviewInteractionState.INSTANCE.get(
-                            InstrumentationRegistry.getInstrumentation().getTargetContext()).
-                            notifySwipeUpSettingChanged(mLauncher.isSwipeUpEnabled()));
-                    // TODO(b/124236673): avoid using sleep().
-                    mLauncher.getDevice().waitForIdle();
-                    Thread.sleep(2000);
-                    mLauncher.getDevice().waitForIdle();
-                }
             };
         } else {
             return base;
diff --git a/res/drawable/horizontal_ellipsis.xml b/res/drawable/horizontal_ellipsis.xml
index ad08529..65e958d 100644
--- a/res/drawable/horizontal_ellipsis.xml
+++ b/res/drawable/horizontal_ellipsis.xml
@@ -22,6 +22,6 @@
         android:tint="?android:attr/textColorSecondary" >
 
     <path
-        android:pathData="M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"
+        android:pathData="M6 10c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2zm12 0c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2zm-6 0c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2z"
         android:fillColor="@android:color/white" />
 </vector>
\ No newline at end of file
diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml
index 91c3705..c9cea80 100644
--- a/res/layout/launcher.xml
+++ b/res/layout/launcher.xml
@@ -38,6 +38,15 @@
             android:theme="@style/HomeScreenElementTheme"
             launcher:pageIndicator="@+id/page_indicator" />
 
+        <!-- DO NOT CHANGE THE ID -->
+        <com.android.launcher3.Hotseat
+            android:id="@+id/hotseat"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:theme="@style/HomeScreenElementTheme"
+            android:importantForAccessibility="no"
+            launcher:containerType="hotseat" />
+
         <include
             android:id="@+id/overview_panel"
             layout="@layout/overview_panel"
@@ -71,15 +80,6 @@
             android:layout_width="match_parent"
             android:layout_height="match_parent" />
 
-        <!-- DO NOT CHANGE THE ID -->
-        <com.android.launcher3.Hotseat
-            android:id="@+id/hotseat"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:theme="@style/HomeScreenElementTheme"
-            android:importantForAccessibility="no"
-            launcher:containerType="hotseat" />
-
     </com.android.launcher3.dragndrop.DragLayer>
 
 </com.android.launcher3.LauncherRootView>
diff --git a/res/layout/snackbar.xml b/res/layout/snackbar.xml
index bca3308..b818943 100644
--- a/res/layout/snackbar.xml
+++ b/res/layout/snackbar.xml
@@ -29,7 +29,7 @@
         android:ellipsize="end"
         android:textSize="@dimen/snackbar_max_text_size"
         android:textColor="?android:attr/textColorPrimary"
-        android:theme="@style/TextTitle"/>
+        style="@style/TextTitle"/>
     <TextView
         android:id="@+id/action"
         android:layout_height="@dimen/snackbar_content_height"
@@ -42,6 +42,6 @@
         android:textStyle="bold"
         android:textSize="@dimen/snackbar_max_text_size"
         android:textColor="?android:attr/colorAccent"
-        android:theme="@style/TextTitle"
+        style="@style/TextTitle"
         android:capitalize="sentences"/>
 </merge>
\ No newline at end of file
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index e5b1448..62bc53a 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -24,7 +24,6 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
-import android.content.res.XmlResourceParser;
 import android.database.sqlite.SQLiteDatabase;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
@@ -33,9 +32,11 @@
 import android.os.Process;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Patterns;
+import android.util.Xml;
 
 import com.android.launcher3.LauncherProvider.SqlArguments;
 import com.android.launcher3.LauncherSettings.Favorites;
@@ -149,9 +150,6 @@
     private static final String HOTSEAT_CONTAINER_NAME =
             Favorites.containerToString(Favorites.CONTAINER_HOTSEAT);
 
-    private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE =
-            "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE";
-
     @Thunk final Context mContext;
     @Thunk final AppWidgetHost mAppWidgetHost;
     protected final LayoutParserCallback mCallback;
@@ -207,7 +205,7 @@
      */
     protected int parseLayout(int layoutId, IntArray screenIds)
             throws XmlPullParserException, IOException {
-        XmlResourceParser parser = mSourceRes.getXml(layoutId);
+        XmlPullParser parser = mSourceRes.getXml(layoutId);
         beginDocument(parser, mRootTag);
         final int depth = parser.getDepth();
         int type;
@@ -228,7 +226,7 @@
      * Parses container and screenId attribute from the current tag, and puts it in the out.
      * @param out array of size 2.
      */
-    protected void parseContainerAndScreen(XmlResourceParser parser, int[] out) {
+    protected void parseContainerAndScreen(XmlPullParser parser, int[] out) {
         if (HOTSEAT_CONTAINER_NAME.equals(getAttributeValue(parser, ATTR_CONTAINER))) {
             out[0] = Favorites.CONTAINER_HOTSEAT;
             // Hack: hotseat items are stored using screen ids
@@ -243,7 +241,7 @@
      * Parses the current node and returns the number of elements added.
      */
     protected int parseAndAddNode(
-            XmlResourceParser parser, ArrayMap<String, TagParser> tagParserMap, IntArray screenIds)
+            XmlPullParser parser, ArrayMap<String, TagParser> tagParserMap, IntArray screenIds)
         throws XmlPullParserException, IOException {
 
         if (TAG_INCLUDE.equals(parser.getName())) {
@@ -324,7 +322,7 @@
          * Parses the tag and adds to the db
          * @return the id of the row added or -1;
          */
-        int parseAndAdd(XmlResourceParser parser)
+        int parseAndAdd(XmlPullParser parser)
                 throws XmlPullParserException, IOException;
     }
 
@@ -334,7 +332,7 @@
     protected class AppShortcutParser implements TagParser {
 
         @Override
-        public int parseAndAdd(XmlResourceParser parser) {
+        public int parseAndAdd(XmlPullParser parser) {
             final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
             final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
 
@@ -371,7 +369,7 @@
         /**
          * Helper method to allow extending the parser capabilities
          */
-        protected int invalidPackageOrClass(XmlResourceParser parser) {
+        protected int invalidPackageOrClass(XmlPullParser parser) {
             Log.w(TAG, "Skipping invalid <favorite> with no component");
             return -1;
         }
@@ -383,7 +381,7 @@
     protected class AutoInstallParser implements TagParser {
 
         @Override
-        public int parseAndAdd(XmlResourceParser parser) {
+        public int parseAndAdd(XmlPullParser parser) {
             final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
             final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
             if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className)) {
@@ -414,7 +412,7 @@
         }
 
         @Override
-        public int parseAndAdd(XmlResourceParser parser) {
+        public int parseAndAdd(XmlPullParser parser) {
             final int titleResId = getAttributeResourceValue(parser, ATTR_TITLE, 0);
             final int iconId = getAttributeResourceValue(parser, ATTR_ICON, 0);
 
@@ -449,7 +447,7 @@
                     intent, Favorites.ITEM_TYPE_SHORTCUT);
         }
 
-        protected Intent parseIntent(XmlResourceParser parser) {
+        protected Intent parseIntent(XmlPullParser parser) {
             final String url = getAttributeValue(parser, ATTR_URL);
             if (TextUtils.isEmpty(url) || !Patterns.WEB_URL.matcher(url).matches()) {
                 if (LOGD) Log.d(TAG, "Ignoring shortcut, invalid url: " + url);
@@ -470,7 +468,7 @@
     protected class PendingWidgetParser implements TagParser {
 
         @Override
-        public int parseAndAdd(XmlResourceParser parser)
+        public int parseAndAdd(XmlPullParser parser)
                 throws XmlPullParserException, IOException {
             final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
             final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
@@ -541,7 +539,7 @@
         }
 
         @Override
-        public int parseAndAdd(XmlResourceParser parser)
+        public int parseAndAdd(XmlPullParser parser)
                 throws XmlPullParserException, IOException {
             final String title;
             final int titleResId = getAttributeResourceValue(parser, ATTR_TITLE, 0);
@@ -649,7 +647,7 @@
      * Return attribute value, attempting launcher-specific namespace first
      * before falling back to anonymous attribute.
      */
-    protected static String getAttributeValue(XmlResourceParser parser, String attribute) {
+    protected static String getAttributeValue(XmlPullParser parser, String attribute) {
         String value = parser.getAttributeValue(
                 "http://schemas.android.com/apk/res-auto/com.android.launcher3", attribute);
         if (value == null) {
@@ -662,13 +660,14 @@
      * Return attribute resource value, attempting launcher-specific namespace
      * first before falling back to anonymous attribute.
      */
-    protected static int getAttributeResourceValue(XmlResourceParser parser, String attribute,
+    protected static int getAttributeResourceValue(XmlPullParser parser, String attribute,
             int defaultValue) {
-        int value = parser.getAttributeResourceValue(
+        AttributeSet attrs = Xml.asAttributeSet(parser);
+        int value = attrs.getAttributeResourceValue(
                 "http://schemas.android.com/apk/res-auto/com.android.launcher3", attribute,
                 defaultValue);
         if (value == defaultValue) {
-            value = parser.getAttributeResourceValue(null, attribute, defaultValue);
+            value = attrs.getAttributeResourceValue(null, attribute, defaultValue);
         }
         return value;
     }
diff --git a/src/com/android/launcher3/DefaultLayoutParser.java b/src/com/android/launcher3/DefaultLayoutParser.java
index 44830e8..75297f6 100644
--- a/src/com/android/launcher3/DefaultLayoutParser.java
+++ b/src/com/android/launcher3/DefaultLayoutParser.java
@@ -10,7 +10,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
-import android.content.res.XmlResourceParser;
 import android.os.Bundle;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -76,7 +75,7 @@
     }
 
     @Override
-    protected void parseContainerAndScreen(XmlResourceParser parser, int[] out) {
+    protected void parseContainerAndScreen(XmlPullParser parser, int[] out) {
         out[0] = LauncherSettings.Favorites.CONTAINER_DESKTOP;
         String strContainer = getAttributeValue(parser, ATTR_CONTAINER);
         if (strContainer != null) {
@@ -91,7 +90,7 @@
     public class AppShortcutWithUriParser extends AppShortcutParser {
 
         @Override
-        protected int invalidPackageOrClass(XmlResourceParser parser) {
+        protected int invalidPackageOrClass(XmlPullParser parser) {
             final String uri = getAttributeValue(parser, ATTR_URI);
             if (TextUtils.isEmpty(uri)) {
                 Log.e(TAG, "Skipping invalid <favorite> with no component or uri");
@@ -185,7 +184,7 @@
         }
 
         @Override
-        protected Intent parseIntent(XmlResourceParser parser) {
+        protected Intent parseIntent(XmlPullParser parser) {
             String uri = null;
             try {
                 uri = getAttributeValue(parser, ATTR_URI);
@@ -205,7 +204,7 @@
         private final AppShortcutWithUriParser mChildParser = new AppShortcutWithUriParser();
 
         @Override
-        public int parseAndAdd(XmlResourceParser parser) throws XmlPullParserException,
+        public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
                 IOException {
             final int groupDepth = parser.getDepth();
             int type;
@@ -233,7 +232,7 @@
     @Thunk class PartnerFolderParser implements TagParser {
 
         @Override
-        public int parseAndAdd(XmlResourceParser parser) throws XmlPullParserException,
+        public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
                 IOException {
             // Folder contents come from an external XML resource
             final Partner partner = Partner.get(mPackageManager);
@@ -242,7 +241,7 @@
                 final int resId = partnerRes.getIdentifier(Partner.RES_FOLDER,
                         "xml", partner.getPackageName());
                 if (resId != 0) {
-                    final XmlResourceParser partnerParser = partnerRes.getXml(resId);
+                    final XmlPullParser partnerParser = partnerRes.getXml(resId);
                     beginDocument(partnerParser, TAG_FOLDER);
 
                     FolderParser folderParser = new FolderParser(getFolderElementsMap(partnerRes));
@@ -259,7 +258,7 @@
     @Thunk class MyFolderParser extends FolderParser {
 
         @Override
-        public int parseAndAdd(XmlResourceParser parser) throws XmlPullParserException,
+        public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
                 IOException {
             final int resId = getAttributeResourceValue(parser, ATTR_FOLDER_ITEMS, 0);
             if (resId != 0) {
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 01535b0..3b054c2 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -611,6 +611,12 @@
         outBounds.right = outBounds.left + (getCellSize().x * spanX);
     }
 
+    public float getAspectRatioWithInsets() {
+        int w = widthPx - mInsets.left - mInsets.right;
+        int h = heightPx - mInsets.top - mInsets.bottom;
+        return ((float) Math.max(w, h)) / Math.min(w, h);
+    }
+
     private static Context getContext(Context c, int orientation) {
         Configuration context = new Configuration(c.getResources().getConfiguration());
         context.orientation = orientation;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index f571aa3..d820448 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -257,7 +257,7 @@
 
     private RotationHelper mRotationHelper;
 
-    private final Handler mHandler = new Handler();
+    final Handler mHandler = new Handler();
     private final Runnable mHandleDeferredResume = this::handleDeferredResume;
 
     @Override
@@ -319,6 +319,7 @@
             }
         }
         restoreState(savedInstanceState);
+        mStateManager.reapplyState();
 
         // We only load the page synchronously if the user rotates (or triggers a
         // configuration change) while launcher is in the foreground
@@ -1747,6 +1748,8 @@
         setWorkspaceLoading(true);
 
         // Clear the workspace because it's going to be rebound
+        mDragController.cancelDrag();
+
         mWorkspace.clearDropTargets();
         mWorkspace.removeAllWorkspaceScreens();
         mAppWidgetHost.clearViews();
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 5ab6eea..b1664f1 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -34,6 +34,7 @@
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.AddWorkspaceItemsTask;
@@ -210,20 +211,20 @@
             final int itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
         ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId);
         if (modelItem != null && item != modelItem) {
-            // check all the data is consistent
-            if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
-                ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
-                ShortcutInfo shortcut = (ShortcutInfo) item;
-                if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
-                        modelShortcut.intent.filterEquals(shortcut.intent) &&
-                        modelShortcut.id == shortcut.id &&
-                        modelShortcut.itemType == shortcut.itemType &&
-                        modelShortcut.container == shortcut.container &&
-                        modelShortcut.screenId == shortcut.screenId &&
-                        modelShortcut.cellX == shortcut.cellX &&
-                        modelShortcut.cellY == shortcut.cellY &&
-                        modelShortcut.spanX == shortcut.spanX &&
-                        modelShortcut.spanY == shortcut.spanY) {
+            // If it is a release build on a release device, check all the data is consistent as
+            // we don't want to crash non-dev users.
+            if (!Utilities.IS_DEBUG_DEVICE && !FeatureFlags.IS_DOGFOOD_BUILD &&
+                    modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
+                if (modelItem.title.toString().equals(item.title.toString()) &&
+                        modelItem.getIntent().filterEquals(item.getIntent()) &&
+                        modelItem.id == item.id &&
+                        modelItem.itemType == item.itemType &&
+                        modelItem.container == item.container &&
+                        modelItem.screenId == item.screenId &&
+                        modelItem.cellX == item.cellX &&
+                        modelItem.cellY == item.cellY &&
+                        modelItem.spanX == item.spanX &&
+                        modelItem.spanY == item.spanY) {
                     // For all intents and purposes, this is the same object
                     return;
                 }
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index fb33694..ce73229 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -20,6 +20,7 @@
 import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
 
 import android.annotation.TargetApi;
+import android.app.backup.BackupManager;
 import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetManager;
 import android.content.ComponentName;
@@ -150,7 +151,7 @@
             mOpenHelper = new DatabaseHelper(getContext(), mListenerHandler);
 
             if (RestoreDbTask.isPending(getContext())) {
-                if (!RestoreDbTask.performRestore(mOpenHelper)) {
+                if (!RestoreDbTask.performRestore(mOpenHelper, new BackupManager(getContext()))) {
                     mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
                 }
                 // Set is pending to false irrespective of the result, so that it doesn't get
@@ -542,6 +543,7 @@
      * The class is subclassed in tests to create an in-memory db.
      */
     public static class DatabaseHelper extends NoLocaleSQLiteHelper implements LayoutParserCallback {
+        private final BackupManager mBackupManager;
         private final Handler mWidgetHostResetHandler;
         private final Context mContext;
         private int mMaxItemId = -1;
@@ -571,6 +573,7 @@
             super(context, tableName, SCHEMA_VERSION);
             mContext = context;
             mWidgetHostResetHandler = widgetHostResetHandler;
+            mBackupManager = new BackupManager(mContext);
         }
 
         protected void initIds() {
@@ -620,9 +623,12 @@
             Utilities.getPrefs(mContext).edit().putBoolean(EMPTY_DATABASE_CREATED, true).commit();
         }
 
+        public long getSerialNumberForUser(UserHandle user) {
+            return UserManagerCompat.getInstance(mContext).getSerialNumberForUser(user);
+        }
+
         public long getDefaultUserSerial() {
-            return UserManagerCompat.getInstance(mContext).getSerialNumberForUser(
-                    Process.myUserHandle());
+            return getSerialNumberForUser(Process.myUserHandle());
         }
 
         private void addFavoritesTable(SQLiteDatabase db, boolean optional) {
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 6d85612..cee1c26 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -187,6 +187,11 @@
         return new float[] {1, 0, 0};
     }
 
+    public float[] getHotseatScaleAndTranslation(Launcher launcher) {
+        // For most states, treat the hotseat as if it were part of the workspace.
+        return getWorkspaceScaleAndTranslation(launcher);
+    }
+
     /**
      * Returns 2 floats designating how to transition overview:
      *   scale for the current and adjacent pages
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index a877141..f6b54f2 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -17,7 +17,6 @@
 package com.android.launcher3;
 
 import static android.view.View.VISIBLE;
-
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
@@ -249,6 +248,22 @@
             return;
         }
 
+        if (delay > 0) {
+            // Create the animation after the delay as some properties can change between preparing
+            // the animation and running the animation.
+            int startChangeId = mConfig.mChangeId;
+            mUiHandler.postDelayed(() -> {
+                if (mConfig.mChangeId == startChangeId) {
+                    goToStateAnimated(state, fromState, onCompleteRunnable);
+                }
+            }, delay);
+        } else {
+            goToStateAnimated(state, fromState, onCompleteRunnable);
+        }
+    }
+
+    private void goToStateAnimated(LauncherState state, LauncherState fromState,
+            Runnable onCompleteRunnable) {
         // Since state NORMAL can be reached from multiple states, just assume that the
         // transition plays in reverse and use the same duration as previous state.
         mConfig.duration = state == NORMAL ? fromState.transitionDuration : state.transitionDuration;
@@ -257,12 +272,7 @@
         prepareForAtomicAnimation(fromState, state, builder);
         AnimatorSet animation = createAnimationToNewWorkspaceInternal(
                 state, builder, onCompleteRunnable);
-        Runnable runnable = new StartAnimRunnable(animation);
-        if (delay > 0) {
-            mUiHandler.postDelayed(runnable, delay);
-        } else {
-            mUiHandler.post(runnable);
-        }
+        mUiHandler.post(new StartAnimRunnable(animation));
     }
 
     /**
@@ -298,6 +308,8 @@
             if (!isWorkspaceVisible) {
                 workspace.setScaleX(0.92f);
                 workspace.setScaleY(0.92f);
+                workspace.getHotseat().setScaleX(0.92f);
+                workspace.getHotseat().setScaleY(0.92f);
             }
         }
     }
@@ -532,6 +544,8 @@
 
         private AnimatorSet mCurrentAnimation;
         private LauncherState mTargetState;
+        // Id to keep track of config changes, to tie an animation with the corresponding request
+        private int mChangeId = 0;
 
         /**
          * Cancels the current animation and resets config variables.
@@ -553,6 +567,7 @@
 
             mCurrentAnimation = null;
             playbackController = null;
+            mChangeId ++;
         }
 
         public PropertySetter getPropertySetter(AnimatorSetBuilder builder) {
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 00da6fa..7fa1aa0 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -1027,7 +1027,8 @@
         if (overScrollAmount < 0) {
             super.scrollTo(overScrollAmount, getScrollY());
         } else {
-            super.scrollTo(mMaxScrollX + overScrollAmount, getScrollY());
+            int x = Math.max(0, Math.min(getScrollX(), mMaxScrollX));
+            super.scrollTo(x + overScrollAmount, getScrollY());
         }
         invalidate();
     }
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 832e9c9..e699500 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -27,6 +27,7 @@
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherActivityInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -37,6 +38,7 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.DeadObjectException;
@@ -55,14 +57,24 @@
 import android.view.View;
 import android.view.animation.Interpolator;
 
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.ShortcutConfigActivityInfo;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.DeepShortcutView;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.IntArray;
+import com.android.launcher3.widget.PendingAddShortcutInfo;
 
 import java.io.Closeable;
 import java.io.IOException;
 import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
 import java.util.Locale;
 import java.util.concurrent.Executor;
 import java.util.concurrent.LinkedBlockingQueue;
@@ -556,6 +568,7 @@
     public static void getLocationBoundsForView(Launcher launcher, View v, Rect outRect) {
         final DragLayer dragLayer = launcher.getDragLayer();
         final boolean isBubbleTextView = v instanceof BubbleTextView;
+        final boolean isFolderIcon = v instanceof FolderIcon;
         final Rect rect = new Rect();
 
         final boolean fromDeepShortcutView = v.getParent() instanceof DeepShortcutView;
@@ -563,15 +576,14 @@
             // Deep shortcut views have their icon drawn in a separate view.
             DeepShortcutView view = (DeepShortcutView) v.getParent();
             dragLayer.getDescendantRectRelativeToSelf(view.getIconView(), rect);
-        } else if (isBubbleTextView && v.getTag() instanceof ItemInfo
+        } else if ((isBubbleTextView || isFolderIcon) && v.getTag() instanceof ItemInfo
                 && (((ItemInfo) v.getTag()).container == CONTAINER_DESKTOP
                 || ((ItemInfo) v.getTag()).container == CONTAINER_HOTSEAT)) {
-            BubbleTextView btv = (BubbleTextView) v;
-            CellLayout pageViewIsOn = ((CellLayout) btv.getParent().getParent());
+            CellLayout pageViewIsOn = ((CellLayout) v.getParent().getParent());
             int pageNum = launcher.getWorkspace().indexOfChild(pageViewIsOn);
 
             DeviceProfile dp = launcher.getDeviceProfile();
-            ItemInfo info = ((ItemInfo) btv.getTag());
+            ItemInfo info = ((ItemInfo) v.getTag());
             dp.getItemLocation(info.cellX, info.cellY, info.spanX, info.spanY,
                     info.container, pageNum - launcher.getCurrentWorkspaceScreen(), rect);
         } else {
@@ -581,8 +593,9 @@
         int viewLocationTop = rect.top;
 
         if (isBubbleTextView && !fromDeepShortcutView) {
-            BubbleTextView btv = (BubbleTextView) v;
-            btv.getIconBounds(rect);
+            ((BubbleTextView) v).getIconBounds(rect);
+        } else if (isFolderIcon) {
+            ((FolderIcon) v).getPreviewBounds(rect);
         } else {
             rect.set(0, 0, rect.width(), rect.height());
         }
@@ -595,6 +608,51 @@
     public static void unregisterReceiverSafely(Context context, BroadcastReceiver receiver) {
         try {
             context.unregisterReceiver(receiver);
-        } catch (IllegalArgumentException e) { }
+        } catch (IllegalArgumentException e) {}
+    }
+
+    /**
+     * Returns the full drawable for {@param info}.
+     * @param outObj this is set to the internal data associated with {@param info},
+     *               eg {@link LauncherActivityInfo} or {@link ShortcutInfoCompat}.
+     */
+    public static Drawable getFullDrawable(Launcher launcher, ItemInfo info, int width, int height,
+            boolean flattenDrawable, Object[] outObj) {
+        LauncherAppState appState = LauncherAppState.getInstance(launcher);
+        if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+            LauncherActivityInfo activityInfo = LauncherAppsCompat.getInstance(launcher)
+                    .resolveActivity(info.getIntent(), info.user);
+            outObj[0] = activityInfo;
+            return (activityInfo != null) ? appState.getIconCache()
+                    .getFullResIcon(activityInfo, flattenDrawable) : null;
+        } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+            if (info instanceof PendingAddShortcutInfo) {
+                ShortcutConfigActivityInfo activityInfo =
+                        ((PendingAddShortcutInfo) info).activityInfo;
+                outObj[0] = activityInfo;
+                return activityInfo.getFullResIcon(appState.getIconCache());
+            }
+            ShortcutKey key = ShortcutKey.fromItemInfo(info);
+            DeepShortcutManager sm = DeepShortcutManager.getInstance(launcher);
+            List<ShortcutInfoCompat> si = sm.queryForFullDetails(
+                    key.componentName.getPackageName(), Arrays.asList(key.getId()), key.user);
+            if (si.isEmpty()) {
+                return null;
+            } else {
+                outObj[0] = si.get(0);
+                return sm.getShortcutIconDrawable(si.get(0),
+                        appState.getInvariantDeviceProfile().fillResIconDpi);
+            }
+        } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
+            FolderAdaptiveIcon icon = FolderAdaptiveIcon.createFolderAdaptiveIcon(
+                    launcher, info.id, new Point(width, height));
+            if (icon == null) {
+                return null;
+            }
+            outObj[0] = icon;
+            return icon;
+        } else {
+            return null;
+        }
     }
 }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 7f5ca42..09eb6d9 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -19,9 +19,13 @@
 import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_TRANSITION_MS;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
+import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_OVERLAY;
 
 import android.animation.Animator;
@@ -34,7 +38,6 @@
 import android.app.WallpaperManager;
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -44,8 +47,10 @@
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Message;
 import android.os.Parcelable;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.SparseArray;
@@ -98,8 +103,6 @@
 
 import java.util.ArrayList;
 import java.util.HashSet;
-import java.util.Objects;
-import java.util.Set;
 import java.util.function.Predicate;
 
 /**
@@ -521,6 +524,9 @@
         mScreenOrder.clear();
         mWorkspaceScreens.clear();
 
+        // Remove any deferred refresh callbacks
+        mLauncher.mHandler.removeCallbacksAndMessages(DeferredWidgetRefresh.class);
+
         // Ensure that the first page is always present
         bindAndInitFirstWorkspaceScreen(qsb);
 
@@ -1608,7 +1614,7 @@
 
         boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo);
         boolean willBecomeShortcut =
-                (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
+                (info.itemType == ITEM_TYPE_APPLICATION ||
                         info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
                         info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT);
 
@@ -2516,7 +2522,7 @@
             View view;
 
             switch (info.itemType) {
-            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+            case ITEM_TYPE_APPLICATION:
             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
             case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
                 if (info.container == NO_ID && info instanceof AppInfo) {
@@ -2892,51 +2898,29 @@
     }
 
     /**
-     * Returns a list of all the CellLayouts on the Homescreen, starting with
-     * {@param startPage}, then going outward alternating between pages prior to the startPage,
-     * and then the pages after the startPage.
-     * ie. if there are 5 pages [0, 1, 2, 3, 4] and startPage is 1, we return [1, 0, 2, 3, 4].
-     */
-    private CellLayout[] getWorkspaceCellLayouts(int startPage) {
-        int screenCount = getChildCount();
-        final CellLayout[] layouts = new CellLayout[screenCount];
-        int screen = 0;
-
-        layouts[screen] = (CellLayout) getChildAt(startPage);
-        screen++;
-
-        for (int i = 1; screen < screenCount; ++i) {
-            CellLayout prevPage = (CellLayout) getChildAt(startPage - i);
-            CellLayout nextPage = (CellLayout) getChildAt(startPage + i);
-
-            if (prevPage != null) {
-                layouts[screen] = prevPage;
-                screen++;
-            }
-            if (nextPage != null) {
-                layouts[screen] = nextPage;
-                screen++;
-            }
-        }
-        return layouts;
-    }
-
-    /**
      * Similar to {@link #getFirstMatch} but optimized to finding a suitable view for the app close
      * animation.
      *
-     * @param component The component of the task being dismissed.
+     * @param packageName The package name of the app to match.
+     * @param user The user of the app to match.
      */
-    public View getFirstMatchForAppClose(ComponentName component) {
+    public View getFirstMatchForAppClose(String packageName, UserHandle user) {
         final int curPage = getCurrentPage();
         final CellLayout currentPage = (CellLayout) getPageAt(curPage);
-        final Workspace.ItemOperator isItemComponent = (info, view) ->
-                info != null && Objects.equals(info.getTargetComponent(), component);
-        final Workspace.ItemOperator isItemInFolder = (info, view) -> {
+        final Workspace.ItemOperator packageAndUser = (ItemInfo info, View view) -> info != null
+                && info.getTargetComponent() != null
+                && TextUtils.equals(info.getTargetComponent().getPackageName(), packageName)
+                && info.user.equals(user);
+        final Workspace.ItemOperator packageAndUserAndApp = (ItemInfo info, View view) ->
+                packageAndUser.evaluate(info, view) && info.itemType == ITEM_TYPE_APPLICATION;
+        final Workspace.ItemOperator packageAndUserAndShortcut = (ItemInfo info, View view) ->
+                packageAndUser.evaluate(info, view) && (info.itemType == ITEM_TYPE_SHORTCUT
+                        || info.itemType == ITEM_TYPE_DEEP_SHORTCUT);
+        final Workspace.ItemOperator packageAndUserInFolder = (info, view) -> {
             if (info instanceof FolderInfo) {
                 FolderInfo folderInfo = (FolderInfo) info;
                 for (ShortcutInfo shortcutInfo : folderInfo.contents) {
-                    if (Objects.equals(shortcutInfo.getTargetComponent(), component)) {
+                    if (packageAndUser.evaluate(shortcutInfo, view)) {
                         return true;
                     }
                 }
@@ -2944,33 +2928,16 @@
             return false;
         };
 
-        CellLayout[] hotseatAndCurrentPage = new CellLayout[] { getHotseat(), currentPage };
-        // First we look if the app itself is in the hotseat or on the current workspace page.
-        View icon = getFirstMatch(hotseatAndCurrentPage, isItemComponent);
-        if (icon != null) {
-            return icon;
+        // Order: App icons, shortcuts, app/shortcut in folder. Items in hotseat get returned first.
+        if (ADAPTIVE_ICON_WINDOW_ANIM.get()) {
+            return getFirstMatch(new CellLayout[] { getHotseat(), currentPage },
+                    packageAndUserAndApp, packageAndUserAndShortcut, packageAndUserInFolder);
+        } else {
+            // Do not use Folder as a criteria, since it'll cause a crash when trying to draw
+            // FolderAdaptiveIcon as the background.
+            return getFirstMatch(new CellLayout[] { getHotseat(), currentPage },
+                    packageAndUserAndApp, packageAndUserAndShortcut);
         }
-        // Then we look if the app is in a folder on the hotseat or current workspace page.
-        icon = getFirstMatch(hotseatAndCurrentPage, isItemInFolder);
-        if (icon != null) {
-            return icon;
-        }
-        // Continue searching for the app or for a folder with the app on other pages of the
-        // workspace. We skip the current page, since we already searched above.
-        CellLayout[] allPages = getWorkspaceCellLayouts(curPage);
-        CellLayout[] page = new CellLayout[1];
-        for (int i = 1; i < allPages.length; ++i) {
-            page[0] = allPages[i];
-            icon = getFirstMatch(page, isItemComponent);
-            if (icon != null) {
-                return icon;
-            }
-            icon = getFirstMatch(page, isItemInFolder);
-            if (icon != null) {
-                return icon;
-            }
-        }
-        return null;
     }
 
     public View getHomescreenIconByItemId(final int id) {
@@ -2998,21 +2965,38 @@
         return value[0];
     }
 
-    private View getFirstMatch(CellLayout[] cellLayouts, final ItemOperator operator) {
-        final View[] value = new View[1];
+    /**
+     * @param cellLayouts List of CellLayouts to scan, in order of preference.
+     * @param operators List of operators, in order starting from best matching operator.
+     * @return
+     */
+    private View getFirstMatch(CellLayout[] cellLayouts, final ItemOperator... operators) {
+        // This array is filled with the first match for each operator.
+        final View[] matches = new View[operators.length];
+        // For efficiency, the outer loop should be CellLayout.
         for (CellLayout cellLayout : cellLayouts) {
             mapOverCellLayout(MAP_NO_RECURSE, cellLayout, (info, v) -> {
-                if (operator.evaluate(info, v)) {
-                    value[0] = v;
-                    return true;
+                for (int i = 0; i < operators.length; ++i) {
+                    if (matches[i] == null && operators[i].evaluate(info, v)) {
+                        matches[i] = v;
+                        if (i == 0) {
+                            // We can return since this is the best match possible.
+                            return true;
+                        }
+                    }
                 }
                 return false;
             });
-            if (value[0] != null) {
+            if (matches[0] != null) {
                 break;
             }
         }
-        return value[0];
+        for (View match : matches) {
+            if (match != null) {
+                return match;
+            }
+        }
+        return null;
     }
 
     void clearDropTargets() {
@@ -3348,13 +3332,15 @@
             LauncherAppWidgetHost host) {
             mInfos = infos;
             mHost = host;
-            mHandler = new Handler();
+            mHandler = mLauncher.mHandler;
             mRefreshPending = true;
 
             mHost.addProviderChangeListener(this);
             // Force refresh after 10 seconds, if we don't get the provider changed event.
             // This could happen when the provider is no longer available in the app.
-            mHandler.postDelayed(this, 10000);
+            Message msg = Message.obtain(mHandler, this);
+            msg.obj = DeferredWidgetRefresh.class;
+            mHandler.sendMessageDelayed(msg, 10000);
         }
 
         @Override
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 3e09493..0507470 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -19,7 +19,6 @@
 import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
-import static com.android.launcher3.LauncherState.HOTSEAT_SEARCH_BOX;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -35,6 +34,7 @@
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
 
 /**
@@ -72,6 +72,7 @@
     private void setWorkspaceProperty(LauncherState state, PropertySetter propertySetter,
             AnimatorSetBuilder builder, AnimationConfig config) {
         float[] scaleAndTranslation = state.getWorkspaceScaleAndTranslation(mLauncher);
+        float[] hotseatScaleAndTranslation = state.getHotseatScaleAndTranslation(mLauncher);
         mNewScale = scaleAndTranslation[0];
         PageAlphaProvider pageAlphaProvider = state.getWorkspacePageAlphaProvider(mLauncher);
         final int childCount = mWorkspace.getChildCount();
@@ -84,12 +85,24 @@
         Interpolator fadeInterpolator = builder.getInterpolator(ANIM_WORKSPACE_FADE,
                 pageAlphaProvider.interpolator);
         boolean playAtomicComponent = config.playAtomicComponent();
+        Hotseat hotseat = mWorkspace.getHotseat();
         if (playAtomicComponent) {
             Interpolator scaleInterpolator = builder.getInterpolator(ANIM_WORKSPACE_SCALE, ZOOM_OUT);
             propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator);
+
+            // Set the hotseat's pivot point to match the workspace's, so that it scales together.
+            DragLayer dragLayer = mLauncher.getDragLayer();
+            int[] workspacePivot = new int[]{(int) mWorkspace.getPivotX(),
+                    (int) mWorkspace.getPivotY()};
+            dragLayer.getDescendantCoordRelativeToSelf(mWorkspace, workspacePivot);
+            dragLayer.mapCoordInSelfToDescendant(hotseat, workspacePivot);
+            hotseat.setPivotX(workspacePivot[0]);
+            hotseat.setPivotY(workspacePivot[1]);
+            float hotseatScale = hotseatScaleAndTranslation[0];
+            propertySetter.setFloat(hotseat, SCALE_PROPERTY, hotseatScale, scaleInterpolator);
+
             float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
-            propertySetter.setViewAlpha(mLauncher.getHotseat(), hotseatIconsAlpha,
-                    fadeInterpolator);
+            propertySetter.setViewAlpha(hotseat, hotseatIconsAlpha, fadeInterpolator);
             propertySetter.setViewAlpha(mLauncher.getWorkspace().getPageIndicator(),
                     hotseatIconsAlpha, fadeInterpolator);
         }
@@ -105,6 +118,11 @@
         propertySetter.setFloat(mWorkspace, View.TRANSLATION_Y,
                 scaleAndTranslation[2], translationInterpolator);
 
+        propertySetter.setFloat(hotseat, View.TRANSLATION_Y,
+                hotseatScaleAndTranslation[2], translationInterpolator);
+        propertySetter.setFloat(mWorkspace.getPageIndicator(), View.TRANSLATION_Y,
+                hotseatScaleAndTranslation[2], translationInterpolator);
+
         // Set scrim
         WorkspaceAndHotseatScrim scrim = mLauncher.getDragLayer().getScrim();
         propertySetter.setFloat(scrim, SCRIM_PROGRESS, state.getWorkspaceScrimAlpha(mLauncher),
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index bcb5eec..93bf69d 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -124,12 +124,6 @@
         float shiftCurrent = progress * mShiftRange;
 
         mAppsView.setTranslationY(shiftCurrent);
-        float hotseatTranslation = -mShiftRange + shiftCurrent;
-
-        if (!mIsVerticalLayout) {
-            mLauncher.getHotseat().setTranslationY(hotseatTranslation);
-            mLauncher.getWorkspace().getPageIndicator().setTranslationY(hotseatTranslation);
-        }
 
         // Use a light system UI (dark icons) if all apps is behind at least half of the
         // status bar.
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 882529d..d6e450a 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -101,6 +101,10 @@
     public static final TogglableFlag QUICKSTEP_SPRINGS = new TogglableFlag("QUICKSTEP_SPRINGS",
             false, "Enable springs for quickstep animations");
 
+    public static final TogglableFlag ADAPTIVE_ICON_WINDOW_ANIM = new TogglableFlag(
+            "ADAPTIVE_ICON_WINDOW_ANIM", true,
+            "Use adaptive icons for window animations.");
+
     public static final TogglableFlag ENABLE_QUICKSTEP_LIVE_TILE = new TogglableFlag(
             "ENABLE_QUICKSTEP_LIVE_TILE", false, "Enable live tile in Quickstep overview");
 
@@ -112,6 +116,10 @@
             "ENABLE_HINTS_IN_OVERVIEW", false,
             "Show chip hints and gleams on the overview screen");
 
+    public static final TogglableFlag ENABLE_ASSISTANT_GESTURE = new ToggleableGlobalSettingsFlag(
+            "ENABLE_ASSISTANT_GESTURE", false,
+            "Enable swipe up from the bottom right corner to start assistant");
+
     public static void initialize(Context context) {
         // Avoid the disk read for user builds
         if (Utilities.IS_DEBUG_DEVICE) {
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 8f223a3..bdbea29 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -24,7 +24,6 @@
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.annotation.TargetApi;
-import android.content.pm.LauncherActivityInfo;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -54,18 +53,12 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.FirstFrameAnimatorHelper;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.ShortcutConfigActivityInfo;
 import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
-import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.Thunk;
-import com.android.launcher3.widget.PendingAddShortcutInfo;
 
 import java.util.Arrays;
-import java.util.List;
 
 import androidx.dynamicanimation.animation.FloatPropertyCompat;
 import androidx.dynamicanimation.animation.SpringAnimation;
@@ -204,11 +197,12 @@
             public void run() {
                 LauncherAppState appState = LauncherAppState.getInstance(mLauncher);
                 Object[] outObj = new Object[1];
-                Drawable dr = getFullDrawable(info, appState, outObj);
+                int w = mBitmap.getWidth();
+                int h = mBitmap.getHeight();
+                Drawable dr = Utilities.getFullDrawable(mLauncher, info, w, h,
+                        false /* flattenDrawable */, outObj);
 
                 if (dr instanceof AdaptiveIconDrawable) {
-                    int w = mBitmap.getWidth();
-                    int h = mBitmap.getHeight();
                     int blurMargin = (int) mLauncher.getResources()
                             .getDimension(R.dimen.blur_size_medium_outline) / 2;
 
@@ -314,49 +308,6 @@
     }
 
     /**
-     * Returns the full drawable for {@param info}.
-     * @param outObj this is set to the internal data associated with {@param info},
-     *               eg {@link LauncherActivityInfo} or {@link ShortcutInfoCompat}.
-     */
-    private Drawable getFullDrawable(ItemInfo info, LauncherAppState appState, Object[] outObj) {
-        if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
-            LauncherActivityInfo activityInfo = LauncherAppsCompat.getInstance(mLauncher)
-                    .resolveActivity(info.getIntent(), info.user);
-            outObj[0] = activityInfo;
-            return (activityInfo != null) ? appState.getIconCache()
-                    .getFullResIcon(activityInfo, false) : null;
-        } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
-            if (info instanceof PendingAddShortcutInfo) {
-                ShortcutConfigActivityInfo activityInfo =
-                        ((PendingAddShortcutInfo) info).activityInfo;
-                outObj[0] = activityInfo;
-                return activityInfo.getFullResIcon(appState.getIconCache());
-            }
-            ShortcutKey key = ShortcutKey.fromItemInfo(info);
-            DeepShortcutManager sm = DeepShortcutManager.getInstance(mLauncher);
-            List<ShortcutInfoCompat> si = sm.queryForFullDetails(
-                    key.componentName.getPackageName(), Arrays.asList(key.getId()), key.user);
-            if (si.isEmpty()) {
-                return null;
-            } else {
-                outObj[0] = si.get(0);
-                return sm.getShortcutIconDrawable(si.get(0),
-                        appState.getInvariantDeviceProfile().fillResIconDpi);
-            }
-        } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
-            FolderAdaptiveIcon icon =  FolderAdaptiveIcon.createFolderAdaptiveIcon(
-                    mLauncher, info.id, new Point(mBitmap.getWidth(), mBitmap.getHeight()));
-            if (icon == null) {
-                return null;
-            }
-            outObj[0] = icon;
-            return icon;
-        } else {
-            return null;
-        }
-    }
-
-    /**
      * For apps icons and shortcut icons that have badges, this method creates a drawable that can
      * later on be rendered on top of the layers for the badges. For app icons, work profile badges
      * can only be applied. For deep shortcuts, when dragged from the pop up container, there's no
diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
index 794ab4e..5e41bb7 100644
--- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
+++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
@@ -36,6 +36,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.folder.PreviewBackground;
+import com.android.launcher3.graphics.ShiftedBitmapDrawable;
 import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.util.Preconditions;
 
@@ -133,39 +134,4 @@
 
         return new FolderAdaptiveIcon(new ColorDrawable(bg.getBgColor()), foreground, badge, mask);
     }
-
-    /**
-     * A simple drawable which draws a bitmap at a fixed position irrespective of the bounds
-     */
-    private static class ShiftedBitmapDrawable extends Drawable {
-
-        private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
-        private final Bitmap mBitmap;
-        private final float mShiftX;
-        private final float mShiftY;
-
-        ShiftedBitmapDrawable(Bitmap bitmap, float shiftX, float shiftY) {
-            mBitmap = bitmap;
-            mShiftX = shiftX;
-            mShiftY = shiftY;
-        }
-
-        @Override
-        public void draw(Canvas canvas) {
-            canvas.drawBitmap(mBitmap, mShiftX, mShiftY, mPaint);
-        }
-
-        @Override
-        public void setAlpha(int i) { }
-
-        @Override
-        public void setColorFilter(ColorFilter colorFilter) {
-            mPaint.setColorFilter(colorFilter);
-        }
-
-        @Override
-        public int getOpacity() {
-            return PixelFormat.TRANSLUCENT;
-        }
-    }
 }
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 6fc81c9..7a14b36 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -74,6 +74,7 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.Thunk;
+import com.android.launcher3.views.ClipPathView;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 
 import java.util.ArrayList;
@@ -84,7 +85,7 @@
 /**
  * Represents a set of icons chosen by the user or generated by the system.
  */
-public class Folder extends AbstractFloatingView implements DragSource,
+public class Folder extends AbstractFloatingView implements ClipPathView, DragSource,
         View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener,
         View.OnFocusChangeListener, DragListener, ExtendedEditText.OnBackKeyListener {
     private static final String TAG = "Launcher.Folder";
@@ -1460,6 +1461,7 @@
      * Alternative to using {@link #getClipToOutline()} as it only works with derivatives of
      * rounded rect.
      */
+    @Override
     public void setClipPath(Path clipPath) {
         mClipPath = clipPath;
         invalidate();
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 30dbe69..67495ea 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -189,6 +189,10 @@
         return icon;
     }
 
+    public void getPreviewBounds(Rect outBounds) {
+        mBackground.getBounds(outBounds);
+    }
+
     public Folder getFolder() {
         return mFolder;
     }
diff --git a/src/com/android/launcher3/folder/FolderShape.java b/src/com/android/launcher3/folder/FolderShape.java
index 61db6ff..ec6078e 100644
--- a/src/com/android/launcher3/folder/FolderShape.java
+++ b/src/com/android/launcher3/folder/FolderShape.java
@@ -39,6 +39,7 @@
 import android.util.SparseArray;
 import android.util.TypedValue;
 import android.util.Xml;
+import android.view.View;
 import android.view.ViewOutlineProvider;
 
 import com.android.launcher3.R;
@@ -46,6 +47,7 @@
 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.ClipPathView;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -74,8 +76,8 @@
 
     public abstract void addShape(Path path, float offsetX, float offsetY, float radius);
 
-    public abstract Animator createRevealAnimator(Folder target, Rect startRect, Rect endRect,
-            float endRadius, boolean isReversed);
+    public abstract <T extends View & ClipPathView> Animator createRevealAnimator(T target,
+            Rect startRect, Rect endRect, float endRadius, boolean isReversed);
 
     @Nullable
     public TypedValue getAttrValue(int attr) {
@@ -88,8 +90,8 @@
     private static abstract class SimpleRectShape extends FolderShape {
 
         @Override
-        public final Animator createRevealAnimator(Folder target, Rect startRect, Rect endRect,
-                float endRadius, boolean isReversed) {
+        public final <T extends View & ClipPathView> Animator createRevealAnimator(T target,
+                Rect startRect, Rect endRect, float endRadius, boolean isReversed) {
             return new RoundedRectRevealOutlineProvider(
                     getStartRadius(startRect), endRadius, startRect, endRect) {
                 @Override
@@ -121,8 +123,8 @@
                 Rect startRect, Rect endRect, float endRadius, Path outPath);
 
         @Override
-        public final Animator createRevealAnimator(Folder target, Rect startRect, Rect endRect,
-                float endRadius, boolean isReversed) {
+        public final <T extends View & ClipPathView> Animator createRevealAnimator(T target,
+                Rect startRect, Rect endRect, float endRadius, boolean isReversed) {
             Path path = new Path();
             AnimatorUpdateListener listener =
                     newUpdateListener(startRect, endRect, endRadius, path);
diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java
index fd4774f..ac908f4 100644
--- a/src/com/android/launcher3/folder/PreviewBackground.java
+++ b/src/com/android/launcher3/folder/PreviewBackground.java
@@ -33,6 +33,7 @@
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.RadialGradient;
+import android.graphics.Rect;
 import android.graphics.Region;
 import android.graphics.Shader;
 import android.util.Property;
@@ -154,6 +155,14 @@
         invalidate();
     }
 
+    void getBounds(Rect outBounds) {
+        int top = basePreviewOffsetY;
+        int left = basePreviewOffsetX;
+        int right = left + previewSize;
+        int bottom = top + previewSize;
+        outBounds.set(left, top, right, bottom);
+    }
+
     int getRadius() {
         return previewSize / 2;
     }
diff --git a/src/com/android/launcher3/graphics/ShiftedBitmapDrawable.java b/src/com/android/launcher3/graphics/ShiftedBitmapDrawable.java
new file mode 100644
index 0000000..52d45bb
--- /dev/null
+++ b/src/com/android/launcher3/graphics/ShiftedBitmapDrawable.java
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+package com.android.launcher3.graphics;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+
+/**
+ * A simple drawable which draws a bitmap at a fixed position irrespective of the bounds
+ */
+public class ShiftedBitmapDrawable extends Drawable {
+
+    private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
+    private final Bitmap mBitmap;
+    private float mShiftX;
+    private float mShiftY;
+
+    public ShiftedBitmapDrawable(Bitmap bitmap, float shiftX, float shiftY) {
+        mBitmap = bitmap;
+        mShiftX = shiftX;
+        mShiftY = shiftY;
+    }
+
+    public float getShiftX() {
+        return mShiftX;
+    }
+
+    public float getShiftY() {
+        return mShiftY;
+    }
+
+    public void setShiftX(float shiftX) {
+        mShiftX = shiftX;
+    }
+
+    public void setShiftY(float shiftY) {
+        mShiftY = shiftY;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        canvas.drawBitmap(mBitmap, mShiftX, mShiftY, mPaint);
+    }
+
+    @Override
+    public void setAlpha(int i) { }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        mPaint.setColorFilter(colorFilter);
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.TRANSLUCENT;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
index 6fac31e2..66f9dbf 100644
--- a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
+++ b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
@@ -16,9 +16,16 @@
 
 package com.android.launcher3.graphics;
 
+import static android.content.Intent.ACTION_SCREEN_OFF;
+import static android.content.Intent.ACTION_USER_PRESENT;
+
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
 
 import android.animation.ObjectAnimator;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -89,6 +96,20 @@
                 }
             };
 
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (ACTION_SCREEN_OFF.equals(action)) {
+                mAnimateScrimOnNextDraw = true;
+            } else if (ACTION_USER_PRESENT.equals(action)) {
+                // ACTION_USER_PRESENT is sent after onStart/onResume. This covers the case where
+                // the user unlocked and the Launcher is not in the foreground.
+                mAnimateScrimOnNextDraw = false;
+            }
+        }
+    };
+
     private static final int DARK_SCRIM_COLOR = 0x55000000;
     private static final int MAX_HOTSEAT_SCRIM_ALPHA = 100;
     private static final int ALPHA_MASK_HEIGHT_DP = 500;
@@ -204,11 +225,20 @@
     public void onViewAttachedToWindow(View view) {
         mWallpaperColorInfo.addOnChangeListener(this);
         onExtractedColorsChanged(mWallpaperColorInfo);
+
+        if (mTopScrim != null) {
+            IntentFilter filter = new IntentFilter(ACTION_SCREEN_OFF);
+            filter.addAction(ACTION_USER_PRESENT); // When the device wakes up + keyguard is gone
+            mRoot.getContext().registerReceiver(mReceiver, filter);
+        }
     }
 
     @Override
     public void onViewDetachedFromWindow(View view) {
         mWallpaperColorInfo.removeOnChangeListener(this);
+        if (mTopScrim != null) {
+            mRoot.getContext().unregisterReceiver(mReceiver);
+        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/logging/EventLogArray.java b/src/com/android/launcher3/logging/EventLogArray.java
new file mode 100644
index 0000000..bfb3792
--- /dev/null
+++ b/src/com/android/launcher3/logging/EventLogArray.java
@@ -0,0 +1,136 @@
+/*
+ * 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.
+ */
+package com.android.launcher3.logging;
+
+
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * A utility class to record and log events. Events are stored in a fixed size array and old logs
+ * are purged as new events come.
+ */
+public class EventLogArray {
+
+    private static final int TYPE_ONE_OFF = 0;
+    private static final int TYPE_FLOAT = 1;
+    private static final int TYPE_INTEGER = 2;
+    private static final int TYPE_BOOL_TRUE = 3;
+    private static final int TYPE_BOOL_FALSE = 4;
+
+    private final String name;
+    private final EventEntry[] logs;
+    private int nextIndex;
+
+    public EventLogArray(String name, int size) {
+        this.name = name;
+        logs = new EventEntry[size];
+        nextIndex = 0;
+    }
+
+    public void addLog(String event) {
+        addLog(TYPE_ONE_OFF, event, 0);
+    }
+
+    public void addLog(String event, int extras) {
+        addLog(TYPE_INTEGER, event, extras);
+    }
+
+    public void addLog(String event, float extras) {
+        addLog(TYPE_FLOAT, event, extras);
+    }
+
+    public void addLog(String event, boolean extras) {
+        addLog(extras ? TYPE_BOOL_TRUE : TYPE_BOOL_FALSE, event, 0);
+    }
+
+    private void addLog(int type, String event, float extras) {
+        // Merge the logs if its a duplicate
+        int last = (nextIndex + logs.length - 1) % logs.length;
+        int secondLast = (nextIndex + logs.length - 2) % logs.length;
+        if (isEntrySame(logs[last], type, event) && isEntrySame(logs[secondLast], type, event)) {
+            logs[last].update(type, event, extras);
+            logs[secondLast].duplicateCount++;
+            return;
+        }
+
+        if (logs[nextIndex] == null) {
+            logs[nextIndex] = new EventEntry();
+        }
+        logs[nextIndex].update(type, event, extras);
+        nextIndex = (nextIndex + 1) % logs.length;
+    }
+
+    public void dump(String prefix, PrintWriter writer) {
+        writer.println(prefix + name + " event history:");
+        SimpleDateFormat sdf = new SimpleDateFormat("  HH:mm:ss.SSSZ  ", Locale.US);
+        Date date = new Date();
+
+        for (int i = 0; i < logs.length; i++) {
+            EventEntry log = logs[(nextIndex + logs.length - i - 1) % logs.length];
+            if (log == null) {
+                continue;
+            }
+            date.setTime(log.time);
+
+            StringBuilder msg = new StringBuilder(prefix).append(sdf.format(date))
+                    .append(log.event);
+            switch (log.type) {
+                case TYPE_BOOL_FALSE:
+                    msg.append(": false");
+                    break;
+                case TYPE_BOOL_TRUE:
+                    msg.append(": true");
+                    break;
+                case TYPE_FLOAT:
+                    msg.append(": ").append(log.extras);
+                    break;
+                case TYPE_INTEGER:
+                    msg.append(": ").append((int) log.extras);
+                    break;
+                default: // fall out
+            }
+            if (log.duplicateCount > 0) {
+                msg.append(" & ").append(log.duplicateCount).append(" similar events");
+            }
+            writer.println(msg);
+        }
+    }
+
+    private boolean isEntrySame(EventEntry entry, int type, String event) {
+        return entry != null && entry.type == type && entry.event.equals(event);
+    }
+
+    /** A single event entry. */
+    private static class EventEntry {
+
+        private int type;
+        private String event;
+        private float extras;
+        private long time;
+        private int duplicateCount;
+
+        public void update(int type, String event, float extras) {
+            this.type = type;
+            this.event = event;
+            this.extras = extras;
+            time = System.currentTimeMillis();
+            duplicateCount = 0;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 7275576..a27e2c2 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -366,13 +366,13 @@
                             ComponentName cn = intent.getComponent();
                             targetPkg = cn == null ? intent.getPackage() : cn.getPackageName();
 
-                            if (!Process.myUserHandle().equals(c.user)) {
+                            if (allUsers.indexOfValue(c.user) < 0) {
                                 if (c.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
-                                    c.markDeleted("Legacy shortcuts are only allowed for default user");
+                                    c.markDeleted("Legacy shortcuts are only allowed for current users");
                                     continue;
                                 } else if (c.restoreFlag != 0) {
                                     // Don't restore items for other profiles.
-                                    c.markDeleted("Restore from managed profile not supported");
+                                    c.markDeleted("Restore from other profiles not supported");
                                     continue;
                                 }
                             }
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index bcca4d8..040b5e5 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -18,10 +18,15 @@
 
 import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
 
+import android.app.backup.BackupManager;
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
+import android.os.Build;
+import android.os.UserHandle;
+import android.util.LongSparseArray;
+import android.util.SparseLongArray;
 
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherProvider.DatabaseHelper;
@@ -48,10 +53,10 @@
     private static final String INFO_COLUMN_NAME = "name";
     private static final String INFO_COLUMN_DEFAULT_VALUE = "dflt_value";
 
-    public static boolean performRestore(DatabaseHelper helper) {
+    public static boolean performRestore(DatabaseHelper helper, BackupManager backupManager) {
         SQLiteDatabase db = helper.getWritableDatabase();
         try (SQLiteTransaction t = new SQLiteTransaction(db)) {
-            new RestoreDbTask().sanitizeDB(helper, db);
+            new RestoreDbTask().sanitizeDB(helper, db, backupManager);
             t.commit();
             return true;
         } catch (Exception e) {
@@ -62,20 +67,44 @@
 
     /**
      * Makes the following changes in the provider DB.
-     *   1. Removes all entries belonging to a managed profile as managed profiles
-     *      cannot be restored.
+     *   1. Removes all entries belonging to any profiles that were not restored.
      *   2. Marks all entries as restored. The flags are updated during first load or as
      *      the restored apps get installed.
-     *   3. If the user serial for primary profile is different than that of the previous device,
-     *      update the entries to the new profile id.
+     *   3. If the user serial for any restored profile is different than that of the previous
+     *      device, update the entries to the new profile id.
      */
-    private void sanitizeDB(DatabaseHelper helper, SQLiteDatabase db) throws Exception {
+    private void sanitizeDB(DatabaseHelper helper, SQLiteDatabase db, BackupManager backupManager)
+            throws Exception {
+        // Primary user ids
+        long myProfileId = helper.getDefaultUserSerial();
         long oldProfileId = getDefaultProfileId(db);
-        // Delete all entries which do not belong to the main user
-        int itemsDeleted = db.delete(
-                Favorites.TABLE_NAME, "profileId != ?", new String[]{Long.toString(oldProfileId)});
+        LongSparseArray<Long> oldManagedProfileIds = getManagedProfileIds(db, oldProfileId);
+        LongSparseArray<Long> profileMapping = new LongSparseArray<>(oldManagedProfileIds.size()
+                + 1);
+
+        // Build mapping of restored profile ids to their new profile ids.
+        profileMapping.put(oldProfileId, myProfileId);
+        for (int i = oldManagedProfileIds.size() - 1; i >= 0; --i) {
+            long oldManagedProfileId = oldManagedProfileIds.keyAt(i);
+            UserHandle user = getUserForAncestralSerialNumber(backupManager, oldManagedProfileId);
+            if (user != null) {
+                long newManagedProfileId = helper.getSerialNumberForUser(user);
+                profileMapping.put(oldManagedProfileId, newManagedProfileId);
+            }
+        }
+
+        // Delete all entries which do not belong to any restored profile(s).
+        int numProfiles = profileMapping.size();
+        String[] profileIds = new String[numProfiles];
+        profileIds[0] = Long.toString(oldProfileId);
+        StringBuilder whereClause = new StringBuilder("profileId != ?");
+        for (int i = profileMapping.size() - 1; i >= 1; --i) {
+            whereClause.append(" AND profileId != ?");
+            profileIds[i] = Long.toString(profileMapping.keyAt(i));
+        }
+        int itemsDeleted = db.delete(Favorites.TABLE_NAME, whereClause.toString(), profileIds);
         if (itemsDeleted > 0) {
-            FileLog.d(TAG, itemsDeleted + " items belonging to a managed profile, were deleted");
+            FileLog.d(TAG, itemsDeleted + " items from unrestored user(s) were deleted");
         }
 
         // Mark all items as restored.
@@ -85,7 +114,7 @@
                 | (keepAllIcons ? ShortcutInfo.FLAG_RESTORE_STARTED : 0));
         db.update(Favorites.TABLE_NAME, values, null, null);
 
-        // Mark widgets with appropriate restore flag
+        // Mark widgets with appropriate restore flag.
         values.put(Favorites.RESTORED,  LauncherAppWidgetInfo.FLAG_ID_NOT_VALID |
                 LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY |
                 LauncherAppWidgetInfo.FLAG_UI_NOT_READY |
@@ -93,21 +122,46 @@
         db.update(Favorites.TABLE_NAME, values, "itemType = ?",
                 new String[]{Integer.toString(Favorites.ITEM_TYPE_APPWIDGET)});
 
-        long myProfileId = helper.getDefaultUserSerial();
+        // Migrate ids. To avoid any overlap, we initially move conflicting ids to a temp location.
+        // Using Long.MIN_VALUE since profile ids can not be negative, so there will be no overlap.
+        final long tempLocationOffset = Long.MIN_VALUE;
+        SparseLongArray tempMigratedIds = new SparseLongArray(profileMapping.size());
+        int numTempMigrations = 0;
+        for (int i = profileMapping.size() - 1; i >= 0; --i) {
+            long oldId = profileMapping.keyAt(i);
+            long newId = profileMapping.valueAt(i);
+
+            if (oldId != newId) {
+                if (profileMapping.indexOfKey(newId) >= 0) {
+                    tempMigratedIds.put(numTempMigrations, newId);
+                    numTempMigrations++;
+                    newId = tempLocationOffset + newId;
+                }
+                migrateProfileId(db, oldId, newId);
+            }
+        }
+
+        // Migrate ids from their temporary id to their actual final id.
+        for (int i = tempMigratedIds.size() - 1; i >= 0; --i) {
+            long newId = tempMigratedIds.valueAt(i);
+            migrateProfileId(db, tempLocationOffset + newId, newId);
+        }
+
         if (myProfileId != oldProfileId) {
-            FileLog.d(TAG, "Changing primary user id from " + oldProfileId + " to " + myProfileId);
-            migrateProfileId(db, myProfileId);
+            changeDefaultColumn(db, myProfileId);
         }
     }
 
     /**
-     * Updates profile id of all entries and changes the default value for the column.
+     * Updates profile id of all entries from {@param oldProfileId} to {@param newProfileId}.
      */
-    protected void migrateProfileId(SQLiteDatabase db, long newProfileId) {
+    protected void migrateProfileId(SQLiteDatabase db, long oldProfileId, long newProfileId) {
+        FileLog.d(TAG, "Changing profile user id from " + oldProfileId + " to " + newProfileId);
         // Update existing entries.
         ContentValues values = new ContentValues();
         values.put(Favorites.PROFILE_ID, newProfileId);
-        db.update(Favorites.TABLE_NAME, values, null, null);
+        db.update(Favorites.TABLE_NAME, values, "profileId = ?",
+                new String[]{Long.toString(oldProfileId)});
 
         // Change default value of the column.
         db.execSQL("ALTER TABLE favorites RENAME TO favorites_old;");
@@ -116,6 +170,43 @@
         dropTable(db, "favorites_old");
     }
 
+
+    /**
+     * Changes the default value for the column.
+     */
+    protected void changeDefaultColumn(SQLiteDatabase db, long newProfileId) {
+        db.execSQL("ALTER TABLE favorites RENAME TO favorites_old;");
+        Favorites.addTableToDb(db, newProfileId, false);
+        db.execSQL("INSERT INTO favorites SELECT * FROM favorites_old;");
+        dropTable(db, "favorites_old");
+    }
+
+    /**
+     * Returns a list of the managed profile id(s) used in the favorites table of the provided db.
+     */
+    private LongSparseArray<Long> getManagedProfileIds(SQLiteDatabase db, long defaultProfileId) {
+        LongSparseArray<Long> ids = new LongSparseArray<>();
+        try (Cursor c = db.rawQuery("SELECT profileId from favorites WHERE profileId != ? "
+                + "GROUP BY profileId", new String[] {Long.toString(defaultProfileId)})){
+                while (c.moveToNext()) {
+                    ids.put(c.getLong(c.getColumnIndex(Favorites.PROFILE_ID)), null);
+                }
+        }
+        return ids;
+    }
+
+    /**
+     * Returns a UserHandle of a restored managed profile with the given serial number, or null
+     * if none found.
+     */
+    private UserHandle getUserForAncestralSerialNumber(BackupManager backupManager,
+            long ancestralSerialNumber) {
+        if (Build.VERSION.SDK_INT < 29) {
+            return null;
+        }
+        return backupManager.getUserForAncestralSerialNumber(ancestralSerialNumber);
+    }
+
     /**
      * Returns the profile id used in the favorites table of the provided db.
      */
diff --git a/src/com/android/launcher3/states/InternalStateHandler.java b/src/com/android/launcher3/states/InternalStateHandler.java
index d326ff3..c6370c5 100644
--- a/src/com/android/launcher3/states/InternalStateHandler.java
+++ b/src/com/android/launcher3/states/InternalStateHandler.java
@@ -36,7 +36,6 @@
 public abstract class InternalStateHandler extends Binder {
 
     public static final String EXTRA_STATE_HANDLER = "launcher.state_handler";
-    public static final String EXTRA_FROM_HOME_KEY = "android.intent.extra.FROM_HOME_KEY";
 
     private static final Scheduler sScheduler = new Scheduler();
 
@@ -77,10 +76,6 @@
             Launcher launcher, Intent intent, boolean alreadyOnHome, boolean explicitIntent) {
         boolean result = false;
         if (intent != null && intent.getExtras() != null) {
-            // If we know that this the intent comes from pressing Home, defer to the default
-            // processing.
-            if (intent.hasExtra(EXTRA_FROM_HOME_KEY)) return false;
-
             IBinder stateBinder = intent.getExtras().getBinder(EXTRA_STATE_HANDLER);
             if (stateBinder instanceof InternalStateHandler) {
                 InternalStateHandler handler = (InternalStateHandler) stateBinder;
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 35f7f88..fcace98 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -73,6 +73,11 @@
     }
 
     @Override
+    public float[] getHotseatScaleAndTranslation(Launcher launcher) {
+        return new float[] {1, 0, 0};
+    }
+
+    @Override
     public void onStateEnabled(Launcher launcher) {
         Workspace ws = launcher.getWorkspace();
         ws.showPageIndicatorAtCurrentScroll();
diff --git a/src/com/android/launcher3/views/ClipPathView.java b/src/com/android/launcher3/views/ClipPathView.java
new file mode 100644
index 0000000..2152e1d
--- /dev/null
+++ b/src/com/android/launcher3/views/ClipPathView.java
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+package com.android.launcher3.views;
+
+import android.graphics.Path;
+import android.view.View;
+
+/**
+ * Alternative to using {@link View#getClipToOutline()} as it only works with derivatives of
+ * rounded rect.
+ */
+public interface ClipPathView {
+    void setClipPath(Path clipPath);
+}
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 07318c9..ec0462b 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -16,41 +16,91 @@
 package com.android.launcher3.views;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Outline;
+import android.graphics.Path;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.InsettableFrameLayout.LayoutParams;
-import com.android.launcher3.ItemInfoWithIcon;
+import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.graphics.DrawableFactory;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.folder.FolderShape;
+import com.android.launcher3.graphics.ShiftedBitmapDrawable;
+import com.android.launcher3.icons.LauncherIcons;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+
+import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
 
 /**
  * A view that is created to look like another view with the purpose of creating fluid animations.
  */
-public class FloatingIconView extends View implements Animator.AnimatorListener {
+
+public class FloatingIconView extends View implements Animator.AnimatorListener, ClipPathView {
+
+    private static final Rect sTmpRect = new Rect();
 
     private Runnable mStartRunnable;
     private Runnable mEndRunnable;
 
-    public FloatingIconView(Context context) {
-        super(context);
-    }
+    private Drawable mDrawable;
+    private int mOriginalHeight;
+    private final int mBlurSizeOutline;
 
-    public void setRunnables(Runnable startRunnable, Runnable endRunnable) {
-        mStartRunnable = startRunnable;
-        mEndRunnable = endRunnable;
+    private boolean mIsAdaptiveIcon = false;
+
+    private @Nullable Drawable mForeground;
+    private @Nullable Drawable mBackground;
+    private ValueAnimator mRevealAnimator;
+    private final Rect mStartRevealRect = new Rect();
+    private final Rect mEndRevealRect = new Rect();
+    private Path mClipPath;
+    protected final Rect mOutline = new Rect();
+    private final float mTaskCornerRadius;
+
+    private final Rect mFinalDrawableBounds = new Rect();
+    private final Rect mBgDrawableBounds = new Rect();
+    private float mBgDrawableStartScale = 1f;
+
+    private FloatingIconView(Context context) {
+        super(context);
+
+        mBlurSizeOutline = context.getResources().getDimensionPixelSize(
+                R.dimen.blur_size_medium_outline);
+
+        mTaskCornerRadius = 0; // TODO
     }
 
     /**
      * Positions this view to match the size and location of {@param rect}.
+     *
+     * @param alpha The alpha to set this view.
+     * @param progress A value from [0, 1] that represents the animation progress.
+     * @param windowAlphaThreshold The value at which the window alpha is 0.
      */
-    public void update(RectF rect, float alpha) {
+    public void update(RectF rect, float alpha, float progress, float windowAlphaThreshold) {
         setAlpha(alpha);
 
         LayoutParams lp = (LayoutParams) getLayoutParams();
@@ -59,13 +109,44 @@
         setTranslationX(dX);
         setTranslationY(dY);
 
-        float scaleX = rect.width() / (float) getWidth();
-        float scaleY = rect.height() / (float) getHeight();
-        float scale = Math.min(scaleX, scaleY);
+        float scaleX = rect.width() / (float) lp.width;
+        float scaleY = rect.height() / (float) lp.height;
+        float scale = mIsAdaptiveIcon ? Math.max(scaleX, scaleY) : Math.min(scaleX, scaleY);
         setPivotX(0);
         setPivotY(0);
         setScaleX(scale);
         setScaleY(scale);
+
+        // Wait until the window is no longer visible before morphing the icon into its final shape.
+        float shapeRevealProgress = Utilities.mapToRange(Math.max(windowAlphaThreshold, progress),
+                windowAlphaThreshold, 1f, 0f, 1, Interpolators.LINEAR);
+        if (mIsAdaptiveIcon && shapeRevealProgress > 0) {
+            if (mRevealAnimator == null) {
+                mEndRevealRect.set(mOutline);
+                // We play the reveal animation in reverse so that we end with the icon shape.
+                mRevealAnimator = (ValueAnimator) FolderShape.getShape().createRevealAnimator(this,
+                        mStartRevealRect, mEndRevealRect, mTaskCornerRadius / scale, true);
+                mRevealAnimator.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mRevealAnimator = null;
+                    }
+                });
+                mRevealAnimator.start();
+                // We pause here so we can set the current fraction ourselves.
+                mRevealAnimator.pause();
+            }
+
+            float bgScale = shapeRevealProgress + mBgDrawableStartScale * (1 - shapeRevealProgress);
+            setBackgroundDrawableBounds(bgScale);
+
+            mRevealAnimator.setCurrentFraction(shapeRevealProgress);
+            if (Float.compare(shapeRevealProgress, 1f) >= 0f) {
+                mRevealAnimator.end();
+            }
+        }
+        invalidate();
+        invalidateOutline();
     }
 
     @Override
@@ -82,25 +163,17 @@
         }
     }
 
-    @Override
-    public void onAnimationCancel(Animator animator) {
-    }
-
-    @Override
-    public void onAnimationRepeat(Animator animator) {
-    }
-
     /**
      * Sets the size and position of this view to match {@param v}.
      *
      * @param v The view to copy
-     * @param hideOriginal If true, it will hide {@param v} while this view is visible.
      * @param positionOut Rect that will hold the size and position of v.
      */
-    public void matchPositionOf(Launcher launcher, View v, boolean hideOriginal, Rect positionOut) {
+    private void matchPositionOf(Launcher launcher, View v, Rect positionOut) {
         Utilities.getLocationBoundsForView(launcher, v, positionOut);
         final LayoutParams lp = new LayoutParams(positionOut.width(), positionOut.height());
         lp.ignoreInsets = true;
+        mOriginalHeight = lp.height;
 
         // Position the floating view exactly on top of the original
         lp.leftMargin = positionOut.left;
@@ -110,29 +183,191 @@
         // animation frame.
         layout(lp.leftMargin, lp.topMargin, lp.leftMargin + lp.width, lp.topMargin
                 + lp.height);
+    }
 
-        if (v instanceof BubbleTextView && v.getTag() instanceof ItemInfoWithIcon ) {
-            // Create a copy of the app icon
-            setBackground(DrawableFactory.INSTANCE.get(launcher)
-                    .newIcon(v.getContext(), (ItemInfoWithIcon) v.getTag()));
+    @WorkerThread
+    private void getIcon(Launcher launcher, View v, ItemInfo info, boolean useDrawableAsIs,
+            float aspectRatio) {
+        final LayoutParams lp = (LayoutParams) getLayoutParams();
+
+        boolean supportsAdaptiveIcons = ADAPTIVE_ICON_WINDOW_ANIM.get() && !useDrawableAsIs
+                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
+        if (!supportsAdaptiveIcons && v instanceof BubbleTextView) {
+            // Similar to DragView, we simply use the BubbleTextView icon here.
+            mDrawable = ((BubbleTextView) v).getIcon();
+        }
+        if (mDrawable == null) {
+            mDrawable = Utilities.getFullDrawable(launcher, info, lp.width, lp.height,
+                    useDrawableAsIs, new Object[1]);
+        }
+
+        if (supportsAdaptiveIcons && mDrawable instanceof AdaptiveIconDrawable) {
+            mIsAdaptiveIcon = true;
+
+            AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) mDrawable;
+            Drawable background = adaptiveIcon.getBackground();
+            if (background == null) {
+                background = new ColorDrawable(Color.TRANSPARENT);
+            }
+            mBackground = background;
+            Drawable foreground = adaptiveIcon.getForeground();
+            if (foreground == null) {
+                foreground = new ColorDrawable(Color.TRANSPARENT);
+            }
+            mForeground = foreground;
+
+            int offset = getOffsetForAdaptiveIconBounds();
+            mFinalDrawableBounds.set(offset, offset, lp.width - offset, mOriginalHeight - offset);
+            if (mForeground instanceof ShiftedBitmapDrawable && v instanceof FolderIcon) {
+                ShiftedBitmapDrawable sbd = (ShiftedBitmapDrawable) mForeground;
+                ((FolderIcon) v).getPreviewBounds(sTmpRect);
+                sbd.setShiftX(sbd.getShiftX() - sTmpRect.left);
+                sbd.setShiftY(sbd.getShiftY() - sTmpRect.top);
+            }
+            mForeground.setBounds(mFinalDrawableBounds);
+            mBackground.setBounds(mFinalDrawableBounds);
+
+            int blurMargin = mBlurSizeOutline / 2;
+            mStartRevealRect.set(blurMargin, blurMargin , lp.width - blurMargin,
+                    mOriginalHeight - blurMargin);
+
+            if (aspectRatio > 0) {
+                lp.height = (int) Math.max(lp.height, lp.width * aspectRatio);
+                layout(lp.leftMargin, lp.topMargin, lp.leftMargin + lp.width, lp.topMargin
+                        + lp.height);
+            }
+            mBgDrawableStartScale = (float) lp.height / mOriginalHeight;
+            setBackgroundDrawableBounds(mBgDrawableStartScale);
+
+            // Set up outline
+            mOutline.set(0, 0, lp.width, lp.height);
+            setOutlineProvider(new ViewOutlineProvider() {
+                @Override
+                public void getOutline(View view, Outline outline) {
+                    outline.setRoundRect(mOutline, mTaskCornerRadius);
+                }
+            });
+            setClipToOutline(true);
+        } else {
+            setBackground(mDrawable);
+        }
+
+        new Handler(Looper.getMainLooper()).post(() -> {
+            invalidate();
+            invalidateOutline();
+        });
+    }
+
+    private void setBackgroundDrawableBounds(float scale) {
+        mBgDrawableBounds.set(mFinalDrawableBounds);
+        Utilities.scaleRectAboutCenter(mBgDrawableBounds, scale);
+        // Since the drawable is at the top of the view, we need to offset to keep it centered.
+        mBgDrawableBounds.offsetTo(mBgDrawableBounds.left,
+                (int) (mFinalDrawableBounds.top * scale));
+        mBackground.setBounds(mBgDrawableBounds);
+    }
+
+    private int getOffsetForAdaptiveIconBounds() {
+        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O ||
+                !(mDrawable instanceof AdaptiveIconDrawable)) {
+            return 0;
+        }
+
+        final LayoutParams lp = (LayoutParams) getLayoutParams();
+        Rect bounds = new Rect(0, 0, lp.width + mBlurSizeOutline, lp.height + mBlurSizeOutline);
+        bounds.inset(mBlurSizeOutline / 2, mBlurSizeOutline / 2);
+
+        try (LauncherIcons li = LauncherIcons.obtain(Launcher.fromContext(getContext()))) {
+            Utilities.scaleRectAboutCenter(bounds, li.getNormalizer().getScale(mDrawable, null));
+        }
+
+        bounds.inset(
+                (int) (-bounds.width() * AdaptiveIconDrawable.getExtraInsetFraction()),
+                (int) (-bounds.height() * AdaptiveIconDrawable.getExtraInsetFraction())
+        );
+
+        return bounds.left;
+    }
+
+    @Override
+    public void setClipPath(Path clipPath) {
+        mClipPath = clipPath;
+        invalidate();
+    }
+
+    private void drawAdaptiveIconIfExists(Canvas canvas) {
+        if (mBackground != null) {
+            mBackground.draw(canvas);
+        }
+        if (mForeground != null) {
+            mForeground.draw(canvas);
+        }
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (mClipPath == null) {
+            super.draw(canvas);
+            drawAdaptiveIconIfExists(canvas);
+        } else {
+            int count = canvas.save();
+            canvas.clipPath(mClipPath);
+            super.draw(canvas);
+            drawAdaptiveIconIfExists(canvas);
+            canvas.restoreToCount(count);
+        }
+    }
+
+    @Override
+    public void onAnimationCancel(Animator animator) {}
+
+    @Override
+    public void onAnimationRepeat(Animator animator) {}
+
+    /**
+     * Creates a floating icon view for {@param originalView}.
+     *
+     * @param originalView The view to copy
+     * @param hideOriginal If true, it will hide {@param originalView} while this view is visible.
+     * @param useDrawableAsIs If true, we do not separate the foreground/background of adaptive
+     * icons. TODO(b/122843905): We can remove this once app opening uses new animation.
+     * @param aspectRatio If >= 0, we will use this aspect ratio for the initial adaptive icon size.
+     * @param positionOut Rect that will hold the size and position of v.
+     */
+    public static FloatingIconView getFloatingIconView(Launcher launcher, View originalView,
+            boolean hideOriginal, boolean useDrawableAsIs, float aspectRatio, Rect positionOut,
+            FloatingIconView recycle) {
+        FloatingIconView view = recycle != null ? recycle : new FloatingIconView(launcher);
+
+        // Match the position of the original view.
+        view.matchPositionOf(launcher, originalView, positionOut);
+
+        // Get the drawable on the background thread
+        // Must be called after matchPositionOf so that we know what size to load.
+        if (originalView.getTag() instanceof ItemInfo) {
+            new Handler(LauncherModel.getWorkerLooper()).postAtFrontOfQueue(() -> {
+                view.getIcon(launcher, originalView, (ItemInfo) originalView.getTag(),
+                        useDrawableAsIs, aspectRatio);
+            });
         }
 
         // We need to add it to the overlay, but keep it invisible until animation starts..
         final DragLayer dragLayer = launcher.getDragLayer();
-        setVisibility(INVISIBLE);
-        ((ViewGroup) dragLayer.getParent()).getOverlay().add(this);
+        view.setVisibility(INVISIBLE);
+        ((ViewGroup) dragLayer.getParent()).getOverlay().add(view);
 
-        setRunnables(() -> {
-                    setVisibility(VISIBLE);
-                    if (hideOriginal) {
-                        v.setVisibility(INVISIBLE);
-                    }
-                },
-                () -> {
-                    ((ViewGroup) dragLayer.getParent()).getOverlay().remove(this);
-                    if (hideOriginal) {
-                        v.setVisibility(VISIBLE);
-                    }
-                });
+        view.mStartRunnable = () -> {
+            view.setVisibility(VISIBLE);
+            if (hideOriginal) {
+                originalView.setVisibility(INVISIBLE);
+            }
+        };
+        view.mEndRunnable = () -> {
+            ((ViewGroup) dragLayer.getParent()).getOverlay().remove(view);
+            if (hideOriginal) {
+                originalView.setVisibility(VISIBLE);
+            }
+        };
+        return view;
     }
 }
diff --git a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
index babb731..80facc6 100644
--- a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
+++ b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
@@ -41,11 +41,27 @@
         // Verify item add
         assertEquals(5, getCount(db, "select * from favorites where profileId = 42"));
 
-        new RestoreDbTask().migrateProfileId(db, 33);
+        new RestoreDbTask().migrateProfileId(db, 42, 33);
 
         // verify data migrated
         assertEquals(0, getCount(db, "select * from favorites where profileId = 42"));
         assertEquals(5, getCount(db, "select * from favorites where profileId = 33"));
+    }
+
+    @Test
+    public void testChangeDefaultColumn() throws Exception {
+        SQLiteDatabase db = new MyDatabaseHelper(42).getWritableDatabase();
+        // Add some dummy data
+        for (int i = 0; i < 5; i++) {
+            ContentValues values = new ContentValues();
+            values.put(Favorites._ID, i);
+            values.put(Favorites.TITLE, "item " + i);
+            db.insert(Favorites.TABLE_NAME, null, values);
+        }
+        // Verify item add
+        assertEquals(5, getCount(db, "select * from favorites where profileId = 42"));
+
+        new RestoreDbTask().changeDefaultColumn(db, 33);
 
         // Verify default value changed
         ContentValues values = new ContentValues();
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 02f5502..dd768fd 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -98,6 +98,10 @@
         }
         if (TestHelpers.isInLauncherProcess()) Utilities.enableRunningInTestHarnessForTests();
         mLauncher = new LauncherInstrumentation(instrumentation);
+        try {
+            mDevice.executeShellCommand("settings delete secure assistant");
+        } catch (IOException e) {
+        }
     }
 
     @Rule
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 82ea8be..1353a23 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -29,7 +29,7 @@
 public class AllApps extends LauncherInstrumentation.VisibleContainer {
     private static final int MAX_SCROLL_ATTEMPTS = 40;
     private static final int MIN_INTERACT_SIZE = 100;
-    private static final int FLING_SPEED = 12000;
+    private static final int FLING_SPEED = 3000;
 
     private final int mHeight;
 
@@ -120,7 +120,7 @@
         final UiObject2 allAppsContainer = verifyActiveContainer();
         // Start the gesture in the center to avoid starting at elements near the top.
         allAppsContainer.setGestureMargins(0, 0, 0, mHeight / 2);
-        allAppsContainer.fling(Direction.DOWN, FLING_SPEED);
+        allAppsContainer.fling(Direction.DOWN, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
         verifyActiveContainer();
     }
 
@@ -131,7 +131,7 @@
         final UiObject2 allAppsContainer = verifyActiveContainer();
         // Start the gesture in the center, for symmetry with forward.
         allAppsContainer.setGestureMargins(0, mHeight / 2, 0, 0);
-        allAppsContainer.fling(Direction.UP, FLING_SPEED);
+        allAppsContainer.fling(Direction.UP, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
         verifyActiveContainer();
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index 0ff3070..6e92dad 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -28,7 +28,7 @@
  * Common overview pane for both Launcher and fallback recents
  */
 public class BaseOverview extends LauncherInstrumentation.VisibleContainer {
-    private static final int DEFAULT_FLING_SPEED = 15000;
+    private static final int FLING_SPEED = 1500;
     private static final int FLINGS_FOR_DISMISS_LIMIT = 5;
 
     BaseOverview(LauncherInstrumentation launcher) {
@@ -46,7 +46,7 @@
     public void flingForward() {
         final UiObject2 overview = verifyActiveContainer();
         LauncherInstrumentation.log("Overview.flingForward before fling");
-        overview.fling(Direction.LEFT, DEFAULT_FLING_SPEED);
+        overview.fling(Direction.LEFT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
         mLauncher.waitForIdle();
         verifyActiveContainer();
     }
@@ -73,7 +73,7 @@
     public void flingBackward() {
         final UiObject2 overview = verifyActiveContainer();
         LauncherInstrumentation.log("Overview.flingBackward before fling");
-        overview.fling(Direction.RIGHT, DEFAULT_FLING_SPEED);
+        overview.fling(Direction.RIGHT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
         mLauncher.waitForIdle();
         verifyActiveContainer();
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 7473189..93b4cc6 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -93,8 +93,6 @@
     private static WeakReference<VisibleContainer> sActiveContainer = new WeakReference<>(null);
 
     private final UiDevice mDevice;
-    private final boolean mSwipeUpEnabled;
-    private Boolean mSwipeUpEnabledOverride = null;
     private final Instrumentation mInstrumentation;
     private int mExpectedRotation = Surface.ROTATION_0;
 
@@ -104,13 +102,6 @@
     public LauncherInstrumentation(Instrumentation instrumentation) {
         mInstrumentation = instrumentation;
         mDevice = UiDevice.getInstance(instrumentation);
-        final boolean swipeUpEnabledDefaultValue = SwipeUpSetting.isSwipeUpEnabledDefaultValue();
-        mSwipeUpEnabled = SwipeUpSetting.isSwipeUpSettingAvailable() ?
-                Settings.Secure.getInt(
-                        instrumentation.getTargetContext().getContentResolver(),
-                        SWIPE_UP_SETTING_NAME,
-                        swipeUpEnabledDefaultValue ? 1 : 0) == 1 :
-                swipeUpEnabledDefaultValue;
 
         // Launcher should run in test harness so that custom accessibility protocol between
         // Launcher and TAPL is enabled. In-process tests enable this protocol with a direct call
@@ -119,17 +110,18 @@
                 TestHelpers.isInLauncherProcess() || ActivityManager.isRunningInTestHarness());
     }
 
-    // Used only by TaplTests.
-    public void overrideSwipeUpEnabled(Boolean swipeUpEnabledOverride) {
-        mSwipeUpEnabledOverride = swipeUpEnabledOverride;
-    }
-
     void setActiveContainer(VisibleContainer container) {
         sActiveContainer = new WeakReference<>(container);
     }
 
     public boolean isSwipeUpEnabled() {
-        return mSwipeUpEnabledOverride != null ? mSwipeUpEnabledOverride : mSwipeUpEnabled;
+        final boolean swipeUpEnabledDefaultValue = SwipeUpSetting.isSwipeUpEnabledDefaultValue();
+        return SwipeUpSetting.isSwipeUpSettingAvailable() ?
+                Settings.Secure.getInt(
+                        mInstrumentation.getTargetContext().getContentResolver(),
+                        SWIPE_UP_SETTING_NAME,
+                        swipeUpEnabledDefaultValue ? 1 : 0) == 1 :
+                swipeUpEnabledDefaultValue;
     }
 
     static void log(String message) {
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 77555a3..89affd1 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -23,7 +23,7 @@
  * All widgets container.
  */
 public final class Widgets extends LauncherInstrumentation.VisibleContainer {
-    private static final int FLING_SPEED = 6000;
+    private static final int FLING_SPEED = 1500;
 
     Widgets(LauncherInstrumentation launcher) {
         super(launcher);
@@ -36,7 +36,7 @@
     public void flingForward() {
         final UiObject2 widgetsContainer = verifyActiveContainer();
         widgetsContainer.setGestureMargin(100);
-        widgetsContainer.fling(Direction.DOWN, FLING_SPEED);
+        widgetsContainer.fling(Direction.DOWN, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
         verifyActiveContainer();
     }
 
@@ -46,7 +46,7 @@
     public void flingBackward() {
         final UiObject2 widgetsContainer = verifyActiveContainer();
         widgetsContainer.setGestureMargin(100);
-        widgetsContainer.fling(Direction.UP, FLING_SPEED);
+        widgetsContainer.fling(Direction.UP, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
         mLauncher.waitForIdle();
         verifyActiveContainer();
     }