Merge "Only show collapse handle when the bottom widgets picker is scrollable" into sc-dev
diff --git a/quickstep/res/drawable/ic_ime_switcher.xml b/quickstep/res/drawable/ic_ime_switcher.xml
new file mode 100644
index 0000000..a86d390
--- /dev/null
+++ b/quickstep/res/drawable/ic_ime_switcher.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2021 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
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="20dp"
+    android:height="20dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:pathData="M19,7h2v2h-2V7zM15,7h2v2h-2V7zM3,7h2v2H3V7zM7,7h2v2H7V7zM11,7h2v2h-2V7zM19,11h2v2h-2V11zM15,11h2v2h-2V11zM3,11h2v2H3V11zM7,11h2v2H7V11zM11,11h2v2h-2V11zM7,15h10v2H7V15z"
+        android:fillColor="@android:color/white" />
+</vector>
diff --git a/quickstep/res/drawable/ic_sysbar_back.xml b/quickstep/res/drawable/ic_sysbar_back.xml
new file mode 100644
index 0000000..1eea677
--- /dev/null
+++ b/quickstep/res/drawable/ic_sysbar_back.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2021 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="28dp"
+    android:height="28dp"
+    android:autoMirrored="true"
+    android:viewportWidth="28"
+    android:viewportHeight="28">
+
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M6.49,14.86c-0.66-0.39-0.66-1.34,0-1.73l6.02-3.53l5.89-3.46C19.11,5.73,20,6.26,20,7.1V14v6.9 c0,0.84-0.89,1.37-1.6,0.95l-5.89-3.46L6.49,14.86z" />
+</vector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/ic_sysbar_home.xml b/quickstep/res/drawable/ic_sysbar_home.xml
new file mode 100644
index 0000000..b4b397b
--- /dev/null
+++ b/quickstep/res/drawable/ic_sysbar_home.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2021 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="28dp"
+    android:height="28dp"
+    android:viewportWidth="28"
+    android:viewportHeight="28">
+
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M 14 7 C 17.8659932488 7 21 10.1340067512 21 14 C 21 17.8659932488 17.8659932488 21 14 21 C 10.1340067512 21 7 17.8659932488 7 14 C 7 10.1340067512 10.1340067512 7 14 7 Z" />
+</vector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/ic_sysbar_recent.xml b/quickstep/res/drawable/ic_sysbar_recent.xml
new file mode 100644
index 0000000..f8c4778
--- /dev/null
+++ b/quickstep/res/drawable/ic_sysbar_recent.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2021 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="28dp"
+    android:height="28dp"
+    android:viewportWidth="28"
+    android:viewportHeight="28">
+
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M19.9,21.5H8.1c-0.88,0-1.6-0.72-1.6-1.6V8.1c0-0.88,0.72-1.6,1.6-1.6h11.8c0.88,0,1.6,0.72,1.6,1.6v11.8 C21.5,20.78,20.78,21.5,19.9,21.5z" />
+</vector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/taskbar_icon_click_feedback_roundrect.xml b/quickstep/res/drawable/taskbar_icon_click_feedback_roundrect.xml
new file mode 100644
index 0000000..d6160de
--- /dev/null
+++ b/quickstep/res/drawable/taskbar_icon_click_feedback_roundrect.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2021 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.
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/taskbar_icon_selection_ripple">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <solid android:color="@android:color/white" />
+            <corners android:radius="8dp" />
+        </shape>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml
index 732222a..240fe55 100644
--- a/quickstep/res/layout/taskbar.xml
+++ b/quickstep/res/layout/taskbar.xml
@@ -26,4 +26,10 @@
         android:layout_height="wrap_content"
         android:gravity="center"/>
 
+    <com.android.launcher3.taskbar.ImeBarView
+        android:id="@+id/ime_bar_view"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:visibility="gone"/>
+
 </com.android.launcher3.taskbar.TaskbarContainerView>
\ No newline at end of file
diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
index 3bc8ddc..167c7c3 100644
--- a/quickstep/res/values/colors.xml
+++ b/quickstep/res/values/colors.xml
@@ -27,4 +27,5 @@
 
     <!-- Taskbar -->
     <color name="taskbar_background">#101010</color>
+    <color name="taskbar_icon_selection_ripple">#E0E0E0</color>
 </resources>
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index cd22196..2aac877 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON;
 import static com.android.launcher3.LauncherState.FLAG_HIDE_BACK_BUTTON;
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.NO_OFFSET;
 import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
@@ -30,8 +31,11 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.ComponentName;
+import android.content.ServiceConnection;
 import android.os.Bundle;
 import android.os.CancellationSignal;
+import android.os.IBinder;
 import android.view.View;
 
 import androidx.annotation.Nullable;
@@ -61,6 +65,7 @@
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskUtils;
+import com.android.quickstep.TouchInteractionService;
 import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.util.RemoteFadeOutAnimationListener;
 import com.android.quickstep.util.SplitSelectStateController;
@@ -82,6 +87,8 @@
 
     private DepthController mDepthController = new DepthController(this);
     private QuickstepTransitionManager mAppTransitionManager;
