Merge "Improving TAPL diagnostics (contains changes in Launcher)" into ub-launcher3-master
diff --git a/Android.bp b/Android.bp
index 1121a79..5acec37 100644
--- a/Android.bp
+++ b/Android.bp
@@ -23,7 +23,6 @@
     ],
     srcs: [
         "tests/tapl/**/*.java",
-        "quickstep/src/com/android/quickstep/SwipeUpSetting.java",
         "src/com/android/launcher3/util/SecureSettingsObserver.java",
         "src/com/android/launcher3/TestProtocol.java",
     ],
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
index 321861b..d9b9686 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
@@ -30,7 +30,7 @@
 import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
 import com.android.launcher3.util.TouchController;
-import com.android.quickstep.OverviewInteractionState;
+import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.views.IconRecentsView;
 
 import java.util.ArrayList;
@@ -52,8 +52,8 @@
             list.add(new LandscapeStatesTouchController(launcher));
             list.add(new LandscapeEdgeSwipeController(launcher));
         } else {
-            boolean allowDragToOverview = OverviewInteractionState.INSTANCE.get(launcher)
-                    .isSwipeUpGestureEnabled();
+            boolean allowDragToOverview = SysUINavigationMode.INSTANCE.get(launcher)
+                    .getMode().hasGestures;
             list.add(new PortraitStatesTouchController(launcher, allowDragToOverview));
         }
         if (FeatureFlags.PULL_DOWN_STATUS_BAR && Utilities.IS_DEBUG_DEVICE
diff --git a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index 051c80f..d1d697c 100644
--- a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -173,8 +173,10 @@
 
         // Keep recents visible throughout the animation.
         SurfaceParams[] params = new SurfaceParams[2];
+        // Closing app should stay on top.
+        int boostedMode = MODE_CLOSING;
         params[0] = new SurfaceParams(recentsTarget.leash, 1f, null /* matrix */,
-                null /* windowCrop */, getLayer(recentsTarget, MODE_OPENING), 0 /* cornerRadius */);
+                null /* windowCrop */, getLayer(recentsTarget, boostedMode), 0 /* cornerRadius */);
 
         valueAnimator.addUpdateListener(new MultiValueUpdateListener() {
             private final FloatProp mScaleX;
@@ -214,7 +216,7 @@
                 m.postTranslate(mTranslationX.value, mTranslationY.value);
 
                 params[1] = new SurfaceParams(appTarget.leash, mAlpha.value, m,
-                        null /* windowCrop */, getLayer(appTarget, MODE_CLOSING),
+                        null /* windowCrop */, getLayer(appTarget, boostedMode),
                         0 /* cornerRadius */);
                 surfaceApplier.scheduleApply(params);
             }
diff --git a/go/quickstep/src/com/android/quickstep/RecentsActivity.java b/go/quickstep/src/com/android/quickstep/RecentsActivity.java
index 447e7e7..f2ca368 100644
--- a/go/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/go/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -67,9 +67,7 @@
 
     @Override
     protected void onStart() {
-        // Set the alpha to 1 before calling super, as it may get set back to 0 due to
-        // onActivityStart callback.
-        mIconRecentsView.setAlpha(0);
+        mIconRecentsView.onBeginTransitionToOverview();
         super.onStart();
     }
 }
diff --git a/go/quickstep/src/com/android/quickstep/TaskActionController.java b/go/quickstep/src/com/android/quickstep/TaskActionController.java
index b2d495b..77b287b 100644
--- a/go/quickstep/src/com/android/quickstep/TaskActionController.java
+++ b/go/quickstep/src/com/android/quickstep/TaskActionController.java
@@ -71,7 +71,6 @@
      * Clears all tasks and updates the model and view.
      */
     public void clearAllTasks() {
-        // TODO: Play an animation so transition is more natural.
         int count = mAdapter.getItemCount();
         ActivityManagerWrapper.getInstance().removeAllRecentTasks();
         mLoader.clearAllTasks();
diff --git a/go/quickstep/src/com/android/quickstep/TaskAdapter.java b/go/quickstep/src/com/android/quickstep/TaskAdapter.java
index e56cc51..c98eca6 100644
--- a/go/quickstep/src/com/android/quickstep/TaskAdapter.java
+++ b/go/quickstep/src/com/android/quickstep/TaskAdapter.java
@@ -75,8 +75,20 @@
             // Task list has updated.
             return;
         }
-        holder.bindTask(tasks.get(position));
-
+        Task task = tasks.get(position);
+        holder.bindTask(task);
+        mLoader.loadTaskIconAndLabel(task, () -> {
+            // Ensure holder still has the same task.
+            if (task.equals(holder.getTask())) {
+                holder.getTaskItemView().setIcon(task.icon);
+                holder.getTaskItemView().setLabel(task.titleDescription);
+            }
+        });
+        mLoader.loadTaskThumbnail(task, () -> {
+            if (task.equals(holder.getTask())) {
+                holder.getTaskItemView().setThumbnail(task.thumbnail.thumbnail);
+            }
+        });
     }
 
     @Override
diff --git a/go/quickstep/src/com/android/quickstep/TaskHolder.java b/go/quickstep/src/com/android/quickstep/TaskHolder.java
index a89229f..744afd7 100644
--- a/go/quickstep/src/com/android/quickstep/TaskHolder.java
+++ b/go/quickstep/src/com/android/quickstep/TaskHolder.java
@@ -35,17 +35,18 @@
         mTaskItemView = itemView;
     }
 
+    public TaskItemView getTaskItemView() {
+        return mTaskItemView;
+    }
+
     /**
-     * Bind task content to the view. This includes the task icon and title as well as binding
-     * input handlers such as which task to launch/remove.
+     * Bind a task to the holder, resetting the view and preparing it for content to load in.
      *
      * @param task the task to bind to the view
      */
     public void bindTask(Task task) {
         mTask = task;
-        mTaskItemView.setLabel(task.titleDescription);
-        mTaskItemView.setIcon(task.icon);
-        mTaskItemView.setThumbnail(task.thumbnail.thumbnail);
+        mTaskItemView.resetTaskItemView();
     }
 
     /**
diff --git a/go/quickstep/src/com/android/quickstep/TaskListLoader.java b/go/quickstep/src/com/android/quickstep/TaskListLoader.java
index c86c24e..1234989 100644
--- a/go/quickstep/src/com/android/quickstep/TaskListLoader.java
+++ b/go/quickstep/src/com/android/quickstep/TaskListLoader.java
@@ -25,7 +25,6 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 
 /**
@@ -39,35 +38,48 @@
 
     private ArrayList<Task> mTaskList = new ArrayList<>();
     private int mTaskListChangeId;
+    private RecentsModel.TaskThumbnailChangeListener listener = (taskId, thumbnailData) -> {
+        Task foundTask = null;
+        for (Task task : mTaskList) {
+            if (task.key.id == taskId) {
+                foundTask = task;
+                break;
+            }
+        }
+        if (foundTask != null) {
+            foundTask.thumbnail = thumbnailData;
+        }
+        return foundTask;
+    };
 
     public TaskListLoader(Context context) {
         mRecentsModel = RecentsModel.INSTANCE.get(context);
+        mRecentsModel.addThumbnailChangeListener(listener);
     }
 
     /**
-     * Returns the current task list as of the last completed load (see
-     * {@link #loadTaskList}) as a read-only list. This list of tasks is guaranteed to always have
-     * all its task content loaded.
+     * Returns the current task list as of the last completed load (see {@link #loadTaskList}) as a
+     * read-only list. This list of tasks is not guaranteed to have all content loaded.
      *
-     * @return the current list of tasks w/ all content loaded
+     * @return the current list of tasks
      */
     public List<Task> getCurrentTaskList() {
         return Collections.unmodifiableList(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.
+     * Fetches the most recent tasks and updates the task list asynchronously. This call does not
+     * provide guarantees the task content (icon, thumbnail, label) are loaded but will fill in
+     * what it has. May run the callback immediately if there have been no changes in the task
+     * list.
      *
-     * @param onTasksLoadedCallback callback for when the tasks are fully loaded. Done on the UI
-     *                              thread
+     * @param onLoadedCallback callback to run when task list is loaded
      */
-    public void loadTaskList(@Nullable Consumer<ArrayList<Task>> onTasksLoadedCallback) {
+    public void loadTaskList(@Nullable Consumer<ArrayList<Task>> onLoadedCallback) {
         if (mRecentsModel.isTaskListValid(mTaskListChangeId)) {
             // Current task list is already up to date. No need to update.
-            if (onTasksLoadedCallback != null) {
-                onTasksLoadedCallback.accept(mTaskList);
+            if (onLoadedCallback != null) {
+                onLoadedCallback.accept(mTaskList);
             }
             return;
         }
@@ -76,16 +88,46 @@
             // 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);
+            for (Task task : tasks) {
+                int loadedPos = mTaskList.indexOf(task);
+                if (loadedPos == -1) {
+                    continue;
                 }
-            });
+                Task loadedTask = mTaskList.get(loadedPos);
+                task.icon = loadedTask.icon;
+                task.titleDescription = loadedTask.titleDescription;
+                task.thumbnail = loadedTask.thumbnail;
+            }
+            mTaskList = tasks;
+            onLoadedCallback.accept(tasks);
         });
     }
 
     /**
+     * Load task icon and label asynchronously if it is not already loaded in the task. If the task
+     * already has an icon, this calls the callback immediately.
+     *
+     * @param task task to update with icon + label
+     * @param onLoadedCallback callback to run when task has icon and label
+     */
+    public void loadTaskIconAndLabel(Task task, @Nullable Runnable onLoadedCallback) {
+        mRecentsModel.getIconCache().updateIconInBackground(task,
+                loadedTask -> onLoadedCallback.run());
+    }
+
+    /**
+     * Load thumbnail asynchronously if not already loaded in the task. If the task already has a
+     * thumbnail or if the thumbnail is cached, this calls the callback immediately.
+     *
+     * @param task task to update with the thumbnail
+     * @param onLoadedCallback callback to run when task has thumbnail
+     */
+    public void loadTaskThumbnail(Task task, @Nullable Runnable onLoadedCallback) {
+        mRecentsModel.getThumbnailCache().updateThumbnailInBackground(task,
+                thumbnail -> onLoadedCallback.run());
+    }
+
+    /**
      * Removes the task from the current task list.
      */
     void removeTask(Task task) {
@@ -98,42 +140,4 @@
     void clearAllTasks() {
         mTaskList.clear();
     }
-
-    /**
-     * Loads task content for a list of tasks, including the label, icon, and thumbnail. For content
-     * that isn't cached, load the content asynchronously in the background.
-     *
-     * @param tasksToLoad list of tasks that need to load their content
-     * @param onFullyLoadedCallback runnable to run after all tasks have loaded their content
-     */
-    private void loadTaskContents(ArrayList<Task> tasksToLoad,
-            @Nullable Runnable onFullyLoadedCallback) {
-        // Make two load requests per task, one for the icon/title and one for the thumbnail.
-        AtomicInteger loadRequestsCount = new AtomicInteger(tasksToLoad.size() * 2);
-        Runnable itemLoadedRunnable = () -> {
-            if (loadRequestsCount.decrementAndGet() == 0 && onFullyLoadedCallback != null) {
-                onFullyLoadedCallback.run();
-            }
-        };
-        for (Task task : tasksToLoad) {
-            // Load icon and title.
-            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;
-                itemLoadedRunnable.run();
-            } else {
-                // Otherwise, load the content in the background.
-                mRecentsModel.getIconCache().updateIconInBackground(task,
-                        loadedTask -> itemLoadedRunnable.run());
-            }
-
-            // Load the thumbnail. May return immediately and synchronously if the thumbnail is
-            // cached.
-            mRecentsModel.getThumbnailCache().updateThumbnailInBackground(task,
-                    thumbnail -> itemLoadedRunnable.run());
-        }
-    }
 }
diff --git a/go/quickstep/src/com/android/quickstep/fallback/GoRecentsActivityRootView.java b/go/quickstep/src/com/android/quickstep/fallback/GoRecentsActivityRootView.java
index d748e89..c0ebcb5 100644
--- a/go/quickstep/src/com/android/quickstep/fallback/GoRecentsActivityRootView.java
+++ b/go/quickstep/src/com/android/quickstep/fallback/GoRecentsActivityRootView.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.util.AttributeSet;
 
+import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.RecentsActivity;
 
@@ -27,5 +28,7 @@
 public final class GoRecentsActivityRootView extends BaseDragLayer<RecentsActivity> {
     public GoRecentsActivityRootView(Context context, AttributeSet attrs) {
         super(context, attrs, 1 /* alphaChannelCount */);
+        // Go leaves touch control to the view itself.
+        mControllers = new TouchController[0];
     }
 }
diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
index a1d62c2..5bb4c5a 100644
--- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
+++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
@@ -19,6 +19,10 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
@@ -65,6 +69,10 @@
                 }
             };
     private static final long CROSSFADE_DURATION = 300;
+    private static final long ITEM_ANIMATE_OUT_DURATION = 150;
+    private static final long ITEM_ANIMATE_OUT_DELAY_BETWEEN = 40;
+    private static final float ITEM_ANIMATE_OUT_TRANSLATION_X_RATIO = .25f;
+    private static final long CLEAR_ALL_FADE_DELAY = 120;
 
     /**
      * A ratio representing the view's relative placement within its padded space. For example, 0
@@ -81,6 +89,7 @@
     private RecyclerView mTaskRecyclerView;
     private View mEmptyView;
     private View mContentView;
+    private View mClearAllView;
     private boolean mTransitionedFromApp;
 
     public IconRecentsView(Context context, AttributeSet attrs) {
@@ -117,12 +126,22 @@
                     updateContentViewVisibility();
                 }
             });
-
-            View clearAllView = findViewById(R.id.clear_all_button);
-            clearAllView.setOnClickListener(v -> mTaskActionController.clearAllTasks());
+            mClearAllView = findViewById(R.id.clear_all_button);
+            mClearAllView.setOnClickListener(v -> animateClearAllTasks());
         }
     }
 
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        super.setEnabled(enabled);
+        TaskItemView[] itemViews = getTaskViews();
+        for (TaskItemView itemView : itemViews) {
+            itemView.setEnabled(enabled);
+        }
+        mClearAllView.setEnabled(enabled);
+    }
+
     /**
      * Set activity helper for the view to callback to.
      *
@@ -136,8 +155,6 @@
      * 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
@@ -195,6 +212,78 @@
     }
 
     /**
+     * Clear all tasks and animate out.
+     */
+    private void animateClearAllTasks() {
+        setEnabled(false);
+        TaskItemView[] itemViews = getTaskViews();
+
+        AnimatorSet clearAnim = new AnimatorSet();
+        long currentDelay = 0;
+
+        // Animate each item view to the right and fade out.
+        for (TaskItemView itemView : itemViews) {
+            PropertyValuesHolder transXproperty = PropertyValuesHolder.ofFloat(TRANSLATION_X,
+                    0, itemView.getWidth() * ITEM_ANIMATE_OUT_TRANSLATION_X_RATIO);
+            PropertyValuesHolder alphaProperty = PropertyValuesHolder.ofFloat(ALPHA, 1.0f, 0f);
+            ObjectAnimator itemAnim = ObjectAnimator.ofPropertyValuesHolder(itemView,
+                    transXproperty, alphaProperty);
+            itemAnim.setDuration(ITEM_ANIMATE_OUT_DURATION);
+            itemAnim.setStartDelay(currentDelay);
+
+            clearAnim.play(itemAnim);
+            currentDelay += ITEM_ANIMATE_OUT_DELAY_BETWEEN;
+        }
+
+        // Animate view fading and leave recents when faded enough.
+        ValueAnimator contentAlpha = ValueAnimator.ofFloat(1.0f, 0f)
+                .setDuration(CROSSFADE_DURATION);
+        contentAlpha.setStartDelay(CLEAR_ALL_FADE_DELAY);
+        contentAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            private boolean mLeftRecents = false;
+
+            @Override
+            public void onAnimationUpdate(ValueAnimator valueAnimator) {
+                mContentView.setAlpha((float) valueAnimator.getAnimatedValue());
+                // Leave recents while fading out.
+                if ((float) valueAnimator.getAnimatedValue() < .5f && !mLeftRecents) {
+                    mActivityHelper.leaveRecents();
+                    mLeftRecents = true;
+                }
+            }
+        });
+
+        clearAnim.play(contentAlpha);
+        clearAnim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                for (TaskItemView itemView : itemViews) {
+                    itemView.setTranslationX(0);
+                    itemView.setAlpha(1.0f);
+                }
+                setEnabled(true);
+                mContentView.setVisibility(GONE);
+                mTaskActionController.clearAllTasks();
+            }
+        });
+        clearAnim.start();
+    }
+
+    /**
+     * Get attached task item views ordered by most recent.
+     *
+     * @return array of attached task item views
+     */
+    private TaskItemView[] getTaskViews() {
+        int taskCount = mTaskRecyclerView.getChildCount();
+        TaskItemView[] itemViews = new TaskItemView[taskCount];
+        for (int i = 0; i < taskCount; i ++) {
+            itemViews[i] = (TaskItemView) mTaskRecyclerView.getChildAt(i);
+        }
+        return itemViews;
+    }
+
+    /**
      * Update the content view so that the appropriate view is shown based off the current list
      * of tasks.
      */
diff --git a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java
index 373f107..d831b20 100644
--- a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java
+++ b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java
@@ -17,6 +17,7 @@
 
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.graphics.Color;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.View;
@@ -24,6 +25,8 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.R;
 
 /**
@@ -31,12 +34,16 @@
  */
 public final class TaskItemView extends LinearLayout {
 
+    private static final String DEFAULT_LABEL = "...";
+    private final Drawable mDefaultIcon;
     private TextView mLabelView;
     private ImageView mIconView;
     private ImageView mThumbnailView;
 
     public TaskItemView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mDefaultIcon = context.getResources().getDrawable(
+                android.R.drawable.sym_def_app_icon, context.getTheme());
     }
 
     @Override
@@ -48,33 +55,56 @@
     }
 
     /**
-     * Set the label for the task item.
+     * Resets task item view to default values.
+     */
+    public void resetTaskItemView() {
+        setLabel(DEFAULT_LABEL);
+        setIcon(null);
+        setThumbnail(null);
+    }
+
+    /**
+     * Set the label for the task item. Sets to a default label if null.
      *
      * @param label task label
      */
-    public void setLabel(String label) {
+    public void setLabel(@Nullable String label) {
+        if (label == null) {
+            mLabelView.setText(DEFAULT_LABEL);
+            return;
+        }
         mLabelView.setText(label);
     }
 
     /**
-     * Set the icon for the task item.
+     * Set the icon for the task item. Sets to a default icon if null.
      *
      * @param icon task icon
      */
-    public void setIcon(Drawable icon) {
+    public void setIcon(@Nullable Drawable icon) {
         // TODO: Scale the icon up based off the padding on the side
         // The icon proper is actually smaller than the drawable and has "padding" on the side for
         // the purpose of drawing the shadow, allowing the icon to pop up, so we need to scale the
         // view if we want the icon to be flush with the bottom of the thumbnail.
+        if (icon == null) {
+            mIconView.setImageDrawable(mDefaultIcon);
+            return;
+        }
         mIconView.setImageDrawable(icon);
     }
 
     /**
-     * Set the task thumbnail for the task.
+     * Set the task thumbnail for the task. Sets to a default thumbnail if null.
      *
      * @param thumbnail task thumbnail for the task
      */
-    public void setThumbnail(Bitmap thumbnail) {
+    public void setThumbnail(@Nullable Bitmap thumbnail) {
+        if (thumbnail == null) {
+            mThumbnailView.setImageBitmap(null);
+            mThumbnailView.setBackgroundColor(Color.GRAY);
+            return;
+        }
+        mThumbnailView.setBackgroundColor(Color.TRANSPARENT);
         mThumbnailView.setImageBitmap(thumbnail);
     }
 
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 d0a2f27..f507d0f 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
@@ -20,7 +20,6 @@
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.config.FeatureFlags.SWIPE_HOME;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
@@ -31,6 +30,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
+import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.OverviewToAllAppsTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
@@ -39,7 +39,8 @@
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
-import com.android.quickstep.OverviewInteractionState;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
@@ -58,16 +59,13 @@
     private static final float RECENTS_PREPARE_SCALE = 1.33f;
 
     public static TouchController[] createTouchControllers(Launcher launcher) {
-        boolean swipeUpEnabled = OverviewInteractionState.INSTANCE.get(launcher)
-                .isSwipeUpGestureEnabled();
-        boolean swipeUpToHome = swipeUpEnabled && SWIPE_HOME.get();
-
+        Mode mode = SysUINavigationMode.INSTANCE.get(launcher).getMode();
 
         ArrayList<TouchController> list = new ArrayList<>();
         list.add(launcher.getDragController());
-
-        if (swipeUpToHome) {
+        if (mode == Mode.NO_BUTTON) {
             list.add(new QuickSwitchTouchController(launcher));
+            list.add(new NavBarToHomeTouchController(launcher));
             list.add(new FlingAndHoldTouchController(launcher));
         } else {
             if (launcher.getDeviceProfile().isVerticalBarLayout()) {
@@ -75,8 +73,8 @@
                 list.add(new LandscapeEdgeSwipeController(launcher));
             } else {
                 list.add(new PortraitStatesTouchController(launcher,
-                        swipeUpEnabled /* allowDragToOverview */));
-                if (swipeUpEnabled) {
+                        mode.hasGestures /* allowDragToOverview */));
+                if (mode.hasGestures) {
                     list.add(new QuickSwitchTouchController(launcher));
                 }
             }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
new file mode 100644
index 0000000..673beff
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -0,0 +1,141 @@
+/*
+ * 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.uioverrides.touchcontrollers;
+
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager.AnimationConfig;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.touch.AbstractStateChangeTouchController;
+import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * Handles swiping up on the nav bar to go home from overview or all apps.
+ */
+public class NavBarToHomeTouchController extends AbstractStateChangeTouchController {
+
+    private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL_3;
+
+    public NavBarToHomeTouchController(Launcher launcher) {
+        super(launcher, SwipeDetector.VERTICAL);
+    }
+
+    @Override
+    protected boolean canInterceptTouch(MotionEvent ev) {
+        boolean cameFromNavBar = (ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) != 0;
+        return cameFromNavBar && (mLauncher.isInState(OVERVIEW) || mLauncher.isInState(ALL_APPS));
+    }
+
+    @Override
+    protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
+        return isDragTowardPositive ? NORMAL : fromState;
+    }
+
+    @Override
+    protected float initCurrentAnimation(int animComponents) {
+        long accuracy = (long) (getShiftRange() * 2);
+        final AnimatorSet anim;
+        if (mFromState == OVERVIEW) {
+            anim = new AnimatorSet();
+            RecentsView recentsView = mLauncher.getOverviewPanel();
+            float pullbackDistance = recentsView.getPaddingStart() / 2;
+            if (!recentsView.isRtl()) {
+                pullbackDistance = -pullbackDistance;
+            }
+            anim.play(ObjectAnimator.ofFloat(recentsView, View.TRANSLATION_X, pullbackDistance));
+            anim.setInterpolator(PULLBACK_INTERPOLATOR);
+        } else { // if (mFromState == ALL_APPS)
+            AnimatorSetBuilder builder = new AnimatorSetBuilder();
+            AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
+            final float pullbackDistance = mLauncher.getDeviceProfile().allAppsIconSizePx / 2;
+            Animator allAppsProgress = ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS,
+                    -pullbackDistance / allAppsController.getShiftRange());
+            allAppsProgress.setInterpolator(PULLBACK_INTERPOLATOR);
+            builder.play(allAppsProgress);
+            // Slightly fade out all apps content to further distinguish from scrolling.
+            builder.setInterpolator(AnimatorSetBuilder.ANIM_ALL_APPS_FADE, Interpolators
+                    .mapToProgress(PULLBACK_INTERPOLATOR, 0, 0.5f));
+            AnimationConfig config = new AnimationConfig();
+            config.duration = accuracy;
+            allAppsController.setAlphas(mToState.getVisibleElements(mLauncher), config, builder);
+            anim = builder.build();
+        }
+        anim.setDuration(accuracy);
+        mCurrentAnimation = AnimatorPlaybackController.wrap(anim, accuracy, this::clearState);
+        return -1 / getShiftRange();
+    }
+
+    @Override
+    public void onDragStart(boolean start) {
+        super.onDragStart(start);
+        mStartContainerType = LauncherLogProto.ContainerType.NAVBAR;
+    }
+
+    @Override
+    public void onDragEnd(float velocity, boolean fling) {
+        final int logAction = fling ? Touch.FLING : Touch.SWIPE;
+        float interpolatedProgress = PULLBACK_INTERPOLATOR.getInterpolation(
+                mCurrentAnimation.getProgressFraction());
+        if (interpolatedProgress >= SUCCESS_TRANSITION_PROGRESS || velocity < 0 && fling) {
+            mLauncher.getStateManager().goToState(mToState, true,
+                    () -> onSwipeInteractionCompleted(mToState, logAction));
+        } else {
+            // Quickly return to the state we came from (we didn't move far).
+            AnimatorPlaybackController anim = mLauncher.getStateManager()
+                    .createAnimationToNewWorkspace(mFromState, 80);
+            anim.setEndAction(() -> onSwipeInteractionCompleted(mFromState, logAction));
+            anim.start();
+        }
+        mCurrentAnimation.dispatchOnCancel();
+    }
+
+    @Override
+    protected int getDirectionForLog() {
+        return LauncherLogProto.Action.Direction.UP;
+    }
+
+    @Override
+    protected boolean goingBetweenNormalAndOverview(LauncherState fromState,
+            LauncherState toState) {
+        // We don't want to create an atomic animation to/from overview.
+        return false;
+    }
+
+    @Override
+    protected int getLogContainerTypeForNormalState() {
+        return LauncherLogProto.ContainerType.NAVBAR;
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index a2219e8..62d954b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -38,7 +38,7 @@
 import com.android.launcher3.util.PendingAnimation;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.OverviewInteractionState;
+import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 
@@ -123,8 +123,7 @@
                     if (mRecentsView.isTaskViewVisible(view) && mActivity.getDragLayer()
                             .isEventOverView(view, ev)) {
                         mTaskBeingDragged = view;
-                        if (!OverviewInteractionState.INSTANCE.get(mActivity)
-                                .isSwipeUpGestureEnabled()) {
+                        if (!SysUINavigationMode.INSTANCE.get(mActivity).getMode().hasGestures) {
                             // Don't allow swipe down to open if we don't support swipe up
                             // to enter overview.
                             directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
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 9763063..69f3338 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -113,8 +113,7 @@
         final Rect iconLocation = new Rect();
         final FloatingIconView floatingView = workspaceView == null ? null
                 : FloatingIconView.getFloatingIconView(activity, workspaceView,
-                true /* hideOriginal */, false /* useDrawableAsIs */,
-                activity.getDeviceProfile().getAspectRatioWithInsets(), iconLocation, null);
+                true /* hideOriginal */, iconLocation, false /* isOpening */, null /* recycle */);
 
         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 8fe0461..323dd9a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -44,10 +44,10 @@
 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.quickstep.SysUINavigationMode.Mode;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -315,8 +315,8 @@
                 mOverviewComponentObserver.getActivityControlHelper();
         if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher) {
             return InputConsumer.NO_OP;
-        } else if (mAssistantAvailable && mOverviewInteractionState.isSwipeUpGestureEnabled()
-                && FeatureFlags.ENABLE_ASSISTANT_GESTURE.get()
+        } else if (mAssistantAvailable
+                && SysUINavigationMode.INSTANCE.get(this).getMode() == Mode.NO_BUTTON
                 && AssistantTouchConsumer.withinTouchRegion(this, event)) {
             return new AssistantTouchConsumer(this, mISystemUiProxy, !activityControl.isResumed()
                             ? createOtherActivityInputConsumer(event, runningTaskInfo) : null);
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 15072a2..0065cb5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -28,6 +28,7 @@
 import static com.android.launcher3.config.FeatureFlags.SWIPE_HOME;
 import static com.android.launcher3.util.RaceConditionTracker.ENTER;
 import static com.android.launcher3.util.RaceConditionTracker.EXIT;
+import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
 import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.HIDE;
 import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.PEEK;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
@@ -945,7 +946,7 @@
 
         // 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;
+        final float windowAlphaThreshold = isFloatingIconView ? 1f - SHAPE_PROGRESS_DURATION : 1f;
         anim.addOnUpdateListener((currentRect, progress) -> {
             float interpolatedProgress = Interpolators.ACCEL_1_5.getInterpolation(progress);
 
@@ -959,7 +960,7 @@
 
             if (isFloatingIconView) {
                 ((FloatingIconView) floatingView).update(currentRect, iconAlpha, progress,
-                        windowAlphaThreshold);
+                        windowAlphaThreshold, mClipAnimationHelper.getCurrentCornerRadius(), false);
             }
 
         });
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 8da1d2b..329436b 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
@@ -32,9 +32,7 @@
 import android.graphics.Rect;
 import android.os.Build;
 import android.util.AttributeSet;
-import android.util.FloatProperty;
 import android.view.View;
-import android.view.ViewDebug;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
@@ -44,7 +42,7 @@
 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.SysUINavigationMode;
 import com.android.quickstep.hints.ChipsContainer;
 import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
@@ -132,7 +130,7 @@
             ClipAnimationHelper helper) {
         AnimatorSet anim = super.createAdjacentPageAnimForTaskLaunch(tv, helper);
 
-        if (!OverviewInteractionState.INSTANCE.get(mActivity).isSwipeUpGestureEnabled()) {
+        if (!SysUINavigationMode.INSTANCE.get(mActivity).getMode().hasGestures) {
             // Hotseat doesn't move when opening recents with the button,
             // so don't animate it here either.
             return anim;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
index 90604ef..a9f6311 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -46,6 +46,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
+import com.android.quickstep.RecentsModel;
 import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
 import com.android.systemui.shared.recents.model.Task;
@@ -81,6 +82,7 @@
     private final Paint mBackgroundPaint = new Paint();
     private final Paint mClearPaint = new Paint();
     private final Paint mDimmingPaintAfterClearing = new Paint();
+    private final float mWindowCornerRadius;
 
     private final Matrix mMatrix = new Matrix();
 
@@ -114,6 +116,7 @@
         mDimmingPaintAfterClearing.setColor(Color.BLACK);
         mActivity = BaseActivity.fromContext(context);
         mIsDarkTextTheme = Themes.getAttrBoolean(mActivity, R.attr.isWorkspaceDarkText);
+        mWindowCornerRadius = RecentsModel.INSTANCE.get(context).getWindowCornerRadius();
     }
 
     public void bind(Task task) {
@@ -196,19 +199,22 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        float fullscreenProgress = ((TaskView) getParent()).getFullscreenProgress();
+        TaskView taskView = (TaskView) getParent();
+        float fullscreenProgress = taskView.getFullscreenProgress();
         if (mIsRotated) {
             // Don't show insets in the wrong orientation.
             fullscreenProgress = 0;
         }
         if (fullscreenProgress > 0) {
             // Draw the insets if we're being drawn fullscreen (we do this for quick switch).
+            float cornerRadius = Utilities.mapRange(fullscreenProgress, mCornerRadius,
+                    mWindowCornerRadius);
             drawOnCanvas(canvas,
                     -mScaledInsets.left * fullscreenProgress,
                     -mScaledInsets.top * fullscreenProgress,
                     getMeasuredWidth() + mScaledInsets.right * fullscreenProgress,
                     getMeasuredHeight() + mScaledInsets.bottom * fullscreenProgress,
-                    mCornerRadius);
+                    cornerRadius / taskView.getRecentsView().getScaleX());
         } else {
             drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(), mCornerRadius);
         }
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 6ba89c9..bea705f 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
@@ -432,6 +432,7 @@
     }
 
     private void resetViewTransforms() {
+        setCurveScale(1);
         setZoomScale(1);
         setTranslationX(0f);
         setTranslationY(0f);
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index c5475d6..f77bd65 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -20,15 +20,16 @@
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
 import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION;
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
+import static com.android.launcher3.anim.Interpolators.EXAGGERATED_EASE;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS;
+import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
@@ -45,6 +46,7 @@
 import android.content.res.Resources;
 import android.graphics.Matrix;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.CancellationSignal;
@@ -52,10 +54,8 @@
 import android.os.Looper;
 import android.util.Pair;
 import android.view.View;
-import android.view.ViewGroup;
 
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
-import com.android.launcher3.InsettableFrameLayout.LayoutParams;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.dragndrop.DragLayer;
@@ -103,13 +103,18 @@
     private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
             "android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
 
-    private static final int APP_LAUNCH_DURATION = 500;
+    private static final long APP_LAUNCH_DURATION = 500;
     // Use a shorter duration for x or y translation to create a curve effect
-    private static final int APP_LAUNCH_CURVED_DURATION = APP_LAUNCH_DURATION / 2;
+    private static final long APP_LAUNCH_CURVED_DURATION = APP_LAUNCH_DURATION / 2;
+    private static final long APP_LAUNCH_ALPHA_DURATION = 50;
+
     // We scale the durations for the downward app launch animations (minus the scale animation).
     private static final float APP_LAUNCH_DOWN_DUR_SCALE_FACTOR = 0.8f;
-    private static final int APP_LAUNCH_ALPHA_START_DELAY = 32;
-    private static final int APP_LAUNCH_ALPHA_DURATION = 50;
+    private static final long APP_LAUNCH_DOWN_DURATION =
+            (long) (APP_LAUNCH_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR);
+    private static final long APP_LAUNCH_DOWN_CURVED_DURATION = APP_LAUNCH_DOWN_DURATION / 2;
+    private static final long APP_LAUNCH_ALPHA_DOWN_DURATION =
+            (long) (APP_LAUNCH_ALPHA_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR);
 
     public static final int RECENTS_LAUNCH_DURATION = 336;
     private static final int LAUNCHER_RESUME_START_DELAY = 100;
@@ -207,11 +212,11 @@
 
             // Note that this duration is a guess as we do not know if the animation will be a
             // recents launch or not for sure until we know the opening app targets.
-            int duration = fromRecents
+            long duration = fromRecents
                     ? RECENTS_LAUNCH_DURATION
                     : APP_LAUNCH_DURATION;
 
-            int statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION
+            long statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION
                     - STATUS_BAR_TRANSITION_PRE_DELAY;
             return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
                     runner, duration, statusBarTransitionDelay));
@@ -266,7 +271,8 @@
             }
             if (!isAllOpeningTargetTrs) break;
         }
-        playIconAnimators(anim, v, windowTargetBounds, !isAllOpeningTargetTrs);
+        anim.play(getOpeningWindowAnimators(v, targets, windowTargetBounds,
+                !isAllOpeningTargetTrs));
         if (launcherClosing) {
             Pair<AnimatorSet, Runnable> launcherContentAnimator =
                     getLauncherContentAnimator(true /* isAppOpening */,
@@ -279,7 +285,6 @@
                 }
             });
         }
-        anim.play(getOpeningWindowAnimators(v, targets, windowTargetBounds));
     }
 
     /**
@@ -398,124 +403,13 @@
             float[] alphas, float[] trans);
 
     /**
-     * Animators for the "floating view" of the view used to launch the target.
-     */
-    private void playIconAnimators(AnimatorSet appOpenAnimator, View v, Rect windowTargetBounds,
-            boolean toggleVisibility) {
-        final boolean isBubbleTextView = v instanceof BubbleTextView;
-        if (mFloatingView != null) {
-            mFloatingView.setTranslationX(0);
-            mFloatingView.setTranslationY(0);
-            mFloatingView.setScaleX(1);
-            mFloatingView.setScaleY(1);
-            mFloatingView.setAlpha(1);
-            mFloatingView.setBackground(null);
-        }
-        Rect rect = new 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();
-        // Special RTL logic is needed to handle the window target bounds.
-        lp.leftMargin = mIsRtl ? windowTargetBounds.width() - rect.right : rect.left;
-        mFloatingView.setLayoutParams(lp);
-
-        int[] dragLayerBounds = new int[2];
-        mDragLayer.getLocationOnScreen(dragLayerBounds);
-
-        // Animate the app icon to the center of the window bounds in screen coordinates.
-        float centerX = windowTargetBounds.centerX() - dragLayerBounds[0];
-        float centerY = windowTargetBounds.centerY() - dragLayerBounds[1];
-
-        float xPosition = mIsRtl
-                ? windowTargetBounds.width() - lp.getMarginStart() - rect.width()
-                : lp.getMarginStart();
-        float dX = centerX - xPosition - (lp.width / 2f);
-        float dY = centerY - lp.topMargin - (lp.height / 2f);
-
-        ObjectAnimator x = ObjectAnimator.ofFloat(mFloatingView, View.TRANSLATION_X, 0f, dX);
-        ObjectAnimator y = ObjectAnimator.ofFloat(mFloatingView, View.TRANSLATION_Y, 0f, dY);
-
-        // Use upward animation for apps that are either on the bottom half of the screen, or are
-        // relatively close to the center.
-        boolean useUpwardAnimation = lp.topMargin > centerY
-                || Math.abs(dY) < mLauncher.getDeviceProfile().cellHeightPx;
-        if (useUpwardAnimation) {
-            x.setDuration(APP_LAUNCH_CURVED_DURATION);
-            y.setDuration(APP_LAUNCH_DURATION);
-        } else {
-            x.setDuration((long) (APP_LAUNCH_DOWN_DUR_SCALE_FACTOR * APP_LAUNCH_DURATION));
-            y.setDuration((long) (APP_LAUNCH_DOWN_DUR_SCALE_FACTOR * APP_LAUNCH_CURVED_DURATION));
-        }
-        x.setInterpolator(AGGRESSIVE_EASE);
-        y.setInterpolator(AGGRESSIVE_EASE);
-        appOpenAnimator.play(x);
-        appOpenAnimator.play(y);
-
-        // Scale the app icon to take up the entire screen. This simplifies the math when
-        // animating the app window position / scale.
-        float maxScaleX = windowTargetBounds.width() / (float) rect.width();
-        float maxScaleY = windowTargetBounds.height() / (float) rect.height();
-        float scale = Math.max(maxScaleX, maxScaleY);
-        float startScale = 1f;
-        if (isBubbleTextView && !(v.getParent() instanceof DeepShortcutView)) {
-            Drawable dr = ((BubbleTextView) v).getIcon();
-            if (dr instanceof FastBitmapDrawable) {
-                startScale = ((FastBitmapDrawable) dr).getAnimatedScale();
-            }
-        }
-
-        ObjectAnimator scaleAnim = ObjectAnimator
-                .ofFloat(mFloatingView, SCALE_PROPERTY, startScale, scale);
-        scaleAnim.setDuration(APP_LAUNCH_DURATION)
-                .setInterpolator(Interpolators.EXAGGERATED_EASE);
-        appOpenAnimator.play(scaleAnim);
-
-        // Fade out the app icon.
-        ObjectAnimator alpha = ObjectAnimator.ofFloat(mFloatingView, View.ALPHA, 1f, 0f);
-        if (useUpwardAnimation) {
-            alpha.setStartDelay(APP_LAUNCH_ALPHA_START_DELAY);
-            alpha.setDuration(APP_LAUNCH_ALPHA_DURATION);
-        } else {
-            alpha.setStartDelay((long) (APP_LAUNCH_DOWN_DUR_SCALE_FACTOR
-                    * APP_LAUNCH_ALPHA_START_DELAY));
-            alpha.setDuration((long) (APP_LAUNCH_DOWN_DUR_SCALE_FACTOR * APP_LAUNCH_ALPHA_DURATION));
-        }
-        alpha.setInterpolator(LINEAR);
-        appOpenAnimator.play(alpha);
-
-        appOpenAnimator.addListener(mFloatingView);
-        appOpenAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                // Reset launcher to normal state
-                if (isBubbleTextView) {
-                    ((BubbleTextView) v).setStayPressed(false);
-                }
-                v.setVisibility(View.VISIBLE);
-                ((ViewGroup) mDragLayer.getParent()).getOverlay().remove(mFloatingView);
-            }
-        });
-    }
-
-    /**
      * @return Animator that controls the window of the opening targets.
      */
     private ValueAnimator getOpeningWindowAnimators(View v, RemoteAnimationTargetCompat[] targets,
-            Rect windowTargetBounds) {
+            Rect windowTargetBounds, boolean toggleVisibility) {
         Rect bounds = new Rect();
-        if (v.getParent() instanceof DeepShortcutView) {
-            // Deep shortcut views have their icon drawn in a separate view.
-            DeepShortcutView view = (DeepShortcutView) v.getParent();
-            mDragLayer.getDescendantRectRelativeToSelf(view.getIconView(), bounds);
-        } else if (v instanceof BubbleTextView) {
-            ((BubbleTextView) v).getIconBounds(bounds);
-        } else {
-            mDragLayer.getDescendantRectRelativeToSelf(v, bounds);
-        }
-        int[] floatingViewBounds = new int[2];
-
+        mFloatingView = FloatingIconView.getFloatingIconView(mLauncher, v, toggleVisibility,
+                bounds, true /* isOpening */, mFloatingView);
         Rect crop = new Rect();
         Matrix matrix = new Matrix();
 
@@ -526,37 +420,99 @@
         SyncRtSurfaceTransactionApplierCompat surfaceApplier =
                 new SyncRtSurfaceTransactionApplierCompat(mFloatingView);
 
+        // Scale the app icon to take up the entire screen. This simplifies the math when
+        // animating the app window position / scale.
+        float maxScaleX = windowTargetBounds.width() / (float) bounds.width();
+        // We use windowTargetBounds.width for scaleY too since we start off the animation where the
+        // window is clipped to a square.
+        float maxScaleY = windowTargetBounds.width() / (float) bounds.height();
+        float scale = Math.max(maxScaleX, maxScaleY);
+        float startScale = 1f;
+        if (v instanceof BubbleTextView && !(v.getParent() instanceof DeepShortcutView)) {
+            Drawable dr = ((BubbleTextView) v).getIcon();
+            if (dr instanceof FastBitmapDrawable) {
+                startScale = ((FastBitmapDrawable) dr).getAnimatedScale();
+            }
+        }
+        final float initialStartScale = startScale;
+
+        int[] dragLayerBounds = new int[2];
+        mDragLayer.getLocationOnScreen(dragLayerBounds);
+
+        // Animate the app icon to the center of the window bounds in screen coordinates.
+        float centerX = windowTargetBounds.centerX() - dragLayerBounds[0];
+        float centerY = windowTargetBounds.centerY() - dragLayerBounds[1];
+
+        float dX = centerX - bounds.centerX();
+        float dY = centerY - bounds.centerY();
+
+        boolean useUpwardAnimation = bounds.top > centerY
+                || Math.abs(dY) < mLauncher.getDeviceProfile().cellHeightPx;
+        final long xDuration = useUpwardAnimation ? APP_LAUNCH_CURVED_DURATION
+                : APP_LAUNCH_DOWN_DURATION;
+        final long yDuration = useUpwardAnimation ? APP_LAUNCH_DURATION
+                : APP_LAUNCH_DOWN_CURVED_DURATION;
+        final long alphaDuration = useUpwardAnimation ? APP_LAUNCH_ALPHA_DURATION
+                : APP_LAUNCH_ALPHA_DOWN_DURATION;
+
+        RectF targetBounds = new RectF(windowTargetBounds);
+        RectF currentBounds = new RectF();
+        RectF temp = new RectF();
+
         ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
         appAnimator.setDuration(APP_LAUNCH_DURATION);
+        appAnimator.setInterpolator(LINEAR);
+        appAnimator.addListener(mFloatingView);
+        appAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (v instanceof BubbleTextView) {
+                    ((BubbleTextView) v).setStayPressed(false);
+                }
+            }
+        });
+
+        float shapeRevealDuration = APP_LAUNCH_DURATION * SHAPE_PROGRESS_DURATION;
         appAnimator.addUpdateListener(new MultiValueUpdateListener() {
-            // Fade alpha for the app window.
-            FloatProp mAlpha = new FloatProp(0f, 1f, 0, 60, LINEAR);
+            FloatProp mDx = new FloatProp(0, dX, 0, xDuration, AGGRESSIVE_EASE);
+            FloatProp mDy = new FloatProp(0, dY, 0, yDuration, AGGRESSIVE_EASE);
+            FloatProp mIconScale = new FloatProp(initialStartScale, scale, 0, APP_LAUNCH_DURATION,
+                    EXAGGERATED_EASE);
+            FloatProp mIconAlpha = new FloatProp(1f, 0f, shapeRevealDuration, alphaDuration,
+                    LINEAR);
+            FloatProp mCropHeight = new FloatProp(windowTargetBounds.width(),
+                    windowTargetBounds.height(), 0, shapeRevealDuration, AGGRESSIVE_EASE);
 
             @Override
             public void onUpdate(float percent) {
-                final float easePercent = AGGRESSIVE_EASE.getInterpolation(percent);
-
                 // Calculate app icon size.
-                float iconWidth = bounds.width() * mFloatingView.getScaleX();
-                float iconHeight = bounds.height() * mFloatingView.getScaleY();
+                float iconWidth = bounds.width() * mIconScale.value;
+                float iconHeight = bounds.height() * mIconScale.value;
+
+                // Animate the window crop so that it starts off as a square, and then reveals
+                // horizontally.
+                int windowWidth = windowTargetBounds.width();
+                int windowHeight = (int) mCropHeight.value;
+                crop.set(0, 0, windowWidth, windowHeight);
 
                 // Scale the app window to match the icon size.
-                float scaleX = iconWidth / windowTargetBounds.width();
-                float scaleY = iconHeight / windowTargetBounds.height();
-                float scale = Math.min(1f, Math.min(scaleX, scaleY));
+                float scaleX = iconWidth / windowWidth;
+                float scaleY = iconHeight / windowHeight;
+                float scale = Math.min(1f, Math.max(scaleX, scaleY));
 
-                // Position the scaled window on top of the icon
-                int windowWidth = windowTargetBounds.width();
-                int windowHeight = windowTargetBounds.height();
                 float scaledWindowWidth = windowWidth * scale;
                 float scaledWindowHeight = windowHeight * scale;
 
                 float offsetX = (scaledWindowWidth - iconWidth) / 2;
                 float offsetY = (scaledWindowHeight - iconHeight) / 2;
-                mFloatingView.getLocationOnScreen(floatingViewBounds);
 
-                float transX0 = floatingViewBounds[0] - offsetX;
-                float transY0 = floatingViewBounds[1] - offsetY;
+                // Calculate the window position
+                temp.set(bounds);
+                temp.offset(dragLayerBounds[0], dragLayerBounds[1]);
+                temp.offset(mDx.value, mDy.value);
+                Utilities.scaleRectFAboutCenter(temp, mIconScale.value);
+                float transX0 = temp.left - offsetX;
+                float transY0 = temp.top - offsetY;
 
                 float windowRadius = 0;
                 if (!mDeviceProfile.isMultiWindowMode &&
@@ -565,19 +521,9 @@
                             .getWindowCornerRadius();
                 }
 
-                // Animate the window crop so that it starts off as a square, and then reveals
-                // horizontally.
-                float cropHeight = windowHeight * easePercent + windowWidth * (1 - easePercent);
-                float initialTop = (windowHeight - windowWidth) / 2f;
-                crop.left = 0;
-                crop.top = (int) (initialTop * (1 - easePercent));
-                crop.right = windowWidth;
-                crop.bottom = (int) (crop.top + cropHeight);
-
                 SurfaceParams[] params = new SurfaceParams[targets.length];
                 for (int i = targets.length - 1; i >= 0; i--) {
                     RemoteAnimationTargetCompat target = targets[i];
-
                     Rect targetCrop;
                     final float alpha;
                     final float cornerRadius;
@@ -585,12 +531,15 @@
                         matrix.setScale(scale, scale);
                         matrix.postTranslate(transX0, transY0);
                         targetCrop = crop;
-                        alpha = mAlpha.value;
+                        alpha = 1f - mIconAlpha.value;
                         cornerRadius = windowRadius;
+                        matrix.mapRect(currentBounds, targetBounds);
+                        mFloatingView.update(currentBounds, mIconAlpha.value, percent, 0f,
+                                cornerRadius * scale, true /* isOpening */);
                     } else {
                         matrix.setTranslate(target.position.x, target.position.y);
-                        alpha = 1f;
                         targetCrop = target.sourceContainerBounds;
+                        alpha = 1f;
                         cornerRadius = 0;
                     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index 25e1c89..6d730b6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -41,8 +41,11 @@
 import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.QuickstepAppTransitionManagerImpl;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.dragndrop.DragLayer;
 import com.android.quickstep.OverviewInteractionState;
 import com.android.quickstep.RecentsModel;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
 import com.android.quickstep.util.RemoteFadeOutAnimationListener;
 import com.android.systemui.shared.system.ActivityCompat;
 
@@ -52,8 +55,11 @@
 
 public class UiFactory extends RecentsUiFactory {
 
-    public static void setOnTouchControllersChangedListener(Context context, Runnable listener) {
-        OverviewInteractionState.INSTANCE.get(context).setOnSwipeUpSettingChangedListener(listener);
+    public static Runnable enableLiveTouchControllerChanges(DragLayer dl) {
+        NavigationModeChangeListener listener = m -> dl.recreateControllers();
+        SysUINavigationMode mode = SysUINavigationMode.INSTANCE.get(dl.getContext());
+        mode.addModeChangeListener(listener);
+        return () -> mode.removeModeChangeListener(listener);
     }
 
     public static StateHandler[] getStateHandler(Launcher launcher) {
@@ -89,8 +95,8 @@
 
                 @Override
                 public void onStateTransitionComplete(LauncherState finalState) {
-                    boolean swipeUpEnabled = OverviewInteractionState.INSTANCE.get(launcher)
-                            .isSwipeUpGestureEnabled();
+                    boolean swipeUpEnabled = SysUINavigationMode.INSTANCE.get(launcher).getMode()
+                            .hasGestures;
                     LauncherState prevState = launcher.getStateManager().getLastState();
 
                     if (((swipeUpEnabled && finalState == OVERVIEW) || (!swipeUpEnabled
diff --git a/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java b/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
index 12757c0..3e9872a 100644
--- a/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
+++ b/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
@@ -19,16 +19,11 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.InstantAppInfo;
 import android.content.pm.PackageManager;
-import android.util.Log;
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.util.InstantAppResolver;
 
-import java.util.ArrayList;
-import java.util.List;
-
 /**
  * Implementation of InstantAppResolver using platform APIs
  */
@@ -40,8 +35,7 @@
 
     private final PackageManager mPM;
 
-    public InstantAppResolverImpl(Context context)
-            throws NoSuchMethodException, ClassNotFoundException {
+    public InstantAppResolverImpl(Context context) {
         mPM = context.getPackageManager();
     }
 
@@ -55,23 +49,4 @@
         ComponentName cn = info.getTargetComponent();
         return cn != null && cn.getClassName().equals(COMPONENT_CLASS_MARKER);
     }
-
-    @Override
-    public List<ApplicationInfo> getInstantApps() {
-        try {
-            List<ApplicationInfo> result = new ArrayList<>();
-            for (InstantAppInfo iai : mPM.getInstantApps()) {
-                ApplicationInfo info = iai.getApplicationInfo();
-                if (info != null) {
-                    result.add(info);
-                }
-            }
-            return result;
-        } catch (SecurityException se) {
-            Log.w(TAG, "getInstantApps failed. Launcher may not be the default home app.", se);
-        } catch (Exception e) {
-            Log.e(TAG, "Error calling API: getInstantApps", e);
-        }
-        return super.getInstantApps();
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/NavBarModeOverlayResourceObserver.java b/quickstep/src/com/android/quickstep/NavBarModeOverlayResourceObserver.java
deleted file mode 100644
index 3136632..0000000
--- a/quickstep/src/com/android/quickstep/NavBarModeOverlayResourceObserver.java
+++ /dev/null
@@ -1,89 +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;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
-import android.util.Log;
-
-import com.android.systemui.shared.system.QuickStepContract;
-
-/**
- * Observer for the resource config that specifies the navigation bar mode.
- */
-public class NavBarModeOverlayResourceObserver extends BroadcastReceiver {
-
-    private static final String TAG = "NavBarModeOverlayResourceObserver";
-
-    private final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
-    private static final String NAV_BAR_INTERACTION_MODE_RES_NAME =
-            "config_navBarInteractionMode";
-
-    private final Context mContext;
-    private final OnChangeListener mOnChangeListener;
-
-    public NavBarModeOverlayResourceObserver(Context context, OnChangeListener listener) {
-        mContext = context;
-        mOnChangeListener = listener;
-    }
-
-    public void register() {
-        IntentFilter filter = new IntentFilter(ACTION_OVERLAY_CHANGED);
-        filter.addDataScheme("package");
-        mContext.registerReceiver(this, filter);
-    }
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        mOnChangeListener.onNavBarModeChanged(getSystemIntegerRes(context,
-                NAV_BAR_INTERACTION_MODE_RES_NAME));
-    }
-
-    public interface OnChangeListener {
-        void onNavBarModeChanged(int mode);
-    }
-
-    public static boolean isSwipeUpModeEnabled(Context context) {
-        return QuickStepContract.isSwipeUpMode(getSystemIntegerRes(context,
-                NAV_BAR_INTERACTION_MODE_RES_NAME));
-    }
-
-    public static boolean isEdgeToEdgeModeEnabled(Context context) {
-        return QuickStepContract.isGesturalMode(getSystemIntegerRes(context,
-                NAV_BAR_INTERACTION_MODE_RES_NAME));
-    }
-
-    public static boolean isLegacyModeEnabled(Context context) {
-        return QuickStepContract.isLegacyMode(getSystemIntegerRes(context,
-                NAV_BAR_INTERACTION_MODE_RES_NAME));
-    }
-
-    private static int getSystemIntegerRes(Context context, String resName) {
-        Resources res = context.getResources();
-        int resId = res.getIdentifier(resName, "integer", "android");
-
-        if (resId != 0) {
-            return res.getInteger(resId);
-        } else {
-            Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
-            return -1;
-        }
-    }
-}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
index 903701d..ce472c6 100644
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
@@ -30,8 +30,8 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.UiThreadHelper;
+import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.systemui.shared.recents.ISystemUiProxy;
-import com.android.systemui.shared.system.QuickStepContract;
 
 import androidx.annotation.WorkerThread;
 
@@ -56,10 +56,7 @@
 
     private static final int MSG_SET_PROXY = 200;
     private static final int MSG_SET_BACK_BUTTON_ALPHA = 201;
-    private static final int MSG_SET_SWIPE_UP_ENABLED = 202;
-
-    // TODO: Discriminate between swipe up and edge to edge
-    private final NavBarModeOverlayResourceObserver mSwipeUpSettingObserver;
+    private static final int MSG_APPLY_FLAGS = 202;
 
     private final Context mContext;
     private final Handler mUiHandler;
@@ -70,8 +67,6 @@
     private boolean mSwipeUpEnabled;
     private float mBackButtonAlpha = 1;
 
-    private Runnable mOnSwipeUpSettingChangedListener;
-
     private OverviewInteractionState(Context context) {
         mContext = context;
 
@@ -81,20 +76,8 @@
         mUiHandler = new Handler(this::handleUiMessage);
         mBgHandler = new Handler(UiThreadHelper.getBackgroundLooper(), this::handleBgMessage);
 
-        mSwipeUpEnabled = NavBarModeOverlayResourceObserver.isSwipeUpModeEnabled(mContext)
-                || NavBarModeOverlayResourceObserver.isEdgeToEdgeModeEnabled(mContext);
-        if (SwipeUpSetting.isSystemNavigationSettingAvailable()) {
-            mSwipeUpSettingObserver = new NavBarModeOverlayResourceObserver(context,
-                    this::notifySwipeUpSettingChanged);
-            mSwipeUpSettingObserver.register();
-            resetHomeBounceSeenOnQuickstepEnabledFirstTime();
-        } else {
-            mSwipeUpSettingObserver = null;
-        }
-    }
-
-    public boolean isSwipeUpGestureEnabled() {
-        return mSwipeUpEnabled;
+        onNavigationModeChanged(SysUINavigationMode.INSTANCE.get(context)
+                .addModeChangeListener(this::onNavigationModeChanged));
     }
 
     public float getBackButtonAlpha() {
@@ -130,23 +113,13 @@
             case MSG_SET_BACK_BUTTON_ALPHA:
                 applyBackButtonAlpha((float) msg.obj, msg.arg1 == 1);
                 return true;
-            case MSG_SET_SWIPE_UP_ENABLED:
-                mSwipeUpEnabled = msg.arg1 != 0;
-                resetHomeBounceSeenOnQuickstepEnabledFirstTime();
-
-                if (mOnSwipeUpSettingChangedListener != null) {
-                    mOnSwipeUpSettingChangedListener.run();
-                }
+            case MSG_APPLY_FLAGS:
                 break;
         }
         applyFlags();
         return true;
     }
 
-    public void setOnSwipeUpSettingChangedListener(Runnable listener) {
-        mOnSwipeUpSettingChangedListener = listener;
-    }
-
     @WorkerThread
     private void applyFlags() {
         if (mISystemUiProxy == null) {
@@ -176,16 +149,12 @@
         }
     }
 
-    private void notifySwipeUpSettingChanged(int mode) {
-        boolean swipeUpEnabled = !QuickStepContract.isLegacyMode(mode);
-        boolean gesturalEnabled = QuickStepContract.isGesturalMode(mode);
+    private void onNavigationModeChanged(SysUINavigationMode.Mode mode) {
+        FeatureFlags.SWIPE_HOME.updateStorage(mContext, mode == Mode.NO_BUTTON);
 
-        FeatureFlags.SWIPE_HOME.updateStorage(mContext, gesturalEnabled);
-        FeatureFlags.ENABLE_ASSISTANT_GESTURE.updateStorage(mContext, gesturalEnabled);
-
-        mUiHandler.removeMessages(MSG_SET_SWIPE_UP_ENABLED);
-        mUiHandler.obtainMessage(MSG_SET_SWIPE_UP_ENABLED, swipeUpEnabled ? 1 : 0, 0).
-                sendToTarget();
+        mSwipeUpEnabled = mode.hasGestures;
+        resetHomeBounceSeenOnQuickstepEnabledFirstTime();
+        mBgHandler.obtainMessage(MSG_APPLY_FLAGS).sendToTarget();
     }
 
     private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
diff --git a/quickstep/src/com/android/quickstep/SwipeUpSetting.java b/quickstep/src/com/android/quickstep/SwipeUpSetting.java
deleted file mode 100644
index 7f830f9..0000000
--- a/quickstep/src/com/android/quickstep/SwipeUpSetting.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2018 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.res.Resources;
-import android.util.Log;
-
-public final class SwipeUpSetting {
-    private static final String TAG = "SwipeUpSetting";
-
-    private static final String SWIPE_UP_SETTING_AVAILABLE_RES_NAME =
-            "config_swipe_up_gesture_setting_available";
-
-    public static boolean isSystemNavigationSettingAvailable() {
-        return getSystemBooleanRes(SWIPE_UP_SETTING_AVAILABLE_RES_NAME);
-    }
-
-    private static boolean getSystemBooleanRes(String resName) {
-        Resources res = Resources.getSystem();
-        int resId = res.getIdentifier(resName, "bool", "android");
-
-        if (resId != 0) {
-            return res.getBoolean(resId);
-        } else {
-            Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
-            return false;
-        }
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/SysUINavigationMode.java b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
new file mode 100644
index 0000000..1953ecb
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
@@ -0,0 +1,127 @@
+/*
+ * 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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.util.Log;
+
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.systemui.shared.system.QuickStepContract;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Observer for the resource config that specifies the navigation bar mode.
+ */
+public class SysUINavigationMode {
+
+    public enum Mode {
+        THREE_BUTTONS(false, 0),
+        TWO_BUTTONS(true, 1),
+        NO_BUTTON(true, 2);
+
+        public final boolean hasGestures;
+        public final int resValue;
+
+        Mode(boolean hasGestures, int resValue) {
+            this.hasGestures = hasGestures;
+            this.resValue = resValue;
+        }
+    }
+
+    public static MainThreadInitializedObject<SysUINavigationMode> INSTANCE =
+            new MainThreadInitializedObject<>(SysUINavigationMode::new);
+
+    private static final String TAG = "SysUINavigationMode";
+
+    private final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
+    private static final String NAV_BAR_INTERACTION_MODE_RES_NAME =
+            "config_navBarInteractionMode";
+
+    private final Context mContext;
+    private Mode mMode;
+
+    private final List<NavigationModeChangeListener> mChangeListeners = new ArrayList<>();
+
+    public SysUINavigationMode(Context context) {
+        mContext = context;
+        initializeMode();
+
+        IntentFilter filter = new IntentFilter(ACTION_OVERLAY_CHANGED);
+        filter.addDataScheme("package");
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                Mode oldMode = mMode;
+                initializeMode();
+                if (mMode != oldMode) {
+                    dispatchModeChange();
+                }
+            }
+        }, filter);
+    }
+
+    private void initializeMode() {
+        int modeInt = getSystemIntegerRes(mContext, NAV_BAR_INTERACTION_MODE_RES_NAME);
+        for(Mode m : Mode.values()) {
+            if (m.resValue == modeInt) {
+                mMode = m;
+            }
+        }
+    }
+
+    private void dispatchModeChange() {
+        for (NavigationModeChangeListener listener : mChangeListeners) {
+            listener.onNavigationModeChanged(mMode);
+        }
+    }
+
+    public Mode addModeChangeListener(NavigationModeChangeListener listener) {
+        mChangeListeners.add(listener);
+        return mMode;
+    }
+
+    public void removeModeChangeListener(NavigationModeChangeListener listener) {
+        mChangeListeners.remove(listener);
+    }
+
+    public Mode getMode() {
+        return mMode;
+    }
+
+    private static int getSystemIntegerRes(Context context, String resName) {
+        Resources res = context.getResources();
+        int resId = res.getIdentifier(resName, "integer", "android");
+
+        if (resId != 0) {
+            return res.getInteger(resId);
+        } else {
+            Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
+            return -1;
+        }
+    }
+
+    public interface NavigationModeChangeListener {
+
+        void onNavigationModeChanged(Mode newMode);
+    }
+}
\ No newline at end of file
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 226e5ee..6623861 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -41,6 +41,7 @@
 import com.android.quickstep.views.RecentsView;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestWatcher;
@@ -71,6 +72,7 @@
 
     @Test
     @PortraitLandscape
+    @Ignore
     public void testPressRecentAppsLauncherAndGetOverview() throws RemoteException {
         mDevice.pressRecentApps();
         waitForState("Launcher internal state didn't switch to Overview", LauncherState.OVERVIEW);
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 6397e14..6a3a26f 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -51,6 +51,9 @@
     public final int heightPx;
     public final int availableWidthPx;
     public final int availableHeightPx;
+
+    public final float aspectRatio;
+
     /**
      * The maximum amount of left/right workspace padding as a percentage of the screen width.
      * To be clear, this means that up to 7% of the screen width can be used as left padding, and
@@ -160,7 +163,7 @@
         isTablet = res.getBoolean(R.bool.is_tablet);
         isLargeTablet = res.getBoolean(R.bool.is_large_tablet);
         isPhone = !isTablet && !isLargeTablet;
-        float aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx);
+        aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx);
         boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0;
 
         // Some more constants
@@ -618,12 +621,6 @@
         }
     }
 
-    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/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 964e8b6..7ab88a0 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -103,9 +103,14 @@
     }
 
     protected FastBitmapDrawable(Bitmap b, int iconColor) {
+        this(b, iconColor, false);
+    }
+
+    protected FastBitmapDrawable(Bitmap b, int iconColor, boolean isDisabled) {
         mBitmap = b;
         mIconColor = iconColor;
         setFilterBitmap(true);
+        setIsDisabled(isDisabled);
     }
 
     @Override
@@ -249,6 +254,10 @@
         }
     }
 
+    protected boolean isDisabled() {
+        return mIsDisabled;
+    }
+
     /**
      * Sets the saturation of this icon, 0 [full color] -> 1 [desaturated]
      */
@@ -338,21 +347,23 @@
 
     @Override
     public ConstantState getConstantState() {
-        return new MyConstantState(mBitmap, mIconColor);
+        return new MyConstantState(mBitmap, mIconColor, mIsDisabled);
     }
 
     protected static class MyConstantState extends ConstantState {
         protected final Bitmap mBitmap;
         protected final int mIconColor;
+        protected final boolean mIsDisabled;
 
-        public MyConstantState(Bitmap bitmap, int color) {
+        public MyConstantState(Bitmap bitmap, int color, boolean isDisabled) {
             mBitmap = bitmap;
             mIconColor = color;
+            mIsDisabled = isDisabled;
         }
 
         @Override
         public Drawable newDrawable() {
-            return new FastBitmapDrawable(mBitmap, mIconColor);
+            return new FastBitmapDrawable(mBitmap, mIconColor, mIsDisabled);
         }
 
         @Override
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 867001a..89236aa 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -257,6 +257,7 @@
     public ViewGroupFocusHelper mFocusHandler;
 
     private RotationHelper mRotationHelper;
+    private Runnable mCancelTouchController;
 
     final Handler mHandler = new Handler();
     private final Runnable mHandleDeferredResume = this::handleDeferredResume;
@@ -946,7 +947,7 @@
 
         // Setup the drag layer
         mDragLayer.setup(mDragController, mWorkspace);
-        UiFactory.setOnTouchControllersChangedListener(this, mDragLayer::recreateControllers);
+        mCancelTouchController = UiFactory.enableLiveTouchControllerChanges(mDragLayer);
 
         mWorkspace.setup(mDragController);
         // Until the workspace is bound, ensure that we keep the wallpaper offset locked to the
@@ -1318,7 +1319,10 @@
         unregisterReceiver(mScreenOffReceiver);
         mWorkspace.removeFolderListeners();
 
-        UiFactory.setOnTouchControllersChangedListener(this, null);
+        if (mCancelTouchController != null) {
+            mCancelTouchController.run();
+            mCancelTouchController = null;
+        }
 
         // Stop callbacks from LauncherModel
         // It's possible to receive onDestroy after a new Launcher activity has
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 106d901..656151f 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -118,10 +118,6 @@
             "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/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index c403e76..0274de3 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -224,7 +224,8 @@
         return true;
     }
 
-    private boolean goingBetweenNormalAndOverview(LauncherState fromState, LauncherState toState) {
+    protected boolean goingBetweenNormalAndOverview(LauncherState fromState,
+            LauncherState toState) {
         return (fromState == NORMAL || fromState == OVERVIEW)
                 && (toState == NORMAL || toState == OVERVIEW)
                 && mPendingAnimation == null;
@@ -242,7 +243,7 @@
             mStartContainerType = LauncherLogProto.ContainerType.ALLAPPS;
         } else if (mStartState == NORMAL) {
             mStartContainerType = getLogContainerTypeForNormalState();
-        } else if (mStartState   == OVERVIEW){
+        } else if (mStartState == OVERVIEW){
             mStartContainerType = LauncherLogProto.ContainerType.TASKSWITCHER;
         }
         if (mCurrentAnimation == null) {
diff --git a/src/com/android/launcher3/util/InstantAppResolver.java b/src/com/android/launcher3/util/InstantAppResolver.java
index 5dc7af8..031a40d 100644
--- a/src/com/android/launcher3/util/InstantAppResolver.java
+++ b/src/com/android/launcher3/util/InstantAppResolver.java
@@ -24,9 +24,6 @@
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.R;
 
-import java.util.Collections;
-import java.util.List;
-
 /**
  * A wrapper class to access instant app related APIs.
  */
@@ -55,8 +52,4 @@
         }
         return false;
     }
-
-    public List<ApplicationInfo> getInstantApps() {
-        return Collections.emptyList();
-    }
 }
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 49ec292..5889468 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.views;
 
+import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
 
 import android.animation.Animator;
@@ -47,7 +48,6 @@
 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.dragndrop.FolderAdaptiveIcon;
 import com.android.launcher3.folder.FolderIcon;
@@ -60,18 +60,20 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
+import static com.android.launcher3.Utilities.mapToRange;
+
 /**
  * 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, ClipPathView {
 
+    public static final float SHAPE_PROGRESS_DURATION = 0.15f;
+
     private static final Rect sTmpRect = new Rect();
 
-    private Runnable mStartRunnable;
     private Runnable mEndRunnable;
 
-    private int mOriginalHeight;
     private final int mBlurSizeOutline;
 
     private boolean mIsAdaptiveIcon = false;
@@ -82,30 +84,28 @@
     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 float mTaskCornerRadius;
 
     private final Rect mFinalDrawableBounds = new Rect();
     private final Rect mBgDrawableBounds = new Rect();
     private float mBgDrawableStartScale = 1f;
+    private float mBgDrawableEndScale = 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.
+     * @param shapeProgressStart The progress value at which to start the shape reveal.
+     * @param cornerRadius The corner radius of {@param rect}.
      */
-    public void update(RectF rect, float alpha, float progress, float windowAlphaThreshold) {
+    public void update(RectF rect, float alpha, float progress, float shapeProgressStart,
+            float cornerRadius, boolean isOpening) {
         setAlpha(alpha);
 
         LayoutParams lp = (LayoutParams) getLayoutParams();
@@ -116,49 +116,42 @@
 
         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);
+        float scale = mIsAdaptiveIcon && !isOpening ? Math.max(scaleX, scaleY)
+                : Math.min(scaleX, scaleY);
+        scale = Math.max(1f, scale);
+
         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) {
+        // shapeRevealProgress = 1 when progress = shapeProgressStart + SHAPE_PROGRESS_DURATION
+        float toMax = isOpening ? 1 / SHAPE_PROGRESS_DURATION : 1f;
+        float shapeRevealProgress = Utilities.boundToRange(mapToRange(
+                Math.max(shapeProgressStart, progress), shapeProgressStart, 1f, 0, toMax,
+                LINEAR), 0, 1);
+
+        mTaskCornerRadius = cornerRadius;
+        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;
-                    }
-                });
+                        mStartRevealRect, mEndRevealRect, mTaskCornerRadius / scale, !isOpening);
                 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);
+
+            float bgScale = (mBgDrawableEndScale * shapeRevealProgress) + mBgDrawableStartScale
+                    * (1 - shapeRevealProgress);
+            setBackgroundDrawableBounds(bgScale);
         }
         invalidate();
         invalidateOutline();
     }
 
     @Override
-    public void onAnimationStart(Animator animator) {
-        if (mStartRunnable != null) {
-            mStartRunnable.run();
-        }
-    }
-
-    @Override
     public void onAnimationEnd(Animator animator) {
         if (mEndRunnable != null) {
             mEndRunnable.run();
@@ -180,7 +173,6 @@
         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;
@@ -193,11 +185,11 @@
     }
 
     @WorkerThread
-    private void getIcon(Launcher launcher, View v, ItemInfo info, boolean useDrawableAsIs,
-            float aspectRatio) {
+    private void getIcon(Launcher launcher, View v, ItemInfo info, boolean isOpening,
+            Runnable onIconLoadedRunnable) {
         final LayoutParams lp = (LayoutParams) getLayoutParams();
         Drawable drawable = null;
-        boolean supportsAdaptiveIcons = ADAPTIVE_ICON_WINDOW_ANIM.get() && !useDrawableAsIs
+        boolean supportsAdaptiveIcons = ADAPTIVE_ICON_WINDOW_ANIM.get()
                 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
         if (!supportsAdaptiveIcons && v instanceof BubbleTextView) {
             // Similar to DragView, we simply use the BubbleTextView icon here.
@@ -214,7 +206,7 @@
         }
         if (drawable == null) {
             drawable = Utilities.getFullDrawable(launcher, info, lp.width, lp.height,
-                    useDrawableAsIs, new Object[1]);
+                    false, new Object[1]);
         }
 
         Drawable finalDrawable = drawable == null ? null
@@ -247,35 +239,50 @@
                     sbd.setShiftY(sbd.getShiftY() - sTmpRect.top);
                 }
 
+                final int originalHeight = lp.height;
+                final int originalWidth = lp.width;
+
                 int blurMargin = mBlurSizeOutline / 2;
-                mFinalDrawableBounds.set(0, 0, lp.width, mOriginalHeight);
+                mFinalDrawableBounds.set(0, 0, originalWidth, originalHeight);
                 if (!isFolderIcon) {
                     mFinalDrawableBounds.inset(iconOffset - blurMargin, iconOffset - blurMargin);
                 }
                 mForeground.setBounds(mFinalDrawableBounds);
                 mBackground.setBounds(mFinalDrawableBounds);
 
-                if (isFolderIcon) {
-                    mStartRevealRect.set(0, 0, lp.width, mOriginalHeight);
+                mStartRevealRect.set(0, 0, originalWidth, originalHeight);
+
+                if (!isFolderIcon) {
+                    mStartRevealRect.inset(mBlurSizeOutline, mBlurSizeOutline);
+                }
+
+                float aspectRatio = launcher.getDeviceProfile().aspectRatio;
+                if (launcher.getDeviceProfile().isVerticalBarLayout()) {
+                    lp.width = (int) Math.max(lp.width, lp.height * aspectRatio);
                 } else {
-                    mStartRevealRect.set(mBlurSizeOutline, mBlurSizeOutline,
-                            lp.width - mBlurSizeOutline, mOriginalHeight - mBlurSizeOutline);
-                }
-
-                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);
+                layout(lp.leftMargin, lp.topMargin, lp.leftMargin + lp.width, lp.topMargin
+                        + lp.height);
 
-                // Set up outline
-                mOutline.set(0, 0, lp.width, lp.height);
+                Rect rectOutline = new Rect();
+                float scale = Math.max((float) lp.height / originalHeight,
+                        (float) lp.width / originalWidth);
+                if (isOpening) {
+                    mBgDrawableStartScale = 1f;
+                    mBgDrawableEndScale = scale;
+                    rectOutline.set(0, 0, originalWidth, originalHeight);
+                } else {
+                    mBgDrawableStartScale = scale;
+                    mBgDrawableEndScale = 1f;
+                    rectOutline.set(0, 0, lp.width, lp.height);
+                }
+                mEndRevealRect.set(0, 0, lp.width, lp.height);
+                setBackgroundDrawableBounds(mBgDrawableStartScale);
                 setOutlineProvider(new ViewOutlineProvider() {
                     @Override
                     public void getOutline(View view, Outline outline) {
-                        outline.setRoundRect(mOutline, mTaskCornerRadius);
+                        outline.setRoundRect(rectOutline, mTaskCornerRadius);
                     }
                 });
                 setClipToOutline(true);
@@ -283,6 +290,7 @@
                 setBackground(finalDrawable);
             }
 
+            onIconLoadedRunnable.run();
             invalidate();
             invalidateOutline();
         });
@@ -350,6 +358,9 @@
     }
 
     @Override
+    public void onAnimationStart(Animator animator) {}
+
+    @Override
     public void onAnimationCancel(Animator animator) {}
 
     @Override
@@ -357,17 +368,16 @@
 
     /**
      * 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.
+     * @param isOpening True if this view replaces the icon for app open animation.
      */
     public static FloatingIconView getFloatingIconView(Launcher launcher, View originalView,
-            boolean hideOriginal, boolean useDrawableAsIs, float aspectRatio, Rect positionOut,
-            FloatingIconView recycle) {
+            boolean hideOriginal, Rect positionOut, boolean isOpening, FloatingIconView recycle) {
+        if (recycle != null) {
+            recycle.recycle();
+        }
         FloatingIconView view = recycle != null ? recycle : new FloatingIconView(launcher);
 
         // Match the position of the original view.
@@ -376,9 +386,16 @@
         // 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) {
+            Runnable onIconLoaded = () -> {
+                // Delay swapping views until the icon is loaded to prevent a flash.
+                view.setVisibility(VISIBLE);
+                if (hideOriginal) {
+                    originalView.setVisibility(INVISIBLE);
+                }
+            };
             new Handler(LauncherModel.getWorkerLooper()).postAtFrontOfQueue(() -> {
-                view.getIcon(launcher, originalView, (ItemInfo) originalView.getTag(),
-                        useDrawableAsIs, aspectRatio);
+                view.getIcon(launcher, originalView, (ItemInfo) originalView.getTag(), isOpening,
+                        onIconLoaded);
             });
         }
 
@@ -387,12 +404,6 @@
         view.setVisibility(INVISIBLE);
         ((ViewGroup) dragLayer.getParent()).getOverlay().add(view);
 
-        view.mStartRunnable = () -> {
-            view.setVisibility(VISIBLE);
-            if (hideOriginal) {
-                originalView.setVisibility(INVISIBLE);
-            }
-        };
         if (hideOriginal) {
             view.mEndRunnable = () -> {
                 AnimatorSet fade = new AnimatorSet();
@@ -442,4 +453,24 @@
         }
         return view;
     }
+
+    private void recycle() {
+        setTranslationX(0);
+        setTranslationY(0);
+        setScaleX(1);
+        setScaleY(1);
+        setAlpha(1);
+        setBackground(null);
+        mEndRunnable = null;
+        mIsAdaptiveIcon = false;
+        mForeground = null;
+        mBackground = null;
+        mClipPath = null;
+        mFinalDrawableBounds.setEmpty();
+        mBgDrawableBounds.setEmpty();;
+        if (mRevealAnimator != null) {
+            mRevealAnimator.cancel();
+        }
+        mRevealAnimator = null;
+    }
 }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
index 0d727fd..9939c25 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -22,6 +22,7 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.util.TouchController;
 
 import java.io.PrintWriter;
@@ -33,7 +34,9 @@
                 launcher.getDragController(), new AllAppsSwipeController(launcher)};
     }
 
-    public static void setOnTouchControllersChangedListener(Context context, Runnable listener) { }
+    public static Runnable enableLiveTouchControllerChanges(DragLayer dl) {
+        return null;
+    }
 
     public static StateHandler[] getStateHandler(Launcher launcher) {
         return new StateHandler[] {
diff --git a/tests/Android.mk b/tests/Android.mk
index ca7395a..080c98b 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -30,7 +30,6 @@
     LOCAL_STATIC_JAVA_LIBRARIES += libSharedSystemUI
 
     LOCAL_SRC_FILES := $(call all-java-files-under, tapl) \
-        ../quickstep/src/com/android/quickstep/SwipeUpSetting.java \
         ../src/com/android/launcher3/util/SecureSettingsObserver.java \
         ../src/com/android/launcher3/TestProtocol.java
 endif
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index e4dced5..98fb07f 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -64,7 +64,7 @@
                 "want to get app icon on all apps")) {
             final UiObject2 allAppsContainer = verifyActiveContainer();
             final UiObject2 navBar = mLauncher.waitForSystemUiObject("navigation_bar_frame");
-            allAppsContainer.setGestureMargins(0, 0, 0, navBar.getVisibleBounds().height());
+            allAppsContainer.setGestureMargins(0, 0, 0, navBar.getVisibleBounds().height() + 1);
             final BySelector appIconSelector = AppIcon.getAppIconSelector(appName, mLauncher);
             if (!hasClickableIcon(allAppsContainer, appIconSelector)) {
                 scrollBackToBeginning();
@@ -141,8 +141,8 @@
      * Flings forward (down) and waits the fling's end.
      */
     public void flingForward() {
-        try(LauncherInstrumentation.Closable c =
-                    mLauncher.addContextLayer("want to fling forward in all apps")) {
+        try (LauncherInstrumentation.Closable c =
+                     mLauncher.addContextLayer("want to fling forward in all apps")) {
             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);
@@ -156,8 +156,8 @@
      * Flings backward (up) and waits the fling's end.
      */
     public void flingBackward() {
-        try(LauncherInstrumentation.Closable c =
-                    mLauncher.addContextLayer("want to fling backward in all apps")) {
+        try (LauncherInstrumentation.Closable c =
+                     mLauncher.addContextLayer("want to fling backward in all apps")) {
             final UiObject2 allAppsContainer = verifyActiveContainer();
             // Start the gesture in the center, for symmetry with forward.
             allAppsContainer.setGestureMargins(0, mHeight / 2, 0, 0);
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index d60c79d..37a5031 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -354,12 +354,7 @@
                 log(action = "0-button: from another app");
                 assertTrue("Launcher is visible, don't know how to go home",
                         !mDevice.hasObject(By.pkg(getLauncherPackageName())));
-                final UiObject2 navBar = waitForSystemUiObject("navigation_bar_frame");
-
-                swipe(
-                        navBar.getVisibleBounds().centerX(), navBar.getVisibleBounds().centerY(),
-                        navBar.getVisibleBounds().centerX(), 0,
-                        BACKGROUND_APP_STATE_ORDINAL, ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME);
+                mDevice.pressHome();
             }
         } else {
             log(action = "clicking home button");