Merge "Add AiAi overview chips to Launcher" into ub-launcher3-master
diff --git a/Android.bp b/Android.bp
index c583244..5688407 100644
--- a/Android.bp
+++ b/Android.bp
@@ -13,19 +13,54 @@
 // limitations under the License.
 
 java_library_static {
+    name: "launcher-log-proto-nano",
+    proto: {
+        type: "nano",
+	output_params: [
+	    "store_unknown_fields=true",
+	    "enum_style=java",
+	],
+	local_include_dirs: [
+	    "protos",
+	    "proto_overrides",
+	],
+    },
+    srcs: [
+        "protos/**/*.proto",
+        "proto_overrides/**/*.proto",
+    ],
+}
+
+android_library{
     name: "launcher-aosp-tapl",
     static_libs: [
         "androidx.annotation_annotation",
+        "androidx.dynamicanimation_dynamicanimation",
+        "androidx.recyclerview_recyclerview",
+        "androidx.preference_preference",
         "androidx.test.runner",
         "androidx.test.rules",
         "androidx.test.uiautomator_uiautomator",
+        "iconloader_base",
+        "launcher-log-proto-nano",
+        "launcherprotosnano",
         "SystemUISharedLib",
     ],
     srcs: [
         "tests/tapl/**/*.java",
-        "quickstep/src/com/android/quickstep/SwipeUpSetting.java",
-        "src/com/android/launcher3/util/SecureSettingsObserver.java",
-        "src/com/android/launcher3/TestProtocol.java",
+        "quickstep/src/**/*.java",
+        "quickstep/recents_ui_overrides/src/**/*.java",
+        "src/**/*.java",
+        "src_build_config/**/*.java",
+        "src_flags/**/*.java",
+        "src_plugins/**/*.java",
+        "src_shortcuts_overrides/**/*.java",
+    ],
+    dxflags: ["--multi-dex"],
+    resource_dirs: [
+        "res",
+        "quickstep/res",
+        "quickstep/recents_ui_overrides/res",
     ],
     platform_apis: true,
 }
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/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..2b76924 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
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..50cc4da 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);
 
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..e02c696
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java
@@ -0,0 +1,195 @@
+/*
+ * 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 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/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
index ffd3b4b..9650a53 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -100,17 +100,19 @@
     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) {
+            ComponentName component = runningTaskView.getTask().key.sourceComponent;
+            workspaceView = activity.getWorkspace().getFirstMatchForAppClose(component);
+        } 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/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index cf50fc1..d1d0e86 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,7 @@
 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.util.LooperExecutor;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.systemui.shared.recents.IOverviewProxy;
@@ -307,6 +308,10 @@
 
         if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher) {
             return InputConsumer.NO_OP;
+        } else if (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(
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..fa827e7 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -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() {
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/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/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 2626481..f7126d0 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -65,4 +65,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/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/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..d65fe76 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -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
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 832e9c9..c3edfe5 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 {
@@ -595,6 +607,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,
+            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, 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(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/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 882529d..8c57f46 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", false,
+            "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 TogglableFlag(
+            "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..8a27f9d 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,11 @@
             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, 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 +307,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/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/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/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/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/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..2b9e7b6 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -16,41 +16,86 @@
 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.FolderShape;
+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 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 final float mBgDrawableStartScale = 5f; // Magic number that can be tuned later.
+
+    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 +104,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 +158,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 +178,173 @@
         // 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, ItemInfo info, boolean useDrawableAsIs,
+            float aspectRatio) {
+        final LayoutParams lp = (LayoutParams) getLayoutParams();
+        mDrawable = Utilities.getFullDrawable(launcher, info, lp.width, lp.height, new Object[1]);
+
+        if (ADAPTIVE_ICON_WINDOW_ANIM.get() && !useDrawableAsIs
+                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+                && 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);
+            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);
+                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);
+        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, (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;
     }
 }