+    private ServiceConnection mTisBinderConnection;
+    protected TouchInteractionService.TISBinder mTisBinder;
 
     /**
      * Reusable command for applying the back button alpha on the background thread.
@@ -103,6 +110,24 @@
         super.onCreate(savedInstanceState);
         SysUINavigationMode.INSTANCE.get(this).addModeChangeListener(this);
         addMultiWindowModeChangedListener(mDepthController);
+        setupTouchInteractionServiceBinder();
+    }
+
+    private void setupTouchInteractionServiceBinder() {
+        Intent intent = new Intent(this, TouchInteractionService.class);
+        mTisBinderConnection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName componentName, IBinder binder) {
+                mTisBinder = ((TouchInteractionService.TISBinder) binder);
+                mTisBinder.setTaskbarOverviewProxyDelegate(mTaskbarController);
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName componentName) {
+                mTisBinder = null;
+            }
+        };
+        bindService(intent, mTisBinderConnection, 0);
     }
 
     @Override
@@ -113,6 +138,10 @@
         if (mTaskbarController != null) {
             mTaskbarController.cleanup();
             mTaskbarController = null;
+            if (mTisBinder != null) {
+                mTisBinder.setTaskbarOverviewProxyDelegate(null);
+                unbindService(mTisBinderConnection);
+            }
         }
 
         super.onDestroy();
@@ -248,6 +277,9 @@
     private void addTaskbarIfNecessary() {
         if (mTaskbarController != null) {
             mTaskbarController.cleanup();
+            if (mTisBinder != null) {
+                mTisBinder.setTaskbarOverviewProxyDelegate(null);
+            }
             mTaskbarController = null;
         }
         if (mDeviceProfile.isTaskbarPresent) {
@@ -256,6 +288,9 @@
             mTaskbarController = new TaskbarController(this,
                     taskbarActivityContext.getTaskbarContainerView(), taskbarViewOnHome);
             mTaskbarController.init();
+            if (mTisBinder != null) {
+                mTisBinder.setTaskbarOverviewProxyDelegate(mTaskbarController);
+            }
         }
     }
 
@@ -343,7 +378,7 @@
     @Override
     public float[] getNormalOverviewScaleAndOffset() {
         return SysUINavigationMode.getMode(this).hasGestures
-                ? new float[] {1, 1} : new float[] {1.1f, 0};
+                ? new float[] {1, NO_OFFSET, 1} : new float[] {1.1f, NO_OFFSET, NO_OFFSET};
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index b0c13f9..a9c2a5e 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -18,7 +18,6 @@
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static android.text.format.DateUtils.formatElapsedTime;
 
-import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
@@ -252,11 +251,9 @@
     }
 
     @Override
-    public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
-        if ((changeFlags & CHANGE_FLAG_GRID) != 0) {
-            // Reinitialize everything
-            Executors.MODEL_EXECUTOR.execute(this::recreatePredictors);
-        }
+    public void onIdpChanged(InvariantDeviceProfile profile) {
+        // Reinitialize everything
+        Executors.MODEL_EXECUTOR.execute(this::recreatePredictors);
     }
 
     private void onAppTargetEvent(AppTargetEvent event, int client) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java b/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java
new file mode 100644
index 0000000..0d4130d
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2021 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.taskbar;
+
+import android.annotation.DrawableRes;
+import android.content.Context;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.launcher3.R;
+
+/**
+ * Creates Buttons for Taskbar for 3 button nav.
+ * Can add animations and state management for buttons in this class as things progress.
+ */
+public class ButtonProvider {
+
+    private int mMarginLeftRight;
+    private final Context mContext;
+
+    public ButtonProvider(Context context) {
+        mContext = context;
+    }
+
+    public void setMarginLeftRight(int margin) {
+        mMarginLeftRight = margin;
+    }
+
+    public View getBack() {
+        // Back button
+        return getButtonForDrawable(R.drawable.ic_sysbar_back);
+    }
+
+    public View getDown() {
+        // Ime down button
+        return getButtonForDrawable(R.drawable.ic_sysbar_back);
+    }
+
+    public View getHome() {
+        // Home button
+        return getButtonForDrawable(R.drawable.ic_sysbar_home);
+    }
+
+    public View getRecents() {
+        // Recents button
+        return getButtonForDrawable(R.drawable.ic_sysbar_recent);
+    }
+
+    public View getImeSwitcher() {
+        // IME Switcher Button
+        return getButtonForDrawable(R.drawable.ic_ime_switcher);
+    }
+
+    private View getButtonForDrawable(@DrawableRes int drawableId) {
+        ImageView buttonView = new ImageView(mContext);
+        buttonView.setImageResource(drawableId);
+        buttonView.setBackgroundResource(R.drawable.taskbar_icon_click_feedback_roundrect);
+        buttonView.setPadding(mMarginLeftRight, 0, mMarginLeftRight, 0);
+        return buttonView;
+    }
+
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java b/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java
new file mode 100644
index 0000000..bb3669b
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2021 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.taskbar;
+
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.RelativeLayout;
+
+import com.android.launcher3.views.ActivityContext;
+
+public class ImeBarView extends RelativeLayout {
+
+    private ButtonProvider mButtonProvider;
+    private TaskbarController.TaskbarViewCallbacks mControllerCallbacks;
+    private View mImeView;
+
+    public ImeBarView(Context context) {
+        this(context, null);
+    }
+
+    public ImeBarView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ImeBarView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public void construct(ButtonProvider buttonProvider) {
+        mButtonProvider = buttonProvider;
+    }
+
+    public void init(TaskbarController.TaskbarViewCallbacks taskbarCallbacks) {
+        mControllerCallbacks = taskbarCallbacks;
+        ActivityContext context = getActivityContext();
+        RelativeLayout.LayoutParams imeParams = new RelativeLayout.LayoutParams(
+                context.getDeviceProfile().iconSizePx,
+                context.getDeviceProfile().iconSizePx
+        );
+        RelativeLayout.LayoutParams downParams = new RelativeLayout.LayoutParams(imeParams);
+
+        imeParams.addRule(ALIGN_PARENT_END);
+        imeParams.setMarginEnd(context.getDeviceProfile().iconSizePx);
+        downParams.setMarginStart(context.getDeviceProfile().iconSizePx);
+        downParams.addRule(ALIGN_PARENT_START);
+
+        // Down Arrow
+        View downView = mButtonProvider.getDown();
+        downView.setOnClickListener(view -> mControllerCallbacks.onNavigationButtonClick(
+                BUTTON_BACK));
+        downView.setLayoutParams(downParams);
+        downView.setRotation(-90);
+        addView(downView);
+
+        // IME switcher button
+        mImeView = mButtonProvider.getImeSwitcher();
+        mImeView.setOnClickListener(view -> mControllerCallbacks.onNavigationButtonClick(
+                BUTTON_IME_SWITCH));
+        mImeView.setLayoutParams(imeParams);
+        addView(mImeView);
+    }
+
+    public void cleanup() {
+        removeAllViews();
+    }
+
+    public void setImeSwitcherVisibility(boolean show) {
+        mImeView.setVisibility(show ? VISIBLE : GONE);
+    }
+
+    private <T extends Context & ActivityContext> T getActivityContext() {
+        return ActivityContext.lookupContext(getContext());
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java
index 46e4506..29f6935 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java
@@ -44,7 +44,7 @@
     private final AnimatedFloat mTaskbarVisibilityAlphaForLauncherState = new AnimatedFloat(
             this::updateVisibilityAlpha);
     private final AnimatedFloat mTaskbarVisibilityAlphaForIme = new AnimatedFloat(
-            this::updateVisibilityAlpha);
+            this::updateVisibilityAlphaForIme);
 
     // Scale.
     private final AnimatedFloat mTaskbarScaleForLauncherState = new AnimatedFloat(
@@ -110,16 +110,22 @@
         // We use mTaskbarBackgroundAlpha as a proxy for whether Launcher is resumed/paused, the
         // assumption being that Taskbar should always be visible regardless of the current
         // LauncherState if Launcher is paused.
+        float alphaDueToIme = mTaskbarVisibilityAlphaForIme.value;
         float alphaDueToLauncher = Math.max(mTaskbarBackgroundAlpha.value,
                 mTaskbarVisibilityAlphaForLauncherState.value);
-        float alphaDueToOther = mTaskbarVisibilityAlphaForIme.value;
-        float taskbarAlpha = alphaDueToLauncher * alphaDueToOther;
+        float taskbarAlpha = alphaDueToLauncher * alphaDueToIme;
         mTaskbarCallbacks.updateTaskbarVisibilityAlpha(taskbarAlpha);
 
         // Make the nav bar invisible if taskbar is visible.
         setNavBarButtonAlpha(1f - taskbarAlpha);
     }
 
+    private void updateVisibilityAlphaForIme() {
+        updateVisibilityAlpha();
+        float taskbarAlphaDueToIme = mTaskbarVisibilityAlphaForIme.value;
+        mTaskbarCallbacks.updateImeBarVisibilityAlpha(1f - taskbarAlphaDueToIme);
+    }
+
     private void updateScale() {
         // We use mTaskbarBackgroundAlpha as a proxy for whether Launcher is resumed/paused, the
         // assumption being that Taskbar should always be at scale 1f regardless of the current
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
index c93de00..6084e10 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.taskbar;
 
+import static android.view.View.GONE;
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -28,6 +31,7 @@
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.inputmethodservice.InputMethodService;
 import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.View;
@@ -48,9 +52,12 @@
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.views.ActivityContext;
 import com.android.quickstep.AnimatedFloat;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.TouchInteractionService.TaskbarOverviewProxyDelegate;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.WindowManagerWrapper;
@@ -58,13 +65,15 @@
 /**
  * Interfaces with Launcher/WindowManager/SystemUI to determine what to show in TaskbarView.
  */
-public class TaskbarController {
+public class TaskbarController implements TaskbarOverviewProxyDelegate {
 
     private static final String WINDOW_TITLE = "Taskbar";
 
     private final TaskbarContainerView mTaskbarContainerView;
     private final TaskbarView mTaskbarViewInApp;
     private final TaskbarView mTaskbarViewOnHome;
+    private final ImeBarView mImeBarView;
+
     private final BaseQuickstepLauncher mLauncher;
     private final WindowManager mWindowManager;
     // Layout width and height of the Taskbar in the default state.
@@ -73,9 +82,13 @@
     private final TaskbarAnimationController mTaskbarAnimationController;
     private final TaskbarHotseatController mHotseatController;
     private final TaskbarDragController mDragController;
+    private final TaskbarNavButtonController mNavButtonController;
 
     // Initialized in init().
     private WindowManager.LayoutParams mWindowLayoutParams;
+    private SysUINavigationMode.Mode mNavMode = SysUINavigationMode.Mode.NO_BUTTON;
+    private final SysUINavigationMode.NavigationModeChangeListener mNavigationModeChangeListener =
+            this::onNavModeChanged;
 
     private @Nullable Animator mAnimator;
     private boolean mIsAnimatingToLauncher;
@@ -85,10 +98,14 @@
         mLauncher = launcher;
         mTaskbarContainerView = taskbarContainerView;
         mTaskbarContainerView.construct(createTaskbarContainerViewCallbacks());
+        ButtonProvider buttonProvider = new ButtonProvider(launcher);
         mTaskbarViewInApp = mTaskbarContainerView.findViewById(R.id.taskbar_view);
-        mTaskbarViewInApp.construct(createTaskbarViewCallbacks());
+        mTaskbarViewInApp.construct(createTaskbarViewCallbacks(), buttonProvider);
         mTaskbarViewOnHome = taskbarViewOnHome;
-        mTaskbarViewOnHome.construct(createTaskbarViewCallbacks());
+        mTaskbarViewOnHome.construct(createTaskbarViewCallbacks(), buttonProvider);
+        mImeBarView = mTaskbarContainerView.findViewById(R.id.ime_bar_view);
+        mImeBarView.construct(buttonProvider);
+        mNavButtonController = new TaskbarNavButtonController(launcher);
         mWindowManager = mLauncher.getWindowManager();
         mTaskbarSize = new Point(MATCH_PARENT, mLauncher.getDeviceProfile().taskbarSize);
         mTaskbarStateHandler = mLauncher.getTaskbarStateHandler();
@@ -108,11 +125,21 @@
 
             @Override
             public void updateTaskbarVisibilityAlpha(float alpha) {
-                mTaskbarContainerView.setAlpha(alpha);
+                mTaskbarViewInApp.setAlpha(alpha);
                 mTaskbarViewOnHome.setAlpha(alpha);
             }
 
             @Override
+            public void updateImeBarVisibilityAlpha(float alpha) {
+                if (mNavMode != SysUINavigationMode.Mode.THREE_BUTTONS) {
+                    // TODO Remove sysui IME bar for gesture nav as well
+                    return;
+                }
+                mImeBarView.setAlpha(alpha);
+                mImeBarView.setVisibility(alpha == 0 ? GONE : VISIBLE);
+            }
+
+            @Override
             public void updateTaskbarScale(float scale) {
                 mTaskbarViewInApp.setScaleX(scale);
                 mTaskbarViewInApp.setScaleY(scale);
@@ -136,16 +163,21 @@
         return new TaskbarContainerViewCallbacks() {
             @Override
             public void onViewRemoved() {
-                if (mTaskbarContainerView.getChildCount() == 1) {
-                    // Only TaskbarView remains.
-                    setTaskbarWindowFullscreen(false);
+                // Ensure no other children present (like Folders, etc)
+                for (int i = 0; i < mTaskbarContainerView.getChildCount(); i++) {
+                    View v = mTaskbarContainerView.getChildAt(i);
+                    if (!((v instanceof TaskbarView) || (v instanceof ImeBarView))){
+                        return;
+                    }
                 }
+                setTaskbarWindowFullscreen(false);
             }
 
             @Override
             public boolean isTaskbarTouchable() {
                 return mTaskbarContainerView.getAlpha() > AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD
-                        && mTaskbarViewInApp.getVisibility() == View.VISIBLE
+                        && (mTaskbarViewInApp.getVisibility() == VISIBLE
+                            || mImeBarView.getVisibility() == VISIBLE)
                         && !mIsAnimatingToLauncher;
             }
         };
@@ -198,7 +230,7 @@
                 // space so that the others line up with the home screen hotseat.
                 boolean isOnHomeScreen = taskbarView == mTaskbarViewOnHome
                         || mLauncher.hasBeenResumed() || mIsAnimatingToLauncher;
-                return isOnHomeScreen ? View.INVISIBLE : View.GONE;
+                return isOnHomeScreen ? INVISIBLE : GONE;
             }
 
             @Override
@@ -212,6 +244,11 @@
                     alignRealHotseatWithTaskbar();
                 }
             }
+
+            @Override
+            public void onNavigationButtonClick(@TaskbarButton int buttonType) {
+                mNavButtonController.onButtonClick(buttonType);
+            }
         };
     }
 
@@ -228,9 +265,12 @@
      * Initializes the Taskbar, including adding it to the screen.
      */
     public void init() {
-        mTaskbarViewInApp.init(mHotseatController.getNumHotseatIcons());
-        mTaskbarViewOnHome.init(mHotseatController.getNumHotseatIcons());
+        mNavMode = SysUINavigationMode.INSTANCE.get(mLauncher)
+                .addModeChangeListener(mNavigationModeChangeListener);
+        mTaskbarViewInApp.init(mHotseatController.getNumHotseatIcons(), mNavMode);
+        mTaskbarViewOnHome.init(mHotseatController.getNumHotseatIcons(), mNavMode);
         mTaskbarContainerView.init(mTaskbarViewInApp);
+        mImeBarView.init(createTaskbarViewCallbacks());
         addToWindowManager();
         mTaskbarStateHandler.setTaskbarCallbacks(createTaskbarStateHandlerCallbacks());
         mTaskbarAnimationController.init();
@@ -272,12 +312,15 @@
         mTaskbarViewInApp.cleanup();
         mTaskbarViewOnHome.cleanup();
         mTaskbarContainerView.cleanup();
+        mImeBarView.cleanup();
         removeFromWindowManager();
         mTaskbarStateHandler.setTaskbarCallbacks(null);
         mTaskbarAnimationController.cleanup();
         mHotseatController.cleanup();
 
         setWhichTaskbarViewIsVisible(null);
+        SysUINavigationMode.INSTANCE.get(mLauncher)
+                .removeModeChangeListener(mNavigationModeChangeListener);
     }
 
     private void removeFromWindowManager() {
@@ -315,6 +358,12 @@
         mWindowManager.addView(mTaskbarContainerView, mWindowLayoutParams);
     }
 
+    private void onNavModeChanged(SysUINavigationMode.Mode newMode) {
+        mNavMode = newMode;
+        cleanup();
+        init();
+    }
+
     /**
      * Should be called from onResume() and onPause(), and animates the Taskbar accordingly.
      */
@@ -387,6 +436,28 @@
      */
     public void setIsImeVisible(boolean isImeVisible) {
         mTaskbarAnimationController.animateToVisibilityForIme(isImeVisible ? 0 : 1);
+        blockTaskbarTouchesForIme(isImeVisible);
+    }
+
+    /**
+     * When in 3 button nav, the above doesn't get called since we prevent sysui nav bar from
+     * instantiating at all, which is what's responsible for sending sysui state flags over.
+     *
+     * @param vis IME visibility flag
+     * @param backDisposition Used to determine back button behavior for software keyboard
+     *                        See BACK_DISPOSITION_* constants in {@link InputMethodService}
+     */
+    public void updateImeStatus(int displayId, int vis, int backDisposition,
+            boolean showImeSwitcher) {
+        if (displayId != mTaskbarContainerView.getContext().getDisplayId() ||
+                mNavMode != SysUINavigationMode.Mode.THREE_BUTTONS) {
+            return;
+        }
+
+        boolean imeVisible = (vis & InputMethodService.IME_VISIBLE) != 0;
+        mTaskbarAnimationController.animateToVisibilityForIme(imeVisible ? 0 : 1);
+        mImeBarView.setImeSwitcherVisibility(showImeSwitcher);
+        blockTaskbarTouchesForIme(imeVisible);
     }
 
     /**
@@ -436,12 +507,17 @@
 
     private void setWhichTaskbarViewIsVisible(@Nullable TaskbarView visibleTaskbar) {
         mTaskbarViewInApp.setVisibility(visibleTaskbar == mTaskbarViewInApp
-                ? View.VISIBLE : View.INVISIBLE);
+                ? VISIBLE : INVISIBLE);
         mTaskbarViewOnHome.setVisibility(visibleTaskbar == mTaskbarViewOnHome
-                ? View.VISIBLE : View.INVISIBLE);
+                ? VISIBLE : INVISIBLE);
         mLauncher.getHotseat().setIconsAlpha(visibleTaskbar != mTaskbarViewInApp ? 1f : 0f);
     }
 
+    private void blockTaskbarTouchesForIme(boolean block) {
+        mTaskbarViewOnHome.setTouchesEnabled(!block);
+        mTaskbarViewInApp.setTouchesEnabled(!block);
+    }
+
     /**
      * Returns the ratio of the taskbar icon size on home vs in an app.
      */
@@ -485,6 +561,7 @@
     protected interface TaskbarAnimationControllerCallbacks {
         void updateTaskbarBackgroundAlpha(float alpha);
         void updateTaskbarVisibilityAlpha(float alpha);
+        void updateImeBarVisibilityAlpha(float alpha);
         void updateTaskbarScale(float scale);
         void updateTaskbarTranslationY(float translationY);
     }
@@ -507,6 +584,7 @@
         /** Returns how much to scale non-icon elements such as spacing and dividers. */
         float getNonIconScale(TaskbarView taskbarView);
         void onItemPositionsChanged(TaskbarView taskbarView);
+        void onNavigationButtonClick(@TaskbarButton int buttonType);
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
new file mode 100644
index 0000000..54e1610
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2021 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.taskbar;
+
+import android.content.Context;
+import android.content.Intent;
+import android.view.inputmethod.InputMethodManager;
+
+import androidx.annotation.IntDef;
+
+import com.android.quickstep.OverviewCommandHelper;
+import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.TouchInteractionService;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Controller for 3 button mode in the taskbar.
+ * Handles all the functionality of the various buttons, making/routing the right calls into
+ * launcher or sysui/system.
+ *
+ * TODO: Create callbacks to hook into UI layer since state will change for more context buttons/
+ *       assistant invocation.
+ */
+public class TaskbarNavButtonController {
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            BUTTON_BACK,
+            BUTTON_HOME,
+            BUTTON_RECENTS,
+            BUTTON_IME_SWITCH
+    })
+
+    public @interface TaskbarButton {}
+
+    static final int BUTTON_BACK = 1;
+    static final int BUTTON_HOME = BUTTON_BACK << 1;
+    static final int BUTTON_RECENTS = BUTTON_HOME << 1;
+    static final int BUTTON_IME_SWITCH = BUTTON_RECENTS << 1;
+
+
+    private final Context mContext;
+
+    public TaskbarNavButtonController(Context context) {
+        mContext = context;
+    }
+
+    public void onButtonClick(@TaskbarButton int buttonType) {
+        switch (buttonType) {
+            case BUTTON_BACK:
+                executeBack();
+                break;
+            case BUTTON_HOME:
+                navigateHome();
+                break;
+            case BUTTON_RECENTS:
+                navigateToOverview();;
+                break;
+            case BUTTON_IME_SWITCH:
+                showIMESwitcher();
+                break;
+        }
+    }
+
+    private void navigateHome() {
+        mContext.startActivity(new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_HOME)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+    }
+
+    private void navigateToOverview() {
+        TouchInteractionService.getInstance().getOverviewCommandHelper()
+                .addCommand(OverviewCommandHelper.TYPE_SHOW);
+    }
+
+    private void executeBack() {
+        SystemUiProxy.INSTANCE.getNoCreate().onBackPressed();
+    }
+
+    private void showIMESwitcher() {
+        mContext.getSystemService(InputMethodManager.class).showInputMethodPickerFromSystem(
+                true /* showAuxiliarySubtypes */, mContext.getDisplayId());
+    }
+
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 60a7add..9e8013e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -15,6 +15,10 @@
  */
 package com.android.launcher3.taskbar;
 
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
+
 import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.LayoutTransition;
@@ -24,8 +28,10 @@
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.os.SystemProperties;
 import android.util.AttributeSet;
 import android.view.DragEvent;
+import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
@@ -45,12 +51,17 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.views.ActivityContext;
+import com.android.quickstep.SysUINavigationMode;
 
 /**
  * Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
  */
 public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconParent, Insettable {
 
+
+    private static final boolean ENABLE_THREE_BUTTON_TASKBAR =
+            SystemProperties.getBoolean("persist.debug.taskbar_three_button", false);
+
     private final int mIconTouchSize;
     private final boolean mIsRtl;
     private final int mTouchSlop;
@@ -68,15 +79,22 @@
     private LayoutTransition mLayoutTransition;
     private int mHotseatStartIndex;
     private int mHotseatEndIndex;
+    private LinearLayout mButtonRegion;
 
     // Delegate touches to the closest view if within mIconTouchSize.
     private boolean mDelegateTargeted;
     private View mDelegateView;
+    // Prevents dispatching touches to children if true
+    private boolean mTouchEnabled = true;
 
     private boolean mIsDraggingItem;
     // Only non-null when the corresponding Folder is open.
     private @Nullable FolderIcon mLeaveBehindFolderIcon;
 
+    private int mNavButtonStartIndex;
+    /** Provider of buttons added to taskbar in 3 button nav */
+    private ButtonProvider mButtonProvider;
+
     public TaskbarView(@NonNull Context context) {
         this(context, null);
     }
@@ -100,15 +118,28 @@
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
     }
 
-    protected void construct(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks) {
+    protected void construct(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks,
+            ButtonProvider buttonProvider) {
         mControllerCallbacks = taskbarViewCallbacks;
         mNonIconScale = mControllerCallbacks.getNonIconScale(this);
         mItemMarginLeftRight = getResources().getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
         mItemMarginLeftRight = Math.round(mItemMarginLeftRight * mNonIconScale);
+        mButtonProvider = buttonProvider;
+        mButtonProvider.setMarginLeftRight(mItemMarginLeftRight);
     }
 
-    protected void init(int numHotseatIcons) {
-        mHotseatStartIndex = 0;
+    protected void init(int numHotseatIcons, SysUINavigationMode.Mode newMode) {
+        // TODO: check if buttons on left
+        if (newMode == SysUINavigationMode.Mode.THREE_BUTTONS && ENABLE_THREE_BUTTON_TASKBAR) {
+            // 3 button
+            mNavButtonStartIndex = 0;
+            createNavButtons();
+        } else {
+            mNavButtonStartIndex = -1;
+            removeNavButtons();
+        }
+
+        mHotseatStartIndex = mNavButtonStartIndex + 1;
         mHotseatEndIndex = mHotseatStartIndex + numHotseatIcons - 1;
         updateHotseatItems(new ItemInfo[numHotseatIcons]);
 
@@ -185,11 +216,11 @@
             if (hotseatView == null || hotseatView.getSourceLayoutResId() != expectedLayoutResId
                     || needsReinflate) {
                 removeView(hotseatView);
-                ActivityContext activityContext = ActivityContext.lookupContext(getContext());
+                ActivityContext activityContext = getActivityContext();
                 if (isFolder) {
                     FolderInfo folderInfo = (FolderInfo) hotseatItemInfo;
                     FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId,
-                            ActivityContext.lookupContext(getContext()), this, folderInfo);
+                            getActivityContext(), this, folderInfo);
                     folderIcon.setTextVisible(false);
                     hotseatView = folderIcon;
                 } else {
@@ -244,11 +275,23 @@
     }
 
     @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (!mTouchEnabled) {
+            return true;
+        }
+        return super.dispatchTouchEvent(ev);
+    }
+
+    @Override
     public boolean onTouchEvent(MotionEvent event) {
         boolean handled = delegateTouchIfNecessary(event);
         return super.onTouchEvent(event) || handled;
     }
 
+    public void setTouchesEnabled(boolean touchEnabled) {
+        this.mTouchEnabled = touchEnabled;
+    }
+
     /**
      * User touched the Taskbar background. Determine whether the touch is close enough to a view
      * that we should forward the touches to it.
@@ -335,12 +378,57 @@
         return findDelegateView(xInOurCoordinates, yInOurCoorindates) != null;
     }
 
+    private void removeNavButtons() {
+        if (mButtonRegion != null) {
+            mButtonRegion.removeAllViews();
+            removeView(mButtonRegion);
+        } // else We've never been in 3 button. Woah Scoob!
+    }
+
+    /**
+     * Add back/home/recents buttons into a single ViewGroup that will be inserted at
+     * {@param navButtonStartIndex}
+     */
+    private void createNavButtons() {
+        ActivityContext context = getActivityContext();
+        if (mButtonRegion == null) {
+            mButtonRegion = new LinearLayout(getContext());
+        } else {
+            mButtonRegion.removeAllViews();
+        }
+        mButtonRegion.setVisibility(VISIBLE);
+
+        LinearLayout.LayoutParams buttonParams = new LinearLayout.LayoutParams(
+                context.getDeviceProfile().iconSizePx,
+                context.getDeviceProfile().iconSizePx
+        );
+        buttonParams.gravity = Gravity.CENTER;
+
+        View backButton = mButtonProvider.getBack();
+        backButton.setOnClickListener(view -> mControllerCallbacks.onNavigationButtonClick(
+                BUTTON_BACK));
+        mButtonRegion.addView(backButton, buttonParams);
+
+        // Home button
+        View homeButton = mButtonProvider.getHome();
+        homeButton.setOnClickListener(view -> mControllerCallbacks.onNavigationButtonClick(
+                BUTTON_HOME));
+        mButtonRegion.addView(homeButton, buttonParams);
+
+        View recentsButton = mButtonProvider.getRecents();
+        recentsButton.setOnClickListener(view -> mControllerCallbacks.onNavigationButtonClick(
+                BUTTON_RECENTS));
+        mButtonRegion.addView(recentsButton, buttonParams);
+
+        addView(mButtonRegion, mNavButtonStartIndex);
+    }
+
     @Override
     public boolean onDragEvent(DragEvent event) {
         switch (event.getAction()) {
             case DragEvent.ACTION_DRAG_STARTED:
                 mIsDraggingItem = true;
-                AbstractFloatingView.closeAllOpenViews(ActivityContext.lookupContext(getContext()));
+                AbstractFloatingView.closeAllOpenViews(getActivityContext());
                 return true;
             case DragEvent.ACTION_DRAG_ENDED:
                 mIsDraggingItem = false;
@@ -407,12 +495,15 @@
     }
 
     private View inflate(@LayoutRes int layoutResId) {
-        return ActivityContext.lookupContext(getContext()).getLayoutInflater()
-                .inflate(layoutResId, this, false);
+        return getActivityContext().getLayoutInflater().inflate(layoutResId, this, false);
     }
 
     @Override
     public void setInsets(Rect insets) {
         // Ignore, we just implement Insettable to draw behind system insets.
     }
+
+    private <T extends Context & ActivityContext> T getActivityContext() {
+        return ActivityContext.lookupContext(getContext());
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index b3374f3..e508690 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -22,8 +22,10 @@
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
-import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_VERTICAL_OFFSET;
 import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
 import static com.android.quickstep.views.RecentsView.TASK_PRIMARY_SPLIT_TRANSLATION;
@@ -62,7 +64,8 @@
     public void setState(@NonNull LauncherState state) {
         float[] scaleAndOffset = state.getOverviewScaleAndOffset(mLauncher);
         RECENTS_SCALE_PROPERTY.set(mRecentsView, scaleAndOffset[0]);
-        ADJACENT_PAGE_OFFSET.set(mRecentsView, scaleAndOffset[1]);
+        ADJACENT_PAGE_HORIZONTAL_OFFSET.set(mRecentsView, scaleAndOffset[1]);
+        ADJACENT_PAGE_VERTICAL_OFFSET.set(mRecentsView, scaleAndOffset[2]);
         TASK_SECONDARY_TRANSLATION.set(mRecentsView, 0f);
 
         getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
@@ -92,8 +95,10 @@
         float[] scaleAndOffset = toState.getOverviewScaleAndOffset(mLauncher);
         setter.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0],
                 config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR));
-        setter.setFloat(mRecentsView, ADJACENT_PAGE_OFFSET, scaleAndOffset[1],
+        setter.setFloat(mRecentsView, ADJACENT_PAGE_HORIZONTAL_OFFSET, scaleAndOffset[1],
                 config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR));
+        setter.setFloat(mRecentsView, ADJACENT_PAGE_VERTICAL_OFFSET, scaleAndOffset[2],
+                config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
         PagedOrientationHandler orientationHandler =
                 ((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler();
         FloatProperty taskViewsFloat = orientationHandler.getSplitSelectTaskOffset(
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index a81bdd5..d822c8c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -84,11 +84,6 @@
     }
 
     @Override
-    public float[] getOverviewScaleAndOffset(Launcher launcher) {
-        return new float[] {0.9f, 1};
-    }
-
-    @Override
     public LauncherState getHistoryForState(LauncherState previousState) {
         return previousState == OVERVIEW ? OVERVIEW : NORMAL;
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 77c2611..06ffae4 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -96,6 +96,6 @@
             BaseDraggingActivity activity) {
         return new float[] {
                 ((RecentsView) activity.getOverviewPanel()).getMaxScaleForFullScreen(),
-                NO_OFFSET};
+                NO_OFFSET, NO_OFFSET};
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
index 6f084a1..1fc288f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -78,6 +78,6 @@
         float scale = Math.min((float) modalTaskSize.height() / taskSize.y,
                 (float) modalTaskSize.width() / taskSize.x);
 
-        return new float[] {scale, NO_OFFSET};
+        return new float[] {scale, NO_OFFSET, NO_OFFSET};
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 135c478..c9cfad3 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -17,7 +17,6 @@
 
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
-import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 
 import android.content.Context;
 import android.graphics.Rect;
@@ -60,9 +59,8 @@
 
     @Override
     public int getTransitionDuration(Context context) {
-        // In no-button mode, overview comes in all the way from the left, so give it more time.
-        boolean isNoButtonMode = SysUINavigationMode.INSTANCE.get(context).getMode() == NO_BUTTON;
-        return isNoButtonMode ? 380 : 250;
+        // In gesture modes, overview comes in all the way from the bottom, so give it more time.
+        return SysUINavigationMode.INSTANCE.get(context).getMode().hasGestures ? 380 : 250;
     }
 
     @Override
@@ -80,7 +78,7 @@
 
     @Override
     public float[] getOverviewScaleAndOffset(Launcher launcher) {
-        return new float[] {NO_SCALE, NO_OFFSET};
+        return new float[] {NO_SCALE, NO_OFFSET, NO_OFFSET};
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 3ac7866..adc6b18 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -23,34 +23,34 @@
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.WorkspaceStateTransitionAnimation.getSpringScaleAnimator;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
-import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
 import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
 import static com.android.launcher3.anim.Interpolators.INSTANT;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_SCRIM_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
-import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
 
 import android.animation.ValueAnimator;
-import android.view.View;
 
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Workspace;
-import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.quickstep.SysUINavigationMode;
@@ -65,6 +65,12 @@
 
     // Scale recents takes before animating in
     private static final float RECENTS_PREPARE_SCALE = 1.33f;
+    // Scale workspace takes before animating in
+    private static final float WORKSPACE_PREPARE_SCALE_GESTURES = 0.97f;
+    private static final float WORKSPACE_PREPARE_SCALE_BUTTONS = 0.92f;
+    // When the overview to home transition reaches this percentage, immediately hide overview and
+    // start animating away the scrim and animating in workspace.
+    private static final float OVERVIEW_TO_HOME_HARD_HAND_OFF = 0.4f;
 
     // Due to use of physics, duration may differ between devices so we need to calculate and
     // cache the value.
@@ -79,21 +85,31 @@
             StateAnimationConfig config) {
         RecentsView overview = mActivity.getOverviewPanel();
         if (toState == NORMAL && fromState == OVERVIEW) {
-            config.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL);
-            config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
-            config.setInterpolator(ANIM_ALL_APPS_FADE, ACCEL);
-            config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
-            config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL_DEACCEL);
-
-            if (SysUINavigationMode.getMode(mActivity) == NO_BUTTON) {
-                // Scrolling in tasks, so make visible straight away
-                if (overview.getTaskViewCount() > 0) {
-                    config.setInterpolator(ANIM_OVERVIEW_FADE, FINAL_FRAME);
-                } else {
-                    config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
-                }
+            final float workspacePrepareScale;
+            if (SysUINavigationMode.getMode(mActivity).hasGestures
+                    && overview.getTaskViewCount() > 0) {
+                workspacePrepareScale = WORKSPACE_PREPARE_SCALE_GESTURES;
+                // Overview is going offscreen, so keep it at its current scale and opacity.
+                config.setInterpolator(ANIM_OVERVIEW_SCALE, FINAL_FRAME);
+                config.setInterpolator(ANIM_OVERVIEW_FADE, clampToProgress(
+                        FINAL_FRAME, 0f, OVERVIEW_TO_HOME_HARD_HAND_OFF));
+                config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, clampToProgress(
+                        DEACCEL, 0f, OVERVIEW_TO_HOME_HARD_HAND_OFF));
+                config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, AGGRESSIVE_EASE_IN_OUT);
+                config.setInterpolator(ANIM_SCRIM_FADE, clampToProgress(
+                        DEACCEL, OVERVIEW_TO_HOME_HARD_HAND_OFF, 1f));
+                config.setInterpolator(ANIM_WORKSPACE_SCALE, clampToProgress(
+                        DEACCEL, OVERVIEW_TO_HOME_HARD_HAND_OFF, 1f));
+                config.setInterpolator(ANIM_WORKSPACE_FADE, clampToProgress(
+                        INSTANT, OVERVIEW_TO_HOME_HARD_HAND_OFF, 1f));
             } else {
+                workspacePrepareScale = WORKSPACE_PREPARE_SCALE_BUTTONS;
+                config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
                 config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
+                config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, LINEAR);
+                config.setInterpolator(ANIM_SCRIM_FADE, LINEAR);
+                config.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL);
+                config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
             }
 
             Workspace workspace = mActivity.getWorkspace();
@@ -106,25 +122,18 @@
                         && currentChild.getShortcutsAndWidgets().getAlpha() > 0;
             }
             if (!isWorkspaceVisible) {
-                workspace.setScaleX(0.92f);
-                workspace.setScaleY(0.92f);
+                workspace.setScaleX(workspacePrepareScale);
+                workspace.setScaleY(workspacePrepareScale);
             }
             Hotseat hotseat = mActivity.getHotseat();
             boolean isHotseatVisible = hotseat.getVisibility() == VISIBLE && hotseat.getAlpha() > 0;
             if (!isHotseatVisible) {
-                hotseat.setScaleX(0.92f);
-                hotseat.setScaleY(0.92f);
-                AllAppsContainerView qsbContainer = mActivity.getAppsView();
-                View qsb = qsbContainer.getSearchView();
-                boolean qsbVisible = qsb.getVisibility() == VISIBLE && qsb.getAlpha() > 0;
-                if (!qsbVisible) {
-                    qsbContainer.setScaleX(0.92f);
-                    qsbContainer.setScaleY(0.92f);
-                }
+                hotseat.setScaleX(workspacePrepareScale);
+                hotseat.setScaleY(workspacePrepareScale);
             }
         } else if ((fromState == NORMAL || fromState == HINT_STATE
                 || fromState == HINT_STATE_TWO_BUTTON) && toState == OVERVIEW) {
-            if (SysUINavigationMode.getMode(mActivity) == NO_BUTTON) {
+            if (SysUINavigationMode.getMode(mActivity).hasGestures) {
                 config.setInterpolator(ANIM_WORKSPACE_SCALE,
                         fromState == NORMAL ? ACCEL : OVERSHOOT_1_2);
                 config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL);
@@ -148,6 +157,10 @@
             config.setInterpolator(ANIM_ALL_APPS_FADE, OVERSHOOT_1_2);
             config.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
             config.setInterpolator(ANIM_DEPTH, OVERSHOOT_1_2);
+            config.setInterpolator(ANIM_SCRIM_FADE, t -> {
+                // Animate at the same rate until reaching progress 1, and skip the overshoot.
+                return Math.min(1, OVERSHOOT_1_2.getInterpolation(t));
+            });
             config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_2);
             config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, OVERSHOOT_1_2);
         } else if (fromState == HINT_STATE && toState == NORMAL) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 8278a5a..0f64abc 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -41,7 +41,8 @@
 import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP;
 import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
-import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_VERTICAL_OFFSET;
 import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
@@ -221,7 +222,8 @@
 
         // Set RecentView's initial properties.
         RECENTS_SCALE_PROPERTY.set(mRecentsView, fromState.getOverviewScaleAndOffset(mLauncher)[0]);
-        ADJACENT_PAGE_OFFSET.set(mRecentsView, 1f);
+        ADJACENT_PAGE_HORIZONTAL_OFFSET.set(mRecentsView, 1f);
+        ADJACENT_PAGE_VERTICAL_OFFSET.set(mRecentsView, 0f);
         mRecentsView.setContentAlpha(1);
         mRecentsView.setFullscreenProgress(fromState.getOverviewFullscreenProgress());
         mLauncher.getActionsView().getVisibilityAlpha().setValue(
@@ -233,7 +235,7 @@
         //   - OverviewScrim
         //   - RecentsView fade (if it's empty)
         PendingAnimation xAnim = new PendingAnimation((long) (mXRange * 2));
-        xAnim.setFloat(mRecentsView, ADJACENT_PAGE_OFFSET, scaleAndOffset[1], LINEAR);
+        xAnim.setFloat(mRecentsView, ADJACENT_PAGE_HORIZONTAL_OFFSET, scaleAndOffset[1], LINEAR);
         xAnim.setViewBackgroundColor(mLauncher.getScrimView(),
                 toState.getWorkspaceScrimColor(mLauncher), LINEAR);
         if (mRecentsView.getTaskViewCount() == 0) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index 3953e42..5891d5f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -30,6 +30,9 @@
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_VERTICAL_OFFSET;
+import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
@@ -105,6 +108,14 @@
         StateAnimationConfig config = new StateAnimationConfig();
         setupInterpolators(config);
         config.duration = (long) (getShiftRange() * 2);
+
+        // Set RecentView's initial properties for coming in from the side.
+        RECENTS_SCALE_PROPERTY.set(mOverviewPanel,
+                QUICK_SWITCH.getOverviewScaleAndOffset(mLauncher)[0] * 0.85f);
+        ADJACENT_PAGE_HORIZONTAL_OFFSET.set(mOverviewPanel, 1f);
+        ADJACENT_PAGE_VERTICAL_OFFSET.set(mOverviewPanel, 0f);
+        mOverviewPanel.setContentAlpha(1);
+
         mCurrentAnimation = mLauncher.getStateManager()
                 .createAnimationToNewWorkspace(mToState, config);
         mCurrentAnimation.getTarget().addListener(mClearStateOnCancelListener);
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 7aa81d4..b60b1be 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -23,8 +23,8 @@
 import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
 import static com.android.quickstep.SysUINavigationMode.getMode;
 import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM;
-import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_TRANSLATE_X_ANIM;
-import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
+import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_TRANSLATE_Y_ANIM;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_VERTICAL_OFFSET;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
@@ -469,17 +469,17 @@
             float fromTranslation = attached ? 1 : 0;
             float toTranslation = attached ? 0 : 1;
             mActivity.getStateManager()
-                    .cancelStateElementAnimation(INDEX_RECENTS_TRANSLATE_X_ANIM);
+                    .cancelStateElementAnimation(INDEX_RECENTS_TRANSLATE_Y_ANIM);
             if (!recentsView.isShown() && animate) {
-                ADJACENT_PAGE_OFFSET.set(recentsView, fromTranslation);
+                ADJACENT_PAGE_VERTICAL_OFFSET.set(recentsView, fromTranslation);
             } else {
-                fromTranslation = ADJACENT_PAGE_OFFSET.get(recentsView);
+                fromTranslation = ADJACENT_PAGE_VERTICAL_OFFSET.get(recentsView);
             }
             if (!animate) {
-                ADJACENT_PAGE_OFFSET.set(recentsView, toTranslation);
+                ADJACENT_PAGE_VERTICAL_OFFSET.set(recentsView, toTranslation);
             } else {
                 mActivity.getStateManager().createStateElementAnimation(
-                        INDEX_RECENTS_TRANSLATE_X_ANIM,
+                        INDEX_RECENTS_TRANSLATE_Y_ANIM,
                         fromTranslation, toTranslation).start();
             }
 
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index c786167..2eb9dd8 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -31,6 +31,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.icons.IconProvider;
+import com.android.launcher3.icons.IconProvider.IconChangeListener;
 import com.android.launcher3.util.Executors.SimpleThreadFactory;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.systemui.shared.recents.model.Task;
@@ -49,7 +50,7 @@
  * Singleton class to load and manage recents model.
  */
 @TargetApi(Build.VERSION_CODES.O)
-public class RecentsModel extends TaskStackChangeListener {
+public class RecentsModel extends TaskStackChangeListener implements IconChangeListener {
 
     // We do not need any synchronization for this variable as its only written on UI thread.
     public static final MainThreadInitializedObject<RecentsModel> INSTANCE =
@@ -69,12 +70,13 @@
         mContext = context;
         mTaskList = new RecentTasksList(MAIN_EXECUTOR,
                 new KeyguardManagerCompat(context), ActivityManagerWrapper.getInstance());
-        mIconCache = new TaskIconCache(context, RECENTS_MODEL_EXECUTOR);
+
+        IconProvider iconProvider = new IconProvider(context);
+        mIconCache = new TaskIconCache(context, RECENTS_MODEL_EXECUTOR, iconProvider);
         mThumbnailCache = new TaskThumbnailCache(context, RECENTS_MODEL_EXECUTOR);
 
         ActivityManagerWrapper.getInstance().registerTaskStackListener(this);
-        IconProvider.registerIconChangeListener(context,
-                this::onPackageIconChanged, MAIN_EXECUTOR.getHandler());
+        iconProvider.registerIconChangeListener(this, MAIN_EXECUTOR.getHandler());
     }
 
     public TaskIconCache getIconCache() {
@@ -183,17 +185,23 @@
         if (level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
             // Clear everything once we reach a low-mem situation
             mThumbnailCache.clear();
-            mIconCache.clear();
+            mIconCache.clearCache();
         }
     }
 
-    private void onPackageIconChanged(String pkg, UserHandle user) {
-        mIconCache.invalidateCacheEntries(pkg, user);
+    @Override
+    public void onAppIconChanged(String packageName, UserHandle user) {
+        mIconCache.invalidateCacheEntries(packageName, user);
         for (int i = mThumbnailChangeListeners.size() - 1; i >= 0; i--) {
-            mThumbnailChangeListeners.get(i).onTaskIconChanged(pkg, user);
+            mThumbnailChangeListeners.get(i).onTaskIconChanged(packageName, user);
         }
     }
 
+    @Override
+    public void onSystemIconStateChanged(String iconState) {
+        mIconCache.clearCache();
+    }
+
     /**
      * Adds a listener for visuals changes
      */
diff --git a/quickstep/src/com/android/quickstep/SysUINavigationMode.java b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
index efec037..74f4bea 100644
--- a/quickstep/src/com/android/quickstep/SysUINavigationMode.java
+++ b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
@@ -34,6 +34,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * Observer for the resource config that specifies the navigation bar mode.
@@ -75,7 +76,8 @@
     private int mNavBarGesturalHeight;
     private int mNavBarLargerGesturalHeight;
 
-    private final List<NavigationModeChangeListener> mChangeListeners = new ArrayList<>();
+    private final List<NavigationModeChangeListener> mChangeListeners =
+            new CopyOnWriteArrayList<>();
     private final List<OneHandedModeChangeListener> mOneHandedOverlayChangeListeners =
             new ArrayList<>();
 
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index ba1c413..fa61fff 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -16,6 +16,7 @@
 package com.android.quickstep;
 
 import static com.android.launcher3.uioverrides.QuickstepLauncher.GO_LOW_RAM_RECENTS_ENABLED;
+import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
 
 import android.app.ActivityManager.TaskDescription;
 import android.content.Context;
@@ -35,9 +36,12 @@
 
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.IconProvider;
-import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
+import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.Preconditions;
 import com.android.quickstep.util.CancellableTask;
 import com.android.quickstep.util.TaskKeyLruCache;
@@ -52,7 +56,7 @@
 /**
  * Manages the caching of task icons and related data.
  */
-public class TaskIconCache {
+public class TaskIconCache implements DisplayInfoChangeListener {
 
     private final Executor mBgExecutor;
     private final AccessibilityManager mAccessibilityManager;
@@ -62,15 +66,27 @@
     private final SparseArray<BitmapInfo> mDefaultIcons = new SparseArray<>();
     private final IconProvider mIconProvider;
 
-    public TaskIconCache(Context context, Executor bgExecutor) {
+    private BaseIconFactory mIconFactory;
+
+    public TaskIconCache(Context context, Executor bgExecutor, IconProvider iconProvider) {
         mContext = context;
         mBgExecutor = bgExecutor;
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+        mIconProvider = iconProvider;
 
         Resources res = context.getResources();
         int cacheSize = res.getInteger(R.integer.recentsIconCacheSize);
+
         mIconCache = new TaskKeyLruCache<>(cacheSize);
-        mIconProvider = new IconProvider(context);
+
+        DisplayController.INSTANCE.get(mContext).addChangeListener(this);
+    }
+
+    @Override
+    public void onDisplayInfoChanged(Context context, Info info, int flags) {
+        if ((flags & CHANGE_DENSITY) != 0) {
+            clearCache();
+        }
     }
 
     /**
@@ -104,8 +120,11 @@
         return request;
     }
 
-    public void clear() {
-        mIconCache.evictAll();
+    /**
+     * Clears the icon cache
+     */
+    public void clearCache() {
+        mBgExecutor.execute(this::resetFactory);
     }
 
     void onTaskRemoved(TaskKey taskKey) {
@@ -193,8 +212,8 @@
         synchronized (mDefaultIcons) {
             BitmapInfo info = mDefaultIcons.get(userId);
             if (info == null) {
-                try (LauncherIcons la = LauncherIcons.obtain(mContext)) {
-                    info = la.makeDefaultIcon(UserHandle.of(userId));
+                try (BaseIconFactory bif = getIconFactory()) {
+                    info = bif.makeDefaultIcon(UserHandle.of(userId));
                 }
                 mDefaultIcons.put(userId, info);
             }
@@ -205,16 +224,32 @@
     @WorkerThread
     private BitmapInfo getBitmapInfo(Drawable drawable, int userId,
             int primaryColor, boolean isInstantApp) {
-        try (LauncherIcons la = LauncherIcons.obtain(mContext)) {
-            la.disableColorExtraction();
-            la.setWrapperBackgroundColor(primaryColor);
+        try (BaseIconFactory bif = getIconFactory()) {
+            bif.disableColorExtraction();
+            bif.setWrapperBackgroundColor(primaryColor);
 
             // User version code O, so that the icon is always wrapped in an adaptive icon container
-            return la.createBadgedIconBitmap(drawable, UserHandle.of(userId),
+            return bif.createBadgedIconBitmap(drawable, UserHandle.of(userId),
                     Build.VERSION_CODES.O, isInstantApp);
         }
     }
 
+    @WorkerThread
+    private BaseIconFactory getIconFactory() {
+        if (mIconFactory == null) {
+            mIconFactory = new BaseIconFactory(mContext,
+                    DisplayController.INSTANCE.get(mContext).getInfo().densityDpi,
+                    mContext.getResources().getDimensionPixelSize(R.dimen.taskbar_icon_size));
+        }
+        return mIconFactory;
+    }
+
+    @WorkerThread
+    private void resetFactory() {
+        mIconFactory = null;
+        mIconCache.evictAll();
+    }
+
     private static class TaskCacheEntry {
         public Drawable icon;
         public String contentDescription = "";
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 338a6ef..5fe0fc7 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -52,6 +52,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.util.Log;
@@ -145,7 +146,22 @@
     @Nullable
     private OverscrollPlugin mOverscrollPlugin;
 
-    private final IBinder mMyBinder = new IOverviewProxy.Stub() {
+    /**
+     * Extension of OverviewProxy aidl interface without needing to modify the actual interface.
+     * This is for methods that need only need local access and not intended to make IPC calls.
+     */
+    public abstract static class TISBinder extends IOverviewProxy.Stub {
+        public abstract void setTaskbarOverviewProxyDelegate(
+                @Nullable TaskbarOverviewProxyDelegate i);
+    }
+
+
+    private final TISBinder mMyBinder = new TISBinder() {
+
+        public void setTaskbarOverviewProxyDelegate(
+                @Nullable TaskbarOverviewProxyDelegate delegate) {
+            mTaskbarOverviewProxyDelegate = delegate;
+        }
 
         @BinderThread
         public void onInitialize(Bundle bundle) {
@@ -252,20 +268,49 @@
             MAIN_EXECUTOR.execute(() -> mDeviceState.setDeferredGestureRegion(region));
         }
 
+        @Override
         public void onSplitScreenSecondaryBoundsChanged(Rect bounds, Rect insets)  {
             WindowBounds wb = new WindowBounds(bounds, insets);
             MAIN_EXECUTOR.execute(() -> SplitScreenBounds.INSTANCE.setSecondaryWindowBounds(wb));
         }
+
+        @Override
+        public void onImeWindowStatusChanged(int displayId, IBinder token, int vis,
+                int backDisposition, boolean showImeSwitcher) throws RemoteException {
+            if (mTaskbarOverviewProxyDelegate == null) {
+                return;
+            }
+            MAIN_EXECUTOR.execute(() -> {
+                if (mTaskbarOverviewProxyDelegate == null) {
+                    return;
+                }
+                mTaskbarOverviewProxyDelegate
+                        .updateImeStatus(displayId, vis, backDisposition, showImeSwitcher);
+            });
+        }
     };
 
+    public interface TaskbarOverviewProxyDelegate {
+        void updateImeStatus(int displayId, int vis, int backDisposition,
+                boolean showImeSwitcher);
+    }
+
     private static boolean sConnected = false;
+    private static TouchInteractionService sInstance;
     private static boolean sIsInitialized = false;
     private RotationTouchHelper mRotationTouchHelper;
+    @Nullable
+    private TaskbarOverviewProxyDelegate mTaskbarOverviewProxyDelegate;
 
     public static boolean isConnected() {
         return sConnected;
     }
 
+    @Nullable
+    public static TouchInteractionService getInstance() {
+        return sInstance;
+    }
+
     public static boolean isInitialized() {
         return sIsInitialized;
     }
@@ -293,6 +338,10 @@
 
     private DisplayManager mDisplayManager;
 
+    public TouchInteractionService() {
+        sInstance = this;
+    }
+
     @Override
     public void onCreate() {
         super.onCreate();
@@ -389,6 +438,10 @@
         onOverviewTargetChange(mOverviewComponentObserver.isHomeAndOverviewSame());
     }
 
+    public OverviewCommandHelper getOverviewCommandHelper() {
+        return mOverviewCommandHelper;
+    }
+
     private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
         if (!mDeviceState.isUserUnlocked() || mDeviceState.isButtonNavMode()) {
             // Skip if not yet unlocked (can't read user shared prefs) or if the current navigation
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index e4d148c..4d776ba 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -22,7 +22,8 @@
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_SCRIM_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
-import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_VERTICAL_OFFSET;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
 import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
@@ -86,8 +87,10 @@
         float[] scaleAndOffset = state.getOverviewScaleAndOffset(mActivity);
         setter.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0],
                 config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR));
-        setter.setFloat(mRecentsView, ADJACENT_PAGE_OFFSET, scaleAndOffset[1],
+        setter.setFloat(mRecentsView, ADJACENT_PAGE_HORIZONTAL_OFFSET, scaleAndOffset[1],
                 config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR));
+        setter.setFloat(mRecentsView, ADJACENT_PAGE_VERTICAL_OFFSET, scaleAndOffset[2],
+                config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
         setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f,
                 config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
 
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
index b6cfdce..532f219 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsState.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
@@ -126,7 +126,7 @@
     }
 
     public float[] getOverviewScaleAndOffset(RecentsActivity activity) {
-        return new float[] { NO_SCALE, NO_OFFSET };
+        return new float[] { NO_SCALE, NO_OFFSET, NO_OFFSET };
     }
 
     /**
@@ -170,7 +170,7 @@
 
         @Override
         public float[] getOverviewScaleAndOffset(RecentsActivity activity) {
-            return new float[] { NO_SCALE, 1 };
+            return new float[] { NO_SCALE, NO_OFFSET, 1 };
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
index 42be9bb..10b7662 100644
--- a/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
+++ b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
@@ -17,44 +17,26 @@
 
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.anim.Interpolators.clampToProgress;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.util.Log;
-import android.view.animation.Interpolator;
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.states.StateAnimationConfig;
-import com.android.quickstep.views.RecentsView;
 
 /**
  * Runs an animation from overview to home. Currently, this animation is just a wrapper around the
- * normal state transition, in order to keep RecentsView at the same scale and translationY that
- * it started out at as it translates offscreen. It also scrolls RecentsView to page 0 and may play
- * a {@link StaggeredWorkspaceAnim} if we're starting from an upward fling.
+ * normal state transition and may play a {@link StaggeredWorkspaceAnim} if we're starting from an
+ * upward fling.
  */
 public class OverviewToHomeAnim {
 
     private static final String TAG = "OverviewToHomeAnim";
 
-    // Constants to specify how to scroll RecentsView to the default page if it's not already there.
-    private static final int DEFAULT_PAGE = 0;
-    private static final int PER_PAGE_SCROLL_DURATION = 150;
-    private static final int MAX_PAGE_SCROLL_DURATION = 750;
-
     private final Launcher mLauncher;
     private final Runnable mOnReachedHome;
 
@@ -95,24 +77,8 @@
             mIsHomeStaggeredAnimFinished = true;
         }
 
-        RecentsView recentsView = mLauncher.getOverviewPanel();
-        int numPagesToScroll = recentsView.getNextPage() - DEFAULT_PAGE;
-        int scrollDuration = Math.min(MAX_PAGE_SCROLL_DURATION,
-                numPagesToScroll * PER_PAGE_SCROLL_DURATION);
-        int duration = Math.max(scrollDuration, startState.getTransitionDuration(mLauncher));
-
-        StateAnimationConfig config = new UseFirstInterpolatorStateAnimConfig();
-        config.duration = duration;
-        boolean isLayoutNaturalToLauncher = recentsView.getPagedOrientationHandler()
-                .isLayoutNaturalToLauncher();
-        config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, isLayoutNaturalToLauncher
-                ? clampToProgress(FAST_OUT_SLOW_IN, 0, 0.75f) : FINAL_FRAME);
-        config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, FINAL_FRAME);
-        config.setInterpolator(ANIM_OVERVIEW_SCALE, FINAL_FRAME);
-        config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, LINEAR);
-        if (!isLayoutNaturalToLauncher) {
-            config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL);
-        }
+        StateAnimationConfig config = new StateAnimationConfig();
+        config.duration = startState.getTransitionDuration(mLauncher);
         AnimatorSet stateAnim = stateManager.createAtomicAnimation(
                 startState, NORMAL, config);
         stateAnim.addListener(new AnimationSuccessListener() {
@@ -125,7 +91,6 @@
         anim.play(stateAnim);
         stateManager.setCurrentAnimation(anim, NORMAL);
         anim.start();
-        recentsView.snapToPage(DEFAULT_PAGE, duration);
     }
 
     private void maybeOverviewToHomeAnimComplete() {
@@ -133,17 +98,4 @@
             mOnReachedHome.run();
         }
     }
-
-    /**
-     * Wrapper around StateAnimationConfig that doesn't allow interpolators to be set if they are
-     * already set. This ensures they aren't overridden before being used.
-     */
-    private static class UseFirstInterpolatorStateAnimConfig extends StateAnimationConfig {
-        @Override
-        public void setInterpolator(int animId, Interpolator interpolator) {
-            if (mInterpolators[animId] == null || interpolator == null) {
-                super.setInterpolator(animId, interpolator);
-            }
-        }
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java b/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java
index ba70bf7..c1ca060 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java
@@ -15,11 +15,13 @@
  */
 package com.android.quickstep.util;
 
-import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_VERTICAL_OFFSET;
 
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
 
+import androidx.dynamicanimation.animation.DynamicAnimation;
+
 import com.android.launcher3.anim.SpringAnimationBuilder;
 import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
 import com.android.launcher3.statemanager.StatefulActivity;
@@ -29,7 +31,7 @@
         extends AtomicAnimationFactory<STATE_TYPE> {
 
     public static final int INDEX_RECENTS_FADE_ANIM = AtomicAnimationFactory.NEXT_INDEX + 0;
-    public static final int INDEX_RECENTS_TRANSLATE_X_ANIM = AtomicAnimationFactory.NEXT_INDEX + 1;
+    public static final int INDEX_RECENTS_TRANSLATE_Y_ANIM = AtomicAnimationFactory.NEXT_INDEX + 1;
 
     private static final int MY_ANIM_COUNT = 2;
 
@@ -46,14 +48,14 @@
             case INDEX_RECENTS_FADE_ANIM:
                 return ObjectAnimator.ofFloat(mActivity.getOverviewPanel(),
                         RecentsView.CONTENT_ALPHA, values);
-            case INDEX_RECENTS_TRANSLATE_X_ANIM: {
+            case INDEX_RECENTS_TRANSLATE_Y_ANIM: {
                 RecentsView rv = mActivity.getOverviewPanel();
                 return new SpringAnimationBuilder(mActivity)
-                        .setMinimumVisibleChange(1f / rv.getPageOffsetScale())
+                        .setMinimumVisibleChange(DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE)
                         .setDampingRatio(0.8f)
                         .setStiffness(250)
                         .setValues(values)
-                        .build(rv, ADJACENT_PAGE_OFFSET);
+                        .build(rv, ADJACENT_PAGE_VERTICAL_OFFSET);
             }
             default:
                 return super.createStateElementAnimation(index, values);
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 2c5f661..65956d5 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -21,7 +21,6 @@
 import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
 import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
-import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
 
 import android.annotation.TargetApi;
 import android.content.Context;
@@ -39,7 +38,6 @@
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.quickstep.LauncherActivityInterface;
-import com.android.quickstep.util.OverviewToHomeAnim;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.RecentsExtraCard;
 
@@ -90,15 +88,7 @@
 
     @Override
     public void startHome() {
-        Runnable onReachedHome = () -> mActivity.getStateManager().goToState(NORMAL, false);
-        OverviewToHomeAnim overviewToHomeAnim = new OverviewToHomeAnim(mActivity, onReachedHome);
-        if (LIVE_TILE.get()) {
-            switchToScreenshot(null,
-                    () -> finishRecentsAnimation(true /* toRecents */,
-                            () -> overviewToHomeAnim.animateWithVelocity(0)));
-        } else {
-            overviewToHomeAnim.animateWithVelocity(0);
-        }
+        mActivity.getStateManager().goToState(NORMAL);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index e4f7b02..a04b886 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -23,7 +23,6 @@
 import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU;
 import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
-import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
 import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
@@ -171,8 +170,7 @@
 public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_TYPE>,
         STATE_TYPE extends BaseState<STATE_TYPE>> extends PagedView implements Insettable,
         TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback,
-        InvariantDeviceProfile.OnIDPChangeListener, TaskVisualsChangeListener,
-        SplitScreenBounds.OnChangeListener {
+        TaskVisualsChangeListener, SplitScreenBounds.OnChangeListener {
 
     public static final FloatProperty<RecentsView> CONTENT_ALPHA =
             new FloatProperty<RecentsView>("contentAlpha") {
@@ -213,19 +211,35 @@
                 }
             };
 
-    public static final FloatProperty<RecentsView> ADJACENT_PAGE_OFFSET =
-            new FloatProperty<RecentsView>("adjacentPageOffset") {
+    public static final FloatProperty<RecentsView> ADJACENT_PAGE_HORIZONTAL_OFFSET =
+            new FloatProperty<RecentsView>("adjacentPageHorizontalOffset") {
                 @Override
                 public void setValue(RecentsView recentsView, float v) {
-                    if (recentsView.mAdjacentPageOffset != v) {
-                        recentsView.mAdjacentPageOffset = v;
+                    if (recentsView.mAdjacentPageHorizontalOffset != v) {
+                        recentsView.mAdjacentPageHorizontalOffset = v;
                         recentsView.updatePageOffsets();
                     }
                 }
 
                 @Override
                 public Float get(RecentsView recentsView) {
-                    return recentsView.mAdjacentPageOffset;
+                    return recentsView.mAdjacentPageHorizontalOffset;
+                }
+            };
+
+    public static final FloatProperty<RecentsView> ADJACENT_PAGE_VERTICAL_OFFSET =
+            new FloatProperty<RecentsView>("adjacentPageVerticalOffset") {
+                @Override
+                public void setValue(RecentsView recentsView, float v) {
+                    if (recentsView.mAdjacentPageVerticalOffset != v) {
+                        recentsView.mAdjacentPageVerticalOffset = v;
+                        recentsView.updateVerticalPageOffsets();
+                    }
+                }
+
+                @Override
+                public Float get(RecentsView recentsView) {
+                    return recentsView.mAdjacentPageVerticalOffset;
                 }
             };
 
@@ -240,6 +254,8 @@
                 @Override
                 public void setValue(RecentsView recentsView, float v) {
                     recentsView.setTaskViewsResistanceTranslation(v);
+                    recentsView.mLastComputedTaskBottomPushOutDistance = null;
+                    recentsView.updateVerticalPageOffsets();
                 }
 
                 @Override
@@ -289,9 +305,11 @@
                     view.setScaleY(scale);
                     view.mLastComputedTaskStartPushOutDistance = null;
                     view.mLastComputedTaskEndPushOutDistance = null;
+                    view.mLastComputedTaskBottomPushOutDistance = null;
                     view.mLiveTileTaskViewSimulator.recentsViewScale.value = scale;
-                    view.updatePageOffsets();
                     view.setTaskViewsResistanceTranslation(view.mTaskViewsSecondaryTranslation);
+                    view.updatePageOffsets();
+                    view.updateVerticalPageOffsets();
                 }
 
                 @Override
@@ -330,6 +348,7 @@
     // How much a task that is directly offscreen will be pushed out due to RecentsView scale/pivot.
     protected Float mLastComputedTaskStartPushOutDistance = null;
     protected Float mLastComputedTaskEndPushOutDistance = null;
+    protected Float mLastComputedTaskBottomPushOutDistance = null;
     protected boolean mEnableDrawingLiveTile = false;
     protected final Rect mTempRect = new Rect();
     protected final RectF mTempRectF = new RectF();
@@ -373,7 +392,8 @@
     private boolean mOverviewGridEnabled;
     private boolean mOverviewFullscreenEnabled;
 
-    private float mAdjacentPageOffset = 0;
+    private float mAdjacentPageHorizontalOffset = 0;
+    private float mAdjacentPageVerticalOffset = 0;
     protected float mTaskViewsSecondaryTranslation = 0;
     protected float mTaskViewsPrimarySplitTranslation = 0;
     protected float mTaskViewsSecondarySplitTranslation = 0;
@@ -622,28 +642,9 @@
         // Draw overscroll
         if (mAllowOverScroll && (!mEdgeGlowRight.isFinished() || !mEdgeGlowLeft.isFinished())) {
             final int restoreCount = canvas.save();
-            final int width = getWidth();
-            final int height = getHeight();
-            int primarySize = mOrientationHandler.getPrimaryValue(width, height);
-            int secondarySize = mOrientationHandler.getSecondaryValue(width, height);
 
-            float effectiveShift = 0;
-            if (!mEdgeGlowLeft.isFinished()) {
-                mEdgeGlowLeft.setSize(secondarySize, primarySize);
-                if (((TranslateEdgeEffect) mEdgeGlowLeft).getTranslationShift(mTempFloat)) {
-                    effectiveShift = mTempFloat[0];
-                    postInvalidateOnAnimation();
-                }
-            }
-            if (!mEdgeGlowRight.isFinished()) {
-                mEdgeGlowRight.setSize(secondarySize, primarySize);
-                if (((TranslateEdgeEffect) mEdgeGlowRight).getTranslationShift(mTempFloat)) {
-                    effectiveShift -= mTempFloat[0];
-                    postInvalidateOnAnimation();
-                }
-            }
-
-            int scroll = OverScroll.dampedScroll(effectiveShift * primarySize, primarySize);
+            int primarySize = mOrientationHandler.getPrimaryValue(getWidth(), getHeight());
+            int scroll = OverScroll.dampedScroll(getUndampedOverScrollShift(), primarySize);
             mOrientationHandler.set(canvas, CANVAS_TRANSLATE, scroll);
 
             if (mOverScrollShift != scroll) {
@@ -665,6 +666,31 @@
         }
     }
 
+    private float getUndampedOverScrollShift() {
+        final int width = getWidth();
+        final int height = getHeight();
+        int primarySize = mOrientationHandler.getPrimaryValue(width, height);
+        int secondarySize = mOrientationHandler.getSecondaryValue(width, height);
+
+        float effectiveShift = 0;
+        if (!mEdgeGlowLeft.isFinished()) {
+            mEdgeGlowLeft.setSize(secondarySize, primarySize);
+            if (((TranslateEdgeEffect) mEdgeGlowLeft).getTranslationShift(mTempFloat)) {
+                effectiveShift = mTempFloat[0];
+                postInvalidateOnAnimation();
+            }
+        }
+        if (!mEdgeGlowRight.isFinished()) {
+            mEdgeGlowRight.setSize(secondarySize, primarySize);
+            if (((TranslateEdgeEffect) mEdgeGlowRight).getTranslationShift(mTempFloat)) {
+                effectiveShift -= mTempFloat[0];
+                postInvalidateOnAnimation();
+            }
+        }
+
+        return effectiveShift * primarySize;
+    }
+
     /**
      * Returns the view shift due to overscroll
      */
@@ -718,16 +744,6 @@
         updateTaskStackListenerState();
     }
 
-    @Override
-    public void onIdpChanged(int changeFlags, InvariantDeviceProfile idp) {
-        if ((changeFlags & CHANGE_FLAG_ICON_PARAMS) == 0) {
-            return;
-        }
-        mModel.getIconCache().clear();
-        unloadVisibleTaskData(TaskView.FLAG_UPDATE_ICON);
-        loadVisibleTaskData(TaskView.FLAG_UPDATE_ICON);
-    }
-
     public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
         mActionsView = actionsView;
         mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
@@ -752,7 +768,6 @@
         mSyncTransactionApplier = new SurfaceTransactionApplier(this);
         mLiveTileParams.setSyncTransactionApplier(mSyncTransactionApplier);
         RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this);
-        mIdp.addOnChangeListener(this);
         mIPipAnimationListener.setActivity(mActivity);
         SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(
                 mIPipAnimationListener);
@@ -771,7 +786,6 @@
         mSyncTransactionApplier = null;
         mLiveTileParams.setSyncTransactionApplier(null);
         RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this);
-        mIdp.removeOnChangeListener(this);
         SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null);
         SplitScreenBounds.INSTANCE.removeOnChangeListener(this);
         mIPipAnimationListener.setActivity(null);
@@ -1188,6 +1202,7 @@
         // Update the set of visible task's data
         loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
         setTaskModalness(0);
+        updateVerticalPageOffsets();
     }
 
     public void setFullscreenProgress(float fullscreenProgress) {
@@ -2642,13 +2657,15 @@
         setTaskModalness(mTaskModalness);
         mLastComputedTaskStartPushOutDistance = null;
         mLastComputedTaskEndPushOutDistance = null;
+        mLastComputedTaskBottomPushOutDistance = null;
         updatePageOffsets();
+        updateVerticalPageOffsets();
         setImportantForAccessibility(isModal() ? IMPORTANT_FOR_ACCESSIBILITY_NO
                 : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
     }
 
     private void updatePageOffsets() {
-        float offset = mAdjacentPageOffset;
+        float offset = mAdjacentPageHorizontalOffset;
         float modalOffset = ACCEL_0_75.getInterpolation(mTaskModalness);
         int count = getChildCount();
 
@@ -2659,10 +2676,10 @@
 
         float midpointOffsetSize = 0;
         float leftOffsetSize = midpoint - 1 >= 0
-                ? -getOffsetSize(midpoint - 1, midpoint, offset)
+                ? -getHorizontalOffsetSize(midpoint - 1, midpoint, offset)
                 : 0;
         float rightOffsetSize = midpoint + 1 < count
-                ? getOffsetSize(midpoint + 1, midpoint, offset)
+                ? getHorizontalOffsetSize(midpoint + 1, midpoint, offset)
                 : 0;
 
         boolean showAsGrid = showAsGrid();
@@ -2676,14 +2693,14 @@
             // calculation is the task directly next to the focus task in the grid.
             int referenceIndex = modalMidpoint == 0 ? 1 : 0;
             gridOffsetSize = referenceIndex < count
-                    ? getOffsetSize(referenceIndex, modalMidpoint, modalOffset)
+                    ? getHorizontalOffsetSize(referenceIndex, modalMidpoint, modalOffset)
                     : 0;
         } else {
             modalLeftOffsetSize = modalMidpoint - 1 >= 0
-                    ? getOffsetSize(modalMidpoint - 1, modalMidpoint, modalOffset)
+                    ? getHorizontalOffsetSize(modalMidpoint - 1, modalMidpoint, modalOffset)
                     : 0;
             modalRightOffsetSize = modalMidpoint + 1 < count
-                    ? getOffsetSize(modalMidpoint + 1, modalMidpoint, modalOffset)
+                    ? getHorizontalOffsetSize(modalMidpoint + 1, modalMidpoint, modalOffset)
                     : 0;
         }
 
@@ -2734,7 +2751,7 @@
      * translating away from the given midpoint.
      * @param offsetProgress From 0 to 1 where 0 means no offset and 1 means offset offscreen.
      */
-    private float getOffsetSize(int childIndex, int midpointIndex, float offsetProgress) {
+    private float getHorizontalOffsetSize(int childIndex, int midpointIndex, float offsetProgress) {
         if (offsetProgress == 0) {
             // Don't bother calculating everything below if we won't offset anyway.
             return 0;
@@ -2796,6 +2813,64 @@
         return distanceToOffscreen * offsetProgress;
     }
 
+    private void updateVerticalPageOffsets() {
+        float offset = mAdjacentPageVerticalOffset;
+        int count = getTaskViewCount();
+
+        TaskView runningTask = mRunningTaskId == -1 || !mRunningTaskTileHidden
+                ? null : getTaskView(mRunningTaskId);
+        int midpoint = runningTask == null ? -1 : indexOfChild(runningTask);
+
+        float offsetSize = getVerticalOffsetSize(offset);
+        float midpointOffsetSize = 0;
+
+        for (int i = 0; i < count; i++) {
+            float translation = i == midpoint
+                    ? midpointOffsetSize
+                    : offsetSize;
+            int directionFactor = mOrientationHandler.getSecondaryTranslationDirectionFactor() * -1;
+            translation *= directionFactor;
+            TaskView child = getTaskViewAt(i);
+            FloatProperty translationProperty = child.getSecondaryTaskOffsetTranslationProperty();
+            translationProperty.set(child, translation);
+            if (LIVE_TILE.get() && mEnableDrawingLiveTile && i == getRunningTaskIndex()) {
+                mLiveTileTaskViewSimulator.taskSecondaryTranslation.value = translation;
+                redrawLiveTile();
+            }
+        }
+    }
+
+    /**
+     * Computes the distance to offset the given child such that it is completely offscreen when
+     * translating away from its position in overview.
+     * @param offsetProgress From 0 to 1 where 0 means no offset and 1 means offset offscreen.
+     */
+    private float getVerticalOffsetSize(float offsetProgress) {
+        if (offsetProgress == 0) {
+            // Don't bother calculating everything below if we won't offset anyway.
+            return 0;
+        }
+        // First, find the distance to offscreen from the normal (centered) task position.
+        mTempRectF.set(mLastComputedTaskSize);
+        RectF taskPosition = mTempRectF;
+        float desiredTop = getHeight();
+        float distanceToOffscreen = desiredTop - taskPosition.top;
+        // Next, we need to account for the resistance translation if any (e.g. long swipe up).
+        float translationY = mTaskViewsSecondaryTranslation;
+        distanceToOffscreen -= translationY;
+        // Finally, we need to account for RecentsView scale, because it moves tasks based on its
+        // pivot. To do this, we move the task position to where it would be offscreen at scale = 1
+        // (computed above), then we apply the scale via getMatrix() to determine how much that
+        // moves the task from its desired position, and adjust the computed distance accordingly.
+        if (mLastComputedTaskBottomPushOutDistance == null) {
+            taskPosition.offsetTo(0, desiredTop + translationY);
+            getMatrix().mapRect(taskPosition);
+            mLastComputedTaskBottomPushOutDistance = (taskPosition.top - desiredTop) / getScaleY();
+        }
+        distanceToOffscreen -= mLastComputedTaskBottomPushOutDistance;
+        return distanceToOffscreen * offsetProgress;
+    }
+
     protected void setTaskViewsResistanceTranslation(float translation) {
         mTaskViewsSecondaryTranslation = translation;
         for (int i = 0; i < getTaskViewCount(); i++) {
@@ -2822,13 +2897,6 @@
     }
 
     /**
-     * TODO: Do not assume motion across X axis for adjacent page
-     */
-    public float getPageOffsetScale() {
-        return Math.max(getWidth(), 1);
-    }
-
-    /**
      * Resets the visuals when exit modal state.
      */
     public void resetModalVisuals() {
@@ -3440,8 +3508,16 @@
         if (pageIndex == -1) {
             return 0;
         }
+
+        int overScrollShift = getOverScrollShift();
+        if (mAdjacentPageVerticalOffset > 0) {
+            // Don't dampen the scroll (due to overscroll) if the adjacent tasks are offscreen, so
+            // that the page can move freely given there's no visual indication why it shouldn't.
+            overScrollShift = (int) Utilities.mapRange(mAdjacentPageVerticalOffset, overScrollShift,
+                    getUndampedOverScrollShift());
+        }
         return getScrollForPage(pageIndex) - mOrientationHandler.getPrimaryScroll(this)
-                + getOverScrollShift();
+                + overScrollShift;
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 1477933..6f3aade 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -1151,6 +1151,11 @@
                 TASK_OFFSET_TRANSLATION_X, TASK_OFFSET_TRANSLATION_Y);
     }
 
+    public FloatProperty<TaskView> getSecondaryTaskOffsetTranslationProperty() {
+        return getPagedOrientationHandler().getSecondaryValue(
+                TASK_OFFSET_TRANSLATION_X, TASK_OFFSET_TRANSLATION_Y);
+    }
+
     public FloatProperty<TaskView> getTaskResistanceTranslationProperty() {
         return getPagedOrientationHandler().getSecondaryValue(
                 TASK_RESISTANCE_TRANSLATION_X, TASK_RESISTANCE_TRANSLATION_Y);
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index b263d38..7836fa3 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -17,20 +17,16 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.Utilities.dpiFromPx;
-import static com.android.launcher3.Utilities.getDevicePrefs;
 import static com.android.launcher3.Utilities.getPointString;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TWO_PANEL_HOME;
 import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
 import static com.android.launcher3.util.DisplayController.CHANGE_SUPPORTED_BOUNDS;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
 
 import android.annotation.TargetApi;
 import android.appwidget.AppWidgetHostView;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -49,7 +45,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.Info;
@@ -83,11 +78,6 @@
 
     private static final float ICON_SIZE_DEFINED_IN_APP_DP = 48;
 
-    public static final int CHANGE_FLAG_GRID = 1 << 0;
-    public static final int CHANGE_FLAG_ICON_PARAMS = 1 << 1;
-
-    public static final String KEY_ICON_PATH_REF = "pref_icon_shape_path";
-
     // Constants that affects the interpolation curve between statically defined device profile
     // buckets.
     private static final float KNEARESTNEIGHBOR = 3;
@@ -96,9 +86,6 @@
     // used to offset float not being able to express extremely small weights in extreme cases.
     private static final float WEIGHT_EFFICIENT = 100000f;
 
-    private static final int CONFIG_ICON_MASK_RES_ID = Resources.getSystem().getIdentifier(
-            "config_icon_mask", "string", "android");
-
     /**
      * Number of icons per row and column in the workspace.
      */
@@ -111,7 +98,6 @@
     public int numFolderRows;
     public int numFolderColumns;
     public float iconSize;
-    public String iconShapePath;
     public float landscapeIconSize;
     public float landscapeIconTextSize;
     public int iconBitmapSize;
@@ -162,7 +148,6 @@
     public Rect defaultWidgetPadding;
 
     private final ArrayList<OnIDPChangeListener> mChangeListeners = new ArrayList<>();
-    private OverlayMonitor mOverlayMonitor;
 
     @VisibleForTesting
     public InvariantDeviceProfile() {}
@@ -173,7 +158,6 @@
         numFolderRows = p.numFolderRows;
         numFolderColumns = p.numFolderColumns;
         iconSize = p.iconSize;
-        iconShapePath = p.iconShapePath;
         landscapeIconSize = p.landscapeIconSize;
         iconBitmapSize = p.iconBitmapSize;
         iconTextSize = p.iconTextSize;
@@ -193,7 +177,6 @@
         defaultLayoutId = p.defaultLayoutId;
         demoModeLayoutId = p.demoModeLayoutId;
         mExtraAttrs = p.mExtraAttrs;
-        mOverlayMonitor = p.mOverlayMonitor;
         devicePaddings = p.devicePaddings;
     }
 
@@ -215,7 +198,6 @@
                         onConfigChanged(displayContext);
                     }
                 });
-        mOverlayMonitor = new OverlayMonitor(context);
     }
 
     /**
@@ -266,17 +248,6 @@
                 ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null) : null;
     }
 
-    /**
-     * Retrieve system defined or RRO overriden icon shape.
-     */
-    private static String getIconShapePath(Context context) {
-        if (CONFIG_ICON_MASK_RES_ID == 0) {
-            Log.e(TAG, "Icon mask res identifier failed to retrieve.");
-            return "";
-        }
-        return context.getResources().getString(CONFIG_ICON_MASK_RES_ID);
-    }
-
     private String initGrid(Context context, String gridName) {
         Info displayInfo = DisplayController.INSTANCE.get(context).getInfo();
         // Determine if we have split display
@@ -317,7 +288,6 @@
         mExtraAttrs = closestProfile.extraAttrs;
 
         iconSize = displayOption.iconSize;
-        iconShapePath = getIconShapePath(context);
         landscapeIconSize = displayOption.landscapeIconSize;
         iconBitmapSize = ResourceUtils.pxFromDp(iconSize, metrics);
         iconTextSize = displayOption.iconTextSize;
@@ -391,18 +361,6 @@
         mChangeListeners.remove(listener);
     }
 
-    public void verifyConfigChangedInBackground(final Context context) {
-        String savedIconMaskPath = getDevicePrefs(context).getString(KEY_ICON_PATH_REF, "");
-        // Good place to check if grid size changed in themepicker when launcher was dead.
-        if (savedIconMaskPath.isEmpty()) {
-            getDevicePrefs(context).edit().putString(KEY_ICON_PATH_REF, getIconShapePath(context))
-                    .apply();
-        } else if (!savedIconMaskPath.equals(getIconShapePath(context))) {
-            getDevicePrefs(context).edit().putString(KEY_ICON_PATH_REF, getIconShapePath(context))
-                    .apply();
-            apply(CHANGE_FLAG_ICON_PARAMS);
-        }
-    }
 
     public void setCurrentGrid(Context context, String gridName) {
         Context appContext = context.getApplicationContext();
@@ -414,36 +372,13 @@
         if (TestProtocol.sDebugTracing) {
             Log.d(TestProtocol.LAUNCHER_NOT_TRANSPOSED, "IDP.onConfigChanged");
         }
-        // Config changes, what shall we do?
-        InvariantDeviceProfile oldProfile = new InvariantDeviceProfile(this);
 
         // Re-init grid
         String gridName = getCurrentGridName(context);
         initGrid(context, gridName);
 
-        int changeFlags = 0;
-        if (numRows != oldProfile.numRows ||
-                numColumns != oldProfile.numColumns ||
-                numFolderColumns != oldProfile.numFolderColumns ||
-                numFolderRows != oldProfile.numFolderRows ||
-                numDatabaseHotseatIcons != oldProfile.numDatabaseHotseatIcons) {
-            changeFlags |= CHANGE_FLAG_GRID;
-        }
-
-        if (iconSize != oldProfile.iconSize || iconBitmapSize != oldProfile.iconBitmapSize ||
-                !iconShapePath.equals(oldProfile.iconShapePath)) {
-            changeFlags |= CHANGE_FLAG_ICON_PARAMS;
-        }
-        if (!iconShapePath.equals(oldProfile.iconShapePath)) {
-            IconShape.init(context);
-        }
-
-        apply(changeFlags);
-    }
-
-    private void apply(int changeFlags) {
         for (OnIDPChangeListener listener : mChangeListeners) {
-            listener.onIdpChanged(changeFlags, this);
+            listener.onIdpChanged(this);
         }
     }
 
@@ -650,7 +585,10 @@
 
     public interface OnIDPChangeListener {
 
-        void onIdpChanged(int changeFlags, InvariantDeviceProfile profile);
+        /**
+         * Called when the device provide changes
+         */
+        void onIdpChanged(InvariantDeviceProfile profile);
     }
 
 
@@ -809,18 +747,4 @@
             return this;
         }
     }
-
-    private class OverlayMonitor extends BroadcastReceiver {
-
-        private final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
-
-        OverlayMonitor(Context context) {
-            context.registerReceiver(this, getPackageFilter("android", ACTION_OVERLAY_CHANGED));
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            onConfigChanged(context);
-        }
-    }
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 09c7b7a..deb1147 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -565,11 +565,7 @@
     }
 
     @Override
-    public void onIdpChanged(int changeFlags, InvariantDeviceProfile idp) {
-        onIdpChanged(idp);
-    }
-
-    private void onIdpChanged(InvariantDeviceProfile idp) {
+    public void onIdpChanged(InvariantDeviceProfile idp) {
         if (TestProtocol.sDebugTracing) {
             Log.d(TestProtocol.LAUNCHER_NOT_TRANSPOSED, "onIdpChanged");
         }
@@ -2780,7 +2776,7 @@
      * @see LauncherState#getOverviewScaleAndOffset(Launcher)
      */
     public float[] getNormalOverviewScaleAndOffset() {
-        return new float[] {NO_SCALE, NO_OFFSET};
+        return new float[] {NO_SCALE, NO_OFFSET, NO_OFFSET};
     }
 
     /**
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 11585f9..834b5a7 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -16,7 +16,8 @@
 
 package com.android.launcher3;
 
-import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
+import static com.android.launcher3.Utilities.getDevicePrefs;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_THEMED_ICONS;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
 
@@ -24,12 +25,13 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.LauncherApps;
-import android.os.Handler;
+import android.os.UserHandle;
 import android.util.Log;
 
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.IconProvider;
 import com.android.launcher3.icons.LauncherIcons;
@@ -39,6 +41,7 @@
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
@@ -47,6 +50,7 @@
 public class LauncherAppState {
 
     public static final String ACTION_FORCE_ROLOAD = "force-reload-launcher";
+    private static final String KEY_ICON_STATE = "pref_icon_shape_path";
 
     // We do not need any synchronization for this variable as its only written on UI thread.
     public static final MainThreadInitializedObject<LauncherAppState> INSTANCE =
@@ -54,16 +58,11 @@
 
     private final Context mContext;
     private final LauncherModel mModel;
+    private final IconProvider mIconProvider;
     private final IconCache mIconCache;
     private final WidgetPreviewLoader mWidgetCache;
     private final InvariantDeviceProfile mInvariantDeviceProfile;
-    private SettingsCache.OnChangeListener mNotificationSettingsChangedListener;
-
-    private SettingsCache mSettingsCache;
-    private InstallSessionTracker mInstallSessionTracker;
-    private SimpleBroadcastReceiver mModelChangeReceiver;
-    private SafeCloseable mCalendarChangeTracker;
-    private SafeCloseable mUserChangeListener;
+    private final RunnableList mOnTerminateCallback = new RunnableList();
 
     public static LauncherAppState getInstance(final Context context) {
         return INSTANCE.get(context);
@@ -80,40 +79,47 @@
     public LauncherAppState(Context context) {
         this(context, LauncherFiles.APP_ICONS_DB);
 
-        mModelChangeReceiver = new SimpleBroadcastReceiver(mModel::onBroadcastIntent);
+        mInvariantDeviceProfile.addOnChangeListener(idp -> refreshAndReloadLauncher());
 
         mContext.getSystemService(LauncherApps.class).registerCallback(mModel);
-        mModelChangeReceiver.register(mContext, Intent.ACTION_LOCALE_CHANGED,
+
+        SimpleBroadcastReceiver modelChangeReceiver =
+                new SimpleBroadcastReceiver(mModel::onBroadcastIntent);
+        modelChangeReceiver.register(mContext, Intent.ACTION_LOCALE_CHANGED,
                 Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
                 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
                 Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
         if (FeatureFlags.IS_STUDIO_BUILD) {
-            mModelChangeReceiver.register(mContext, ACTION_FORCE_ROLOAD);
+            modelChangeReceiver.register(mContext, ACTION_FORCE_ROLOAD);
         }
-
-        mCalendarChangeTracker = IconProvider.registerIconChangeListener(mContext,
-                mModel::onAppIconChanged, MODEL_EXECUTOR.getHandler());
+        mOnTerminateCallback.add(() -> mContext.unregisterReceiver(modelChangeReceiver));
 
         // TODO: remove listener on terminate
         FeatureFlags.APP_SEARCH_IMPROVEMENTS.addChangeListener(context, mModel::forceReload);
         CustomWidgetManager.INSTANCE.get(mContext)
                 .setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts);
 
-        mUserChangeListener = UserCache.INSTANCE.get(mContext)
+        SafeCloseable userChangeListener = UserCache.INSTANCE.get(mContext)
                 .addUserChangeListener(mModel::forceReload);
+        mOnTerminateCallback.add(userChangeListener::close);
 
-        mInvariantDeviceProfile.addOnChangeListener(this::onIdpChanged);
-        new Handler().post( () -> mInvariantDeviceProfile.verifyConfigChangedInBackground(context));
+        IconObserver observer = new IconObserver();
+        SafeCloseable iconChangeTracker = mIconProvider.registerIconChangeListener(
+                observer, MODEL_EXECUTOR.getHandler());
+        mOnTerminateCallback.add(iconChangeTracker::close);
+        MODEL_EXECUTOR.execute(observer::verifyIconChanged);
 
-        mInstallSessionTracker = InstallSessionHelper.INSTANCE.get(context)
-                .registerInstallTracker(mModel);
+        InstallSessionTracker installSessionTracker =
+                InstallSessionHelper.INSTANCE.get(context).registerInstallTracker(mModel);
+        mOnTerminateCallback.add(installSessionTracker::unregister);
 
         // Register an observer to rebind the notification listener when dots are re-enabled.
-        mSettingsCache = SettingsCache.INSTANCE.get(mContext);
-        mNotificationSettingsChangedListener = this::onNotificationSettingsChanged;
-        mSettingsCache.register(NOTIFICATION_BADGING_URI,
-                mNotificationSettingsChangedListener);
-        onNotificationSettingsChanged(mSettingsCache.getValue(NOTIFICATION_BADGING_URI));
+        SettingsCache settingsCache = SettingsCache.INSTANCE.get(mContext);
+        SettingsCache.OnChangeListener notificationLister = this::onNotificationSettingsChanged;
+        settingsCache.register(NOTIFICATION_BADGING_URI, notificationLister);
+        onNotificationSettingsChanged(settingsCache.getValue(NOTIFICATION_BADGING_URI));
+        mOnTerminateCallback.add(() ->
+                settingsCache.unregister(NOTIFICATION_BADGING_URI, notificationLister));
     }
 
     public LauncherAppState(Context context, @Nullable String iconCacheFileName) {
@@ -122,30 +128,25 @@
         mContext = context;
 
         mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context);
-
-        mIconCache = new IconCache(mContext, mInvariantDeviceProfile, iconCacheFileName);
+        mIconProvider =  new IconProvider(context, ENABLE_THEMED_ICONS.get());
+        mIconCache = new IconCache(mContext, mInvariantDeviceProfile,
+                iconCacheFileName, mIconProvider);
         mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
         mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext));
     }
 
-    protected void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) {
+    private void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) {
         if (areNotificationDotsEnabled) {
             NotificationListener.requestRebind(new ComponentName(
                     mContext, NotificationListener.class));
         }
     }
 
-    private void onIdpChanged(int changeFlags, InvariantDeviceProfile idp) {
-        if (changeFlags == 0) {
-            return;
-        }
-
-        if ((changeFlags & CHANGE_FLAG_ICON_PARAMS) != 0) {
-            LauncherIcons.clearPool();
-            mIconCache.updateIconParams(idp.fillResIconDpi, idp.iconBitmapSize);
-            mWidgetCache.refresh();
-        }
-
+    private void refreshAndReloadLauncher() {
+        LauncherIcons.clearPool();
+        mIconCache.updateIconParams(
+                mInvariantDeviceProfile.fillResIconDpi, mInvariantDeviceProfile.iconBitmapSize);
+        mWidgetCache.refresh();
         mModel.forceReload();
     }
 
@@ -154,25 +155,13 @@
      */
     public void onTerminate() {
         mModel.destroy();
-        if (mModelChangeReceiver != null) {
-            mContext.unregisterReceiver(mModelChangeReceiver);
-        }
         mContext.getSystemService(LauncherApps.class).unregisterCallback(mModel);
-        if (mInstallSessionTracker != null) {
-            mInstallSessionTracker.unregister();
-        }
-        if (mCalendarChangeTracker != null) {
-            mCalendarChangeTracker.close();
-        }
-        if (mUserChangeListener != null) {
-            mUserChangeListener.close();
-        }
         CustomWidgetManager.INSTANCE.get(mContext).setWidgetRefreshCallback(null);
+        mOnTerminateCallback.executeAllAndDestroy();
+    }
 
-        if (mSettingsCache != null) {
-            mSettingsCache.unregister(NOTIFICATION_BADGING_URI,
-                    mNotificationSettingsChangedListener);
-        }
+    public IconProvider getIconProvider() {
+        return mIconProvider;
     }
 
     public IconCache getIconCache() {
@@ -197,4 +186,26 @@
     public static InvariantDeviceProfile getIDP(Context context) {
         return InvariantDeviceProfile.INSTANCE.get(context);
     }
+
+    private class IconObserver implements IconProvider.IconChangeListener {
+
+        @Override
+        public void onAppIconChanged(String packageName, UserHandle user) {
+            mModel.onAppIconChanged(packageName, user);
+        }
+
+        @Override
+        public void onSystemIconStateChanged(String iconState) {
+            IconShape.init(mContext);
+            refreshAndReloadLauncher();
+            getDevicePrefs(mContext).edit().putString(KEY_ICON_STATE, iconState).apply();
+        }
+
+        void verifyIconChanged() {
+            String iconState = mIconProvider.getSystemIconState();
+            if (!iconState.equals(getDevicePrefs(mContext).getString(KEY_ICON_STATE, ""))) {
+                onSystemIconStateChanged(iconState);
+            }
+        }
+    }
 }
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 4c11725..9d50edd 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -172,10 +172,12 @@
     }
 
     /**
-     * Returns an array of two elements.
+     * Returns an array of three elements.
      * The first specifies the scale for the overview
      * The second is the factor ([0, 1], 0 => center-screen; 1 => offscreen) by which overview
      * should be shifted horizontally.
+     * The third is the factor ([0, 1], 0 => center-screen; 1 => offscreen) by which overview
+     * should be shifted vertically.
      */
     public float[] getOverviewScaleAndOffset(Launcher launcher) {
         return launcher.getNormalOverviewScaleAndOffset();
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 972a6e8..7c00362 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -661,7 +661,7 @@
                     .resolveActivity(info.getIntent(), info.user);
             outObj[0] = activityInfo;
             return activityInfo == null ? null : LauncherAppState.getInstance(launcher)
-                    .getIconCache().getIconProvider().getIcon(
+                    .getIconProvider().getIcon(
                             activityInfo, launcher.getDeviceProfile().inv.fillResIconDpi);
         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
             if (info instanceof PendingAddShortcutInfo) {
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index bc93a1e..297325a 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -81,10 +81,11 @@
     private int mPendingIconRequestCount = 0;
 
     public IconCache(Context context, InvariantDeviceProfile idp) {
-        this(context, idp, LauncherFiles.APP_ICONS_DB);
+        this(context, idp, LauncherFiles.APP_ICONS_DB, new IconProvider(context));
     }
 
-    public IconCache(Context context, InvariantDeviceProfile idp, String dbFileName) {
+    public IconCache(Context context, InvariantDeviceProfile idp, String dbFileName,
+            IconProvider iconProvider) {
         super(context, dbFileName, MODEL_EXECUTOR.getLooper(),
                 idp.fillResIconDpi, idp.iconBitmapSize, true /* inMemoryCache */);
         mComponentWithLabelCachingLogic = new ComponentCachingLogic(context, false);
@@ -93,7 +94,7 @@
         mLauncherApps = mContext.getSystemService(LauncherApps.class);
         mUserManager = UserCache.INSTANCE.get(mContext);
         mInstantAppResolver = InstantAppResolver.newInstance(mContext);
-        mIconProvider = new IconProvider(context, true /* supportsIconTheme */);
+        mIconProvider = iconProvider;
     }
 
     @Override
@@ -106,10 +107,6 @@
         return mInstantAppResolver.isInstantApp(info);
     }
 
-    public IconProvider getIconProvider() {
-        return mIconProvider;
-    }
-
     @Override
     public BaseIconFactory getIconFactory() {
         return LauncherIcons.obtain(mContext);
diff --git a/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java b/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
index 8fc3977..e820ac4 100644
--- a/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
+++ b/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
@@ -58,7 +58,7 @@
     public BitmapInfo loadIcon(Context context, LauncherActivityInfo object) {
         try (LauncherIcons li = LauncherIcons.obtain(context)) {
             return li.createBadgedIconBitmap(LauncherAppState.getInstance(context)
-                            .getIconCache().getIconProvider().getIcon(object, li.mFillResIconDpi),
+                            .getIconProvider().getIcon(object, li.mFillResIconDpi),
                     object.getUser(), object.getApplicationInfo().targetSdkVersion);
         }
     }
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index 0754c29..6813b97 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3.model.data;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_THEMED_ICONS;
-
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -232,7 +230,7 @@
      * Returns a FastBitmapDrawable with the icon and context theme applied
      */
     public FastBitmapDrawable newIcon(Context context, boolean applyTheme) {
-        FastBitmapDrawable drawable = applyTheme && ENABLE_THEMED_ICONS.get()
+        FastBitmapDrawable drawable = applyTheme
                 ? bitmap.newThemedIcon(context) : bitmap.newIcon(context);
         drawable.setIsDisabled(isDisabled());
         return drawable;