Merge "Icon size should update when grid config changes" into ub-launcher3-master
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 1a485ed..70f0c01 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -20,7 +20,7 @@
 <manifest
     xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.launcher3">
-    <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="21"/>
+    <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="25"/>
     <!--
     Manifest entries specific to Launcher3. This is merged with AndroidManifest-common.xml.
     Refer comments around specific entries on how to extend individual components.
@@ -61,6 +61,9 @@
                 <category android:name="android.intent.category.MONKEY"/>
                 <category android:name="android.intent.category.LAUNCHER_APP" />
             </intent-filter>
+            <meta-data
+                android:name="com.android.launcher3.grid.control"
+                android:value="${packageName}.grid_control" />
         </activity>
 
     </application>
diff --git a/build.gradle b/build.gradle
index 4191d47..ab97687 100644
--- a/build.gradle
+++ b/build.gradle
@@ -17,7 +17,7 @@
     buildToolsVersion BUILD_TOOLS_VERSION
 
     defaultConfig {
-        minSdkVersion 21
+        minSdkVersion 25
         targetSdkVersion 28
         versionCode 1
         versionName "1.0"
diff --git a/go/AndroidManifest.xml b/go/AndroidManifest.xml
index 25518af..fae1eff 100644
--- a/go/AndroidManifest.xml
+++ b/go/AndroidManifest.xml
@@ -22,7 +22,7 @@
     xmlns:tools="http://schemas.android.com/tools"
     package="com.android.launcher3" >
 
-    <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="21"/>
+    <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="25"/>
 
     <application
         android:backupAgent="com.android.launcher3.LauncherBackupAgent"
diff --git a/go/quickstep/res/layout/icon_recents_root_view.xml b/go/quickstep/res/layout/icon_recents_root_view.xml
index 122fadf..82d5890 100644
--- a/go/quickstep/res/layout/icon_recents_root_view.xml
+++ b/go/quickstep/res/layout/icon_recents_root_view.xml
@@ -14,11 +14,17 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<!-- TODO(114136250): Remove this temporary placeholder view for Go recents -->
-<TextView
+<com.android.quickstep.views.IconRecentsView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:gravity="center"
-    android:text="Stub!"
-    android:textSize="40sp"/>
\ No newline at end of file
+    android:gravity="center">
+    <!-- TODO(114136250): Remove this temporary placeholder view for Go recents -->
+    <TextView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:text="Stub!"
+        android:textSize="40sp"/>
+</com.android.quickstep.views.IconRecentsView>
\ No newline at end of file
diff --git a/quickstep/res/values/override.xml b/go/quickstep/res/values/override.xml
similarity index 88%
copy from quickstep/res/values/override.xml
copy to go/quickstep/res/values/override.xml
index d683659..7636fb3 100644
--- a/quickstep/res/values/override.xml
+++ b/go/quickstep/res/values/override.xml
@@ -14,8 +14,10 @@
      limitations under the License.
 -->
 
+<!-- Class overrides for Go version of launcher with Go recents. -->
+
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string name="app_transition_manager_class" translatable="false">com.android.launcher3.LauncherAppTransitionManagerImpl</string>
+  <string name="app_transition_manager_class" translatable="false">com.android.launcher3.GoLauncherAppTransitionManagerImpl</string>
 
   <string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
 
diff --git a/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java b/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java
new file mode 100644
index 0000000..95d8c4e
--- /dev/null
+++ b/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java
@@ -0,0 +1,65 @@
+package com.android.launcher3;
+
+import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.quickstep.views.IconRecentsView.CONTENT_ALPHA;
+
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.view.View;
+
+import com.android.quickstep.views.IconRecentsView;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+/**
+ * A {@link QuickstepAppTransitionManagerImpl} with recents-specific app transitions based off
+ * {@link com.android.quickstep.views.IconRecentsView}.
+ */
+public final class GoLauncherAppTransitionManagerImpl extends QuickstepAppTransitionManagerImpl {
+
+    public GoLauncherAppTransitionManagerImpl(Context context) {
+        super(context);
+    }
+
+    @Override
+    protected boolean isLaunchingFromRecents(View v, RemoteAnimationTargetCompat[] targets) {
+        return mLauncher.getStateManager().getState().overviewUi;
+    }
+
+    @Override
+    protected boolean isQuickSwitchInProgress() {
+        // Go does not support quick scrub.
+        return false;
+    }
+
+    @Override
+    protected ActivityOptions getQuickSwitchActivityOptions() {
+        // Go does not support quick scrub.
+        return null;
+    }
+
+    @Override
+    protected void composeRecentsLaunchAnimator(AnimatorSet anim, View v,
+            RemoteAnimationTargetCompat[] targets, boolean launcherClosing) {
+        //TODO: Implement this based off IconRecentsView
+    }
+
+    @Override
+    protected Runnable composeViewContentAnimator(AnimatorSet anim, float[] alphas, float[] trans) {
+        IconRecentsView overview = mLauncher.getOverviewPanel();
+        ObjectAnimator alpha = ObjectAnimator.ofFloat(overview,
+                CONTENT_ALPHA, alphas);
+        alpha.setDuration(CONTENT_ALPHA_DURATION);
+        alpha.setInterpolator(LINEAR);
+        anim.play(alpha);
+
+        ObjectAnimator transY = ObjectAnimator.ofFloat(overview, View.TRANSLATION_Y, trans);
+        transY.setInterpolator(AGGRESSIVE_EASE);
+        transY.setDuration(CONTENT_TRANSLATION_DURATION);
+        anim.play(transY);
+
+        return mLauncher.getStateManager()::reapplyState;
+    }
+}
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
new file mode 100644
index 0000000..283e349
--- /dev/null
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.uioverrides;
+
+import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
+
+import android.graphics.Rect;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.quickstep.RecentsModel;
+
+/**
+ * Definition for overview state
+ */
+public class OverviewState extends LauncherState {
+
+    // TODO: Remove this when we refactor BackgroundAppState
+    protected static final Rect sTempRect = new Rect();
+
+    private static final int STATE_FLAGS = FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED
+            | FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI | FLAG_DISABLE_ACCESSIBILITY;
+
+    public OverviewState(int id) {
+        this(id, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
+    }
+
+    protected OverviewState(int id, int transitionDuration, int stateFlags) {
+        super(id, LauncherLogProto.ContainerType.TASKSWITCHER, transitionDuration, stateFlags);
+    }
+
+    @Override
+    public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
+        return new float[] {1f, 0f};
+    }
+
+    @Override
+    public void onStateDisabled(Launcher launcher) {
+        RecentsModel.INSTANCE.get(launcher).resetAssistCache();
+    }
+
+    @Override
+    public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
+        return new PageAlphaProvider(DEACCEL_2) {
+            @Override
+            public float getPageAlpha(int pageIndex) {
+                return 0;
+            }
+        };
+    }
+
+    @Override
+    public int getVisibleElements(Launcher launcher) {
+        return NONE;
+    }
+
+    @Override
+    public float getWorkspaceScrimAlpha(Launcher launcher) {
+        return 0.5f;
+    }
+
+    @Override
+    public String getDescription(Launcher launcher) {
+        return launcher.getString(R.string.accessibility_desc_recent_apps);
+    }
+
+    @Override
+    public void onBackPressed(Launcher launcher) {
+        // TODO: Add logic to go back to task if coming from a currently running task.
+        super.onBackPressed(launcher);
+    }
+
+
+    public static float getDefaultSwipeHeight(Launcher launcher) {
+        DeviceProfile dp = launcher.getDeviceProfile();
+        return dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
+    }
+}
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/PortraitOverviewStateTouchHelper.java b/go/quickstep/src/com/android/launcher3/uioverrides/PortraitOverviewStateTouchHelper.java
new file mode 100644
index 0000000..a3b41b0
--- /dev/null
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/PortraitOverviewStateTouchHelper.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.uioverrides;
+
+import android.view.MotionEvent;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.util.PendingAnimation;
+
+/**
+ * Helper class for {@link PortraitStatesTouchController} that determines swipeable regions and
+ * animations on the overview state that depend on the recents implementation.
+ */
+public final class PortraitOverviewStateTouchHelper {
+
+    public PortraitOverviewStateTouchHelper(Launcher launcher) {}
+
+    /**
+     * Whether or not {@link PortraitStatesTouchController} should intercept the touch when on the
+     * overview state.
+     *
+     * @param ev the motion event
+     * @return true if we should intercept the motion event
+     */
+    boolean canInterceptTouch(MotionEvent ev) {
+        // Go does not support swiping to all-apps from recents.
+        return false;
+    }
+
+    /**
+     * Whether or not swiping down to leave overview state should return to the currently running
+     * task app.
+     *
+     * @return true if going back should take the user to the currently running task
+     */
+    boolean shouldSwipeDownReturnToApp() {
+        // Go does not support swiping tasks down to launch tasks from recents.
+        return false;
+    }
+
+    /**
+     * Create the animation for going from overview to the task app via swiping.
+     *
+     * @param duration how long the animation should be
+     * @return the animation
+     */
+    PendingAnimation createSwipeDownToTaskAppAnimation(long duration) {
+        // Go does not support swiping tasks down to launch tasks from recents.
+        return null;
+    }
+}
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
index f2c9455..7381574 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
@@ -23,30 +23,40 @@
 import android.view.View;
 
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.LauncherStateManager.StateHandler;
-import com.android.launcher3.uioverrides.RecentsViewStateController;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.TouchController;
+import com.android.quickstep.OverviewInteractionState;
+
+import java.util.ArrayList;
 
 /**
  * Provides recents-related {@link UiFactory} logic and classes.
  */
-public final class RecentsUiFactory {
+public abstract class RecentsUiFactory {
 
     // Scale recents takes before animating in
     private static final float RECENTS_PREPARE_SCALE = 1.33f;
 
-    private RecentsUiFactory() {}
+    public static TouchController[] createTouchControllers(Launcher launcher) {
+        ArrayList<TouchController> list = new ArrayList<>();
+        list.add(launcher.getDragController());
 
-    /**
-     * Creates and returns a touch controller for swiping recents tasks.
-     *
-     * @param launcher the launcher activity
-     * @return the touch controller for recents tasks
-     */
-    public static TouchController createTaskSwipeController(Launcher launcher) {
-        // We leave all input handling to the view itself.
-        return null;
+        if (launcher.getDeviceProfile().isVerticalBarLayout()) {
+            list.add(new OverviewToAllAppsTouchController(launcher));
+            list.add(new LandscapeEdgeSwipeController(launcher));
+        } else {
+            boolean allowDragToOverview = OverviewInteractionState.INSTANCE.get(launcher)
+                    .isSwipeUpGestureEnabled();
+            list.add(new PortraitStatesTouchController(launcher, allowDragToOverview));
+        }
+        if (FeatureFlags.PULL_DOWN_STATUS_BAR && Utilities.IS_DEBUG_DEVICE
+                && !launcher.getDeviceProfile().isMultiWindowMode
+                && !launcher.getDeviceProfile().isVerticalBarLayout()) {
+            list.add(new StatusBarTouchController(launcher));
+        }
+        return list.toArray(new TouchController[list.size()]);
     }
 
     /**
@@ -56,7 +66,6 @@
      * @return state handler for recents
      */
     public static StateHandler createRecentsViewStateController(Launcher launcher) {
-        //TODO Override RecentsViewStateController on low RAM.
         return new RecentsViewStateController(launcher);
     }
 
@@ -65,7 +74,7 @@
      *
      * @param launcher the launcher activity
      */
-    public static void prepareToShowRecents(Launcher launcher) {
+    public static void prepareToShowOverview(Launcher launcher) {
         View overview = launcher.getOverviewPanel();
         if (overview.getVisibility() != VISIBLE) {
             SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
@@ -77,7 +86,7 @@
      *
      * @param launcher the launcher activity
      */
-    public static void resetRecents(Launcher launcher) {}
+    public static void resetOverview(Launcher launcher) {}
 
     /**
      * Recents logic that triggers when launcher state changes or launcher activity stops/resumes.
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
new file mode 100644
index 0000000..f1cb75b
--- /dev/null
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.uioverrides;
+
+import static com.android.quickstep.views.IconRecentsView.CONTENT_ALPHA;
+import static com.android.quickstep.views.IconRecentsView.TRANSLATION_Y_FACTOR;
+
+import android.util.FloatProperty;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.Launcher;
+import com.android.quickstep.views.IconRecentsView;
+
+/**
+ * State handler for Go's {@link IconRecentsView}.
+ */
+public final class RecentsViewStateController extends
+        BaseRecentsViewStateController<IconRecentsView> {
+
+    public RecentsViewStateController(@NonNull Launcher launcher) {
+        super(launcher);
+    }
+
+    @Override
+    FloatProperty<IconRecentsView> getTranslationYFactorProperty() {
+        return TRANSLATION_Y_FACTOR;
+    }
+
+    @Override
+    FloatProperty<IconRecentsView> getContentAlphaProperty() {
+        return CONTENT_ALPHA;
+    }
+}
diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
new file mode 100644
index 0000000..e4741e9
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.views;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.view.ViewDebug;
+import android.widget.FrameLayout;
+
+/**
+ * Root view for the icon recents view.
+ */
+public final class IconRecentsView extends FrameLayout {
+
+    public static final FloatProperty<IconRecentsView> TRANSLATION_Y_FACTOR =
+            new FloatProperty<IconRecentsView>("translationYFactor") {
+
+                @Override
+                public void setValue(IconRecentsView view, float v) {
+                    view.setTranslationYFactor(v);
+                }
+
+                @Override
+                public Float get(IconRecentsView view) {
+                    return view.mTranslationYFactor;
+                }
+            };
+
+    public static final FloatProperty<IconRecentsView> CONTENT_ALPHA =
+            new FloatProperty<IconRecentsView>("contentAlpha") {
+                @Override
+                public void setValue(IconRecentsView view, float v) {
+                    ALPHA.set(view, v);
+                }
+
+                @Override
+                public Float get(IconRecentsView view) {
+                    return ALPHA.get(view);
+                }
+            };
+
+    /**
+     * A ratio representing the view's relative placement within its padded space. For example, 0
+     * is top aligned and 0.5 is centered vertically.
+     */
+    @ViewDebug.ExportedProperty(category = "launcher")
+    private float mTranslationYFactor;
+
+    public IconRecentsView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void setTranslationYFactor(float translationFactor) {
+        mTranslationYFactor = translationFactor;
+        setTranslationY(computeTranslationYForFactor(mTranslationYFactor));
+    }
+
+    private float computeTranslationYForFactor(float translationYFactor) {
+        return translationYFactor * (getPaddingBottom() - getPaddingTop());
+    }
+}
diff --git a/gradle.properties b/gradle.properties
index b299cfe..e31f59e 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -4,7 +4,7 @@
 
 ANDROID_X_VERSION=1.0.0-beta01
 
-GRADLE_CLASS_PATH=com.android.tools.build:gradle:3.2.0-rc03
+GRADLE_CLASS_PATH=com.android.tools.build:gradle:3.3.0
 
 PROTOBUF_CLASS_PATH=com.google.protobuf:protobuf-gradle-plugin:0.8.6
 PROTOBUF_DEPENDENCY=com.google.protobuf.nano:protobuf-javanano:3.0.0-alpha-7
diff --git a/iconloaderlib/build.gradle b/iconloaderlib/build.gradle
index f6a820a..4fd3189 100644
--- a/iconloaderlib/build.gradle
+++ b/iconloaderlib/build.gradle
@@ -16,7 +16,7 @@
     publishNonDefault true
 
     defaultConfig {
-        minSdkVersion 21
+        minSdkVersion 25
         targetSdkVersion 28
         versionCode 1
         versionName "1.0"
diff --git a/iconloaderlib/src/com/android/launcher3/icons/FixedScaleDrawable.java b/iconloaderlib/src/com/android/launcher3/icons/FixedScaleDrawable.java
index e594f47..516965e 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/FixedScaleDrawable.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/FixedScaleDrawable.java
@@ -1,12 +1,10 @@
 package com.android.launcher3.icons;
 
-import android.annotation.TargetApi;
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.graphics.Canvas;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.DrawableWrapper;
-import android.os.Build;
 import android.util.AttributeSet;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -14,7 +12,6 @@
 /**
  * Extension of {@link DrawableWrapper} which scales the child drawables by a fixed amount.
  */
-@TargetApi(Build.VERSION_CODES.N)
 public class FixedScaleDrawable extends DrawableWrapper {
 
     // TODO b/33553066 use the constant defined in MaskableIconDrawable
diff --git a/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java b/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java
index 455c58c..5df8043 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java
@@ -122,7 +122,11 @@
         }
 
         public Bitmap createPill(int width, int height) {
-            radius = height / 2f;
+            return createPill(width, height, height / 2f);
+        }
+
+        public Bitmap createPill(int width, int height, float r) {
+            radius = r;
 
             int centerX = Math.round(width / 2f + shadowBlur);
             int centerY = Math.round(radius + shadowBlur + keyShadowDistance);
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
index ce66448..2966cb1 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
@@ -47,16 +47,15 @@
 import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.icons.GraphicsUtils;
 import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.Provider;
 import com.android.launcher3.util.SQLiteCacheHelper;
 
 import java.util.AbstractMap;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Supplier;
 
 import androidx.annotation.NonNull;
 
@@ -231,13 +230,8 @@
      * incorporates all the properties that can affect the cache like locale and system-version.
      */
     private void updateSystemState() {
-        final String locale;
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-            locale = mContext.getResources().getConfiguration().getLocales().toLanguageTags();
-        } else {
-            locale = Locale.getDefault().toString();
-        }
-
+        final String locale =
+                mContext.getResources().getConfiguration().getLocales().toLanguageTags();
         mSystemState = locale + "," + Build.VERSION.SDK_INT;
     }
 
@@ -309,7 +303,7 @@
      */
     protected <T> CacheEntry cacheLocked(
             @NonNull ComponentName componentName, @NonNull UserHandle user,
-            @NonNull Provider<T> infoProvider, @NonNull CachingLogic<T> cachingLogic,
+            @NonNull Supplier<T> infoProvider, @NonNull CachingLogic<T> cachingLogic,
             boolean usePackageIcon, boolean useLowResIcon) {
         return cacheLocked(componentName, user, infoProvider, cachingLogic, usePackageIcon,
                 useLowResIcon, true);
@@ -317,7 +311,7 @@
 
     protected <T> CacheEntry cacheLocked(
             @NonNull ComponentName componentName, @NonNull UserHandle user,
-            @NonNull Provider<T> infoProvider, @NonNull CachingLogic<T> cachingLogic,
+            @NonNull Supplier<T> infoProvider, @NonNull CachingLogic<T> cachingLogic,
             boolean usePackageIcon, boolean useLowResIcon, boolean addToMemCache) {
         assertWorkerThread();
         ComponentKey cacheKey = new ComponentKey(componentName, user);
diff --git a/iconloaderlib/src/com/android/launcher3/util/Provider.java b/iconloaderlib/src/com/android/launcher3/util/Provider.java
deleted file mode 100644
index 4a54c0f..0000000
--- a/iconloaderlib/src/com/android/launcher3/util/Provider.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2016 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.util;
-
-/**
- * Utility class to allow lazy initialization of objects.
- */
-public interface Provider<T> {
-
-    /**
-     * Initializes and returns the object. This may contain expensive operations not suitable
-     * to UI thread.
-     */
-    T get();
-
-    static <T> Provider<T> of (T value) {
-        return() -> value;
-    }
-}
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index 8af310c..ab97344 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/res/values/override.xml b/quickstep/recents_ui_overrides/res/values/override.xml
similarity index 95%
rename from quickstep/res/values/override.xml
rename to quickstep/recents_ui_overrides/res/values/override.xml
index d683659..c60cf5a 100644
--- a/quickstep/res/values/override.xml
+++ b/quickstep/recents_ui_overrides/res/values/override.xml
@@ -14,6 +14,8 @@
      limitations under the License.
 -->
 
+<!-- Class overrides for launcher with quickstep. -->
+
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
   <string name="app_transition_manager_class" translatable="false">com.android.launcher3.LauncherAppTransitionManagerImpl</string>
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
new file mode 100644
index 0000000..9921455
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.quickstep.TaskUtils.findTaskViewToLaunch;
+import static com.android.quickstep.TaskUtils.getRecentsWindowAnimator;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.Interpolators;
+import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+/**
+ * A {@link QuickstepAppTransitionManagerImpl} that also implements recents transitions from
+ * {@link RecentsView}.
+ */
+public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransitionManagerImpl {
+
+    private RecentsView mRecentsView;
+
+    public LauncherAppTransitionManagerImpl(Context context) {
+        super(context);
+        mRecentsView = mLauncher.getOverviewPanel();
+    }
+
+    @Override
+    protected boolean isLaunchingFromRecents(@NonNull View v,
+            @Nullable RemoteAnimationTargetCompat[] targets) {
+        return mLauncher.getStateManager().getState().overviewUi
+                && findTaskViewToLaunch(mLauncher, v, targets) != null;
+    }
+
+    @Override
+    protected boolean isQuickSwitchInProgress() {
+        return mRecentsView.getQuickScrubController().isQuickSwitch();
+    }
+
+    @Override
+    protected ActivityOptions getQuickSwitchActivityOptions() {
+        return ActivityOptions.makeCustomAnimation(mLauncher, R.anim.no_anim,
+                R.anim.no_anim);
+    }
+
+    @Override
+    protected void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
+            @NonNull RemoteAnimationTargetCompat[] targets, boolean launcherClosing) {
+        RecentsView recentsView = mLauncher.getOverviewPanel();
+        boolean skipLauncherChanges = !launcherClosing;
+        boolean isLaunchingFromQuickscrub =
+                recentsView.getQuickScrubController().isWaitingForTaskLaunch();
+
+        TaskView taskView = findTaskViewToLaunch(mLauncher, v, targets);
+
+        int duration = isLaunchingFromQuickscrub
+                ? RECENTS_QUICKSCRUB_LAUNCH_DURATION
+                : RECENTS_LAUNCH_DURATION;
+
+        ClipAnimationHelper helper = new ClipAnimationHelper(mLauncher);
+        anim.play(getRecentsWindowAnimator(taskView, skipLauncherChanges, targets, helper)
+                .setDuration(duration));
+
+        Animator childStateAnimation = null;
+        // Found a visible recents task that matches the opening app, lets launch the app from there
+        Animator launcherAnim;
+        final AnimatorListenerAdapter windowAnimEndListener;
+        if (launcherClosing) {
+            launcherAnim = recentsView.createAdjacentPageAnimForTaskLaunch(taskView, helper);
+            launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
+            launcherAnim.setDuration(duration);
+
+            // Make sure recents gets fixed up by resetting task alphas and scales, etc.
+            windowAnimEndListener = new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mLauncher.getStateManager().moveToRestState();
+                    mLauncher.getStateManager().reapplyState();
+                }
+            };
+        } else {
+            AnimatorPlaybackController controller =
+                    mLauncher.getStateManager().createAnimationToNewWorkspace(NORMAL, duration);
+            controller.dispatchOnStart();
+            childStateAnimation = controller.getTarget();
+            launcherAnim = controller.getAnimationPlayer().setDuration(duration);
+            windowAnimEndListener = new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mLauncher.getStateManager().goToState(NORMAL, false);
+                }
+            };
+        }
+        anim.play(launcherAnim);
+
+        // Set the current animation first, before adding windowAnimEndListener. Setting current
+        // animation adds some listeners which need to be called before windowAnimEndListener
+        // (the ordering of listeners matter in this case).
+        mLauncher.getStateManager().setCurrentAnimation(anim, childStateAnimation);
+        anim.addListener(windowAnimEndListener);
+    }
+
+    @Override
+    protected Runnable composeViewContentAnimator(@NonNull AnimatorSet anim, float[] alphas,
+            float[] trans) {
+        RecentsView overview = mLauncher.getOverviewPanel();
+        ObjectAnimator alpha = ObjectAnimator.ofFloat(overview,
+                RecentsView.CONTENT_ALPHA, alphas);
+        alpha.setDuration(CONTENT_ALPHA_DURATION);
+        alpha.setInterpolator(LINEAR);
+        anim.play(alpha);
+
+        ObjectAnimator transY = ObjectAnimator.ofFloat(overview, View.TRANSLATION_Y, trans);
+        transY.setInterpolator(AGGRESSIVE_EASE);
+        transY.setDuration(CONTENT_TRANSLATION_DURATION);
+        anim.play(transY);
+
+        return mLauncher.getStateManager()::reapplyState;
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/FlingAndHoldTouchController.java
new file mode 100644
index 0000000..fb83cd3
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/FlingAndHoldTouchController.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.uioverrides;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherStateManager.NON_ATOMIC_COMPONENT;
+
+import android.animation.ValueAnimator;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.quickstep.util.MotionPauseDetector;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * Touch controller which handles swipe and hold to go to Overview
+ */
+public class FlingAndHoldTouchController extends PortraitStatesTouchController {
+
+    private final MotionPauseDetector mMotionPauseDetector;
+
+    public FlingAndHoldTouchController(Launcher l) {
+        super(l, false /* allowDragToOverview */);
+        mMotionPauseDetector = new MotionPauseDetector(l);
+    }
+
+    @Override
+    public void onDragStart(boolean start) {
+        mMotionPauseDetector.clear();
+
+        super.onDragStart(start);
+
+        if (mStartState == NORMAL) {
+            mMotionPauseDetector.setOnMotionPauseListener(isPaused -> {
+                RecentsView recentsView = mLauncher.getOverviewPanel();
+                recentsView.setOverviewStateEnabled(isPaused);
+                maybeUpdateAtomicAnim(NORMAL, OVERVIEW, isPaused ? 1 : 0);
+            });
+        }
+    }
+
+    @Override
+    public boolean onDrag(float displacement) {
+        mMotionPauseDetector.addPosition(displacement);
+        return super.onDrag(displacement);
+    }
+
+    @Override
+    public void onDragEnd(float velocity, boolean fling) {
+        if (mMotionPauseDetector.isPaused() && mStartState == NORMAL) {
+            float range = getShiftRange();
+            long maxAccuracy = (long) (2 * range);
+
+            // Let the state manager know that the animation didn't go to the target state,
+            // but don't cancel ourselves (we already clean up when the animation completes).
+            Runnable onCancel = mCurrentAnimation.getOnCancelRunnable();
+            mCurrentAnimation.setOnCancelRunnable(null);
+            mCurrentAnimation.dispatchOnCancel();
+            mCurrentAnimation = mLauncher.getStateManager()
+                    .createAnimationToNewWorkspace(OVERVIEW, new AnimatorSetBuilder(), maxAccuracy,
+                            onCancel, NON_ATOMIC_COMPONENT);
+
+            final int logAction = fling ? Touch.FLING : Touch.SWIPE;
+            mCurrentAnimation.setEndAction(() -> onSwipeInteractionCompleted(OVERVIEW, logAction));
+
+
+            ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
+            maybeUpdateAtomicAnim(NORMAL, OVERVIEW, 1f);
+            mCurrentAnimation.dispatchOnStartWithVelocity(1, velocity);
+
+            // TODO: Find a better duration
+            anim.setDuration(100);
+            anim.start();
+            settleAtomicAnimation(1f, anim.getDuration());
+        } else {
+            super.onDragEnd(velocity, fling);
+        }
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java
similarity index 100%
rename from quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
rename to quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PortraitOverviewStateTouchHelper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PortraitOverviewStateTouchHelper.java
new file mode 100644
index 0000000..eead4c8
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PortraitOverviewStateTouchHelper.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.uioverrides;
+
+import static com.android.launcher3.uioverrides.PortraitStatesTouchController.isTouchOverHotseat;
+
+import android.view.MotionEvent;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.util.PendingAnimation;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
+
+/**
+ * Helper class for {@link PortraitStatesTouchController} that determines swipeable regions and
+ * animations on the overview state that depend on the recents implementation.
+ */
+public final class PortraitOverviewStateTouchHelper {
+
+    RecentsView mRecentsView;
+    Launcher mLauncher;
+
+    public PortraitOverviewStateTouchHelper(Launcher launcher) {
+        mLauncher = launcher;
+        mRecentsView = launcher.getOverviewPanel();
+    }
+
+    /**
+     * Whether or not {@link PortraitStatesTouchController} should intercept the touch when on the
+     * overview state.
+     *
+     * @param ev the motion event
+     * @return true if we should intercept the motion event
+     */
+    boolean canInterceptTouch(MotionEvent ev) {
+        if (mRecentsView.getChildCount() > 0) {
+            // Allow swiping up in the gap between the hotseat and overview.
+            return ev.getY() >= mRecentsView.getChildAt(0).getBottom();
+        } else {
+            // If there are no tasks, we only intercept if we're below the hotseat height.
+            return isTouchOverHotseat(mLauncher, ev);
+        }
+    }
+
+    /**
+     * Whether or not swiping down to leave overview state should return to the currently running
+     * task app.
+     *
+     * @return true if going back should take the user to the currently running task
+     */
+    boolean shouldSwipeDownReturnToApp() {
+        TaskView taskView = mRecentsView.getTaskViewAt(mRecentsView.getNextPage());
+        return taskView != null && mRecentsView.shouldSwipeDownLaunchApp();
+    }
+
+    /**
+     * Create the animation for going from overview to the task app via swiping. Should only be
+     * called when {@link #shouldSwipeDownReturnToApp()} returns true.
+     *
+     * @param duration how long the animation should be
+     * @return the animation
+     */
+    PendingAnimation createSwipeDownToTaskAppAnimation(long duration) {
+        TaskView taskView = mRecentsView.getTaskViewAt(mRecentsView.getNextPage());
+        if (taskView == null) {
+            throw new IllegalStateException("There is no task view to animate to.");
+        }
+        return mRecentsView.createTaskLauncherAnimation(taskView, duration);
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
index f18f43c..51e9495 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
@@ -21,32 +21,65 @@
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.config.FeatureFlags.SWIPE_HOME;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.TouchController;
+import com.android.launcher3.util.UiThreadHelper;
+import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
+import com.android.quickstep.OverviewInteractionState;
 import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.system.WindowManagerWrapper;
+
+import java.util.ArrayList;
 
 /**
  * Provides recents-related {@link UiFactory} logic and classes.
  */
-public final class RecentsUiFactory {
+public abstract class RecentsUiFactory {
+
+    private static final AsyncCommand SET_SHELF_HEIGHT_CMD = (visible, height) ->
+            WindowManagerWrapper.getInstance().setShelfHeight(visible != 0, height);
 
     // Scale recents takes before animating in
     private static final float RECENTS_PREPARE_SCALE = 1.33f;
 
-    private RecentsUiFactory() {}
+    public static TouchController[] createTouchControllers(Launcher launcher) {
+        boolean swipeUpEnabled = OverviewInteractionState.INSTANCE.get(launcher)
+                .isSwipeUpGestureEnabled();
+        boolean swipeUpToHome = swipeUpEnabled && SWIPE_HOME.get();
 
-    /**
-     * Creates and returns a touch controller for swiping recents tasks.
-     *
-     * @param launcher the launcher activity
-     * @return the touch controller for recents tasks
-     */
-    public static TouchController createTaskSwipeController(Launcher launcher) {
-        return new LauncherTaskViewController(launcher);
+
+        ArrayList<TouchController> list = new ArrayList<>();
+        list.add(launcher.getDragController());
+
+        if (swipeUpToHome) {
+            list.add(new FlingAndHoldTouchController(launcher));
+            list.add(new OverviewToAllAppsTouchController(launcher));
+        } else {
+            if (launcher.getDeviceProfile().isVerticalBarLayout()) {
+                list.add(new OverviewToAllAppsTouchController(launcher));
+                list.add(new LandscapeEdgeSwipeController(launcher));
+            } else {
+                list.add(new PortraitStatesTouchController(launcher,
+                        swipeUpEnabled /* allowDragToOverview */));
+            }
+        }
+
+        if (FeatureFlags.PULL_DOWN_STATUS_BAR && Utilities.IS_DEBUG_DEVICE
+                && !launcher.getDeviceProfile().isMultiWindowMode
+                && !launcher.getDeviceProfile().isVerticalBarLayout()) {
+            list.add(new StatusBarTouchController(launcher));
+        }
+
+        list.add(new LauncherTaskViewController(launcher));
+        return list.toArray(new TouchController[list.size()]);
     }
 
     /**
@@ -64,7 +97,7 @@
      *
      * @param launcher the launcher activity
      */
-    public static void prepareToShowRecents(Launcher launcher) {
+    public static void prepareToShowOverview(Launcher launcher) {
         RecentsView overview = launcher.getOverviewPanel();
         if (overview.getVisibility() != VISIBLE || overview.getContentAlpha() == 0) {
             SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
@@ -76,9 +109,8 @@
      *
      * @param launcher the launcher activity
      */
-    public static void resetRecents(Launcher launcher) {
-        RecentsView recents = launcher.getOverviewPanel();
-        recents.reset();
+    public static void resetOverview(Launcher launcher) {
+        launcher.<RecentsView>getOverviewPanel().reset();
     }
 
     /**
@@ -88,6 +120,14 @@
      */
     public static void onLauncherStateOrResumeChanged(Launcher launcher) {
         LauncherState state = launcher.getStateManager().getState();
+        if (!OverviewInteractionState.INSTANCE.get(launcher).swipeGestureInitializing()) {
+            DeviceProfile profile = launcher.getDeviceProfile();
+            boolean visible = (state == NORMAL || state == OVERVIEW) && launcher.isUserActive()
+                    && !profile.isVerticalBarLayout();
+            UiThreadHelper.runAsyncCommand(launcher, SET_SHELF_HEIGHT_CMD,
+                    visible ? 1 : 0, profile.hotseatBarSizePx);
+        }
+
         if (state == NORMAL) {
             launcher.<RecentsView>getOverviewPanel().setSwipeDownShouldLaunchApp(false);
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
new file mode 100644
index 0000000..7d7946d
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.uioverrides;
+
+import static com.android.launcher3.LauncherState.FAST_OVERVIEW;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_START_INTERPOLATOR;
+import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_TRANSLATION_Y_FACTOR;
+import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_Y_FACTOR;
+import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
+
+import android.animation.ValueAnimator;
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.util.FloatProperty;
+import android.view.animation.Interpolator;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager.AnimationConfig;
+import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.Interpolators;
+import com.android.quickstep.views.LauncherRecentsView;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * State handler for handling UI changes for {@link LauncherRecentsView}. In addition to managing
+ * the basic view properties, this class also manages changes in the task visuals.
+ */
+@TargetApi(Build.VERSION_CODES.O)
+public final class RecentsViewStateController extends
+        BaseRecentsViewStateController<LauncherRecentsView> {
+
+    public RecentsViewStateController(Launcher launcher) {
+        super(launcher);
+    }
+
+    @Override
+    public void setState(@NonNull LauncherState state) {
+        super.setState(state);
+        if (state.overviewUi) {
+            mRecentsView.updateEmptyMessage();
+            mRecentsView.resetTaskVisuals();
+        }
+    }
+
+    @Override
+    void setStateWithAnimationInternal(@NonNull final LauncherState toState,
+            @NonNull AnimatorSetBuilder builder, @NonNull AnimationConfig config) {
+        super.setStateWithAnimationInternal(toState, builder, config);
+
+        if (!toState.overviewUi) {
+            builder.addOnFinishRunnable(mRecentsView::resetTaskVisuals);
+        }
+
+        if (toState.overviewUi) {
+            ValueAnimator updateAnim = ValueAnimator.ofFloat(0, 1);
+            updateAnim.addUpdateListener(valueAnimator -> {
+                // While animating into recents, update the visible task data as needed
+                mRecentsView.loadVisibleTaskData();
+            });
+            updateAnim.setDuration(config.duration);
+            builder.play(updateAnim);
+            mRecentsView.updateEmptyMessage();
+        }
+    }
+
+    @Override
+    Interpolator getScaleAndTransYInterpolator(@NonNull LauncherState toState,
+            @NonNull AnimatorSetBuilder builder) {
+        if (mLauncher.getStateManager().getState() == OVERVIEW && toState == FAST_OVERVIEW) {
+            return Interpolators.clampToProgress(QUICK_SCRUB_START_INTERPOLATOR, 0,
+                    QUICK_SCRUB_TRANSLATION_Y_FACTOR);
+        }
+        return super.getScaleAndTransYInterpolator(toState, builder);
+    }
+
+    @Override
+    FloatProperty<LauncherRecentsView> getTranslationYFactorProperty() {
+        return TRANSLATION_Y_FACTOR;
+    }
+
+    @Override
+    FloatProperty<RecentsView> getContentAlphaProperty() {
+        return CONTENT_ALPHA;
+    }
+}
diff --git a/quickstep/res/drawable/hourglass_bottom.xml b/quickstep/res/drawable/hourglass_bottom.xml
new file mode 100644
index 0000000..b5ef008
--- /dev/null
+++ b/quickstep/res/drawable/hourglass_bottom.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <group>
+        <clip-path android:pathData="M0,0H24V24H0Z M 0,0"/>
+        <path
+            android:fillColor="#FFFFFFFF"
+            android:pathData="M6,2V8H6l4,4L6,16H6v6H18V16h0l-4,-4,4,-4h0V2Zm6,9.5,-4,-4V4h8V7.5Z"/>
+    </group>
+</vector>
diff --git a/quickstep/res/drawable/hourglass_top.xml b/quickstep/res/drawable/hourglass_top.xml
new file mode 100644
index 0000000..7fc77d3
--- /dev/null
+++ b/quickstep/res/drawable/hourglass_top.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <group>
+        <clip-path android:pathData="M0,0H24V24H0Z M 0,0"/>
+        <path
+            android:fillColor="#FFFFFFFF"
+            android:pathData="M6,2V8H6l4,4L6,16H6v6H18V16h0l-4,-4,4,-4h0V2ZM16,16.5V20H8V16.5l4,-4Z"/>
+    </group>
+</vector>
diff --git a/quickstep/res/layout/overview_clear_all_button.xml b/quickstep/res/layout/overview_clear_all_button.xml
index ea7a494..fc06ba0 100644
--- a/quickstep/res/layout/overview_clear_all_button.xml
+++ b/quickstep/res/layout/overview_clear_all_button.xml
@@ -17,6 +17,7 @@
 <com.android.quickstep.views.ClearAllButton
     xmlns:android="http://schemas.android.com/apk/res/android"
     style="@android:style/Widget.DeviceDefault.Button.Borderless"
+    android:id="@+id/clear_all"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:text="@string/recents_clear_all"
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 4cfefb8..f96a66f 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -40,12 +40,24 @@
         android:layout_width="match_parent"
         android:layout_height="48dp"
         android:importantForAccessibility="noHideDescendants"
-        android:fontFamily="sans-serif"
-        android:textSize="14sp"
         android:background="@drawable/bg_wellbeing_toast"
         android:layout_gravity="bottom"
         android:gravity="center"
-        android:textColor="@android:color/white"
-        android:visibility="gone"
+        android:visibility="gone">
+        <ImageView
+            android:id="@+id/digital_well_being_hourglass"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:layout_marginEnd="8dp"
         />
+        <TextView
+            android:id="@+id/digital_well_being_remaining_time"
+            android:layout_width="wrap_content"
+            android:layout_height="24dp"
+            android:fontFamily="sans-serif"
+            android:textSize="14sp"
+            android:textColor="@android:color/white"
+            android:gravity="center_vertical"
+        />
+    </com.android.quickstep.views.DigitalWellBeingToast>
 </com.android.quickstep.views.TaskView>
\ No newline at end of file
diff --git a/quickstep/res/values-af/strings.xml b/quickstep/res/values-af/strings.xml
index 5ce3c0d..6f02728 100644
--- a/quickstep/res/values-af/strings.xml
+++ b/quickstep/res/values-af/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Programgebruikinstellings"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Vee alles uit"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Onlangse programme"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-am/strings.xml b/quickstep/res/values-am/strings.xml
index 99dc137..03dec1b 100644
--- a/quickstep/res/values-am/strings.xml
+++ b/quickstep/res/values-am/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"የመተግበሪያ አጠቃቀም ቅንብሮች"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"ሁሉንም አጽዳ"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"የቅርብ ጊዜ መተግበሪያዎች"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ar/strings.xml b/quickstep/res/values-ar/strings.xml
index 808082e..268e9bd 100644
--- a/quickstep/res/values-ar/strings.xml
+++ b/quickstep/res/values-ar/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"إعدادات استخدام التطبيق"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"محو الكل"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"التطبيقات التي تمّ استخدامها مؤخرًا"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-as/strings.xml b/quickstep/res/values-as/strings.xml
index eac2878..89ca0f7 100644
--- a/quickstep/res/values-as/strings.xml
+++ b/quickstep/res/values-as/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"এপে ব্যৱহাৰ কৰা ডেটাৰ ছেটিংসমূহ"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"সকলো মচক"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"শেহতীয়া এপসমূহ"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-az/strings.xml b/quickstep/res/values-az/strings.xml
index d707108..4353b75 100644
--- a/quickstep/res/values-az/strings.xml
+++ b/quickstep/res/values-az/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Tətbiq istifadə ayarları"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Hamısını silin"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Son tətbiqlər"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-b+sr+Latn/strings.xml b/quickstep/res/values-b+sr+Latn/strings.xml
index fd449b8..5a80ebd 100644
--- a/quickstep/res/values-b+sr+Latn/strings.xml
+++ b/quickstep/res/values-b+sr+Latn/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Podešavanja korišćenja aplikacije"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Obriši sve"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Nedavne aplikacije"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-be/strings.xml b/quickstep/res/values-be/strings.xml
index fa1f28e..e2a1940 100644
--- a/quickstep/res/values-be/strings.xml
+++ b/quickstep/res/values-be/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Налады выкарыстання праграмы"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Ачысціць усё"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Нядаўнія праграмы"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-bg/strings.xml b/quickstep/res/values-bg/strings.xml
index 0f817ac..5e23876 100644
--- a/quickstep/res/values-bg/strings.xml
+++ b/quickstep/res/values-bg/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Настройки за използването на приложенията"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Изчистване на всички"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Скорошни приложения"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-bn/strings.xml b/quickstep/res/values-bn/strings.xml
index c7342f3..9490692 100644
--- a/quickstep/res/values-bn/strings.xml
+++ b/quickstep/res/values-bn/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"অ্যাপ ব্যবহারের সেটিংস"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"সবকিছু খালি করুন"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"সম্প্রতি ব্যবহৃত অ্যাপ"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-bs/strings.xml b/quickstep/res/values-bs/strings.xml
index d0d0aa6..3af9436 100644
--- a/quickstep/res/values-bs/strings.xml
+++ b/quickstep/res/values-bs/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Postavke korištenja aplikacije"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Obriši sve"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Nedavne aplikacije"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ca/strings.xml b/quickstep/res/values-ca/strings.xml
index 5bdd67d..728c0ea 100644
--- a/quickstep/res/values-ca/strings.xml
+++ b/quickstep/res/values-ca/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Configuració d\'ús d\'aplicacions"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Esborra-ho tot"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Aplicacions recents"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-cs/strings.xml b/quickstep/res/values-cs/strings.xml
index 5fe7088..a9a1ba2 100644
--- a/quickstep/res/values-cs/strings.xml
+++ b/quickstep/res/values-cs/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Nastavení využití aplikací"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Vymazat vše"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Poslední aplikace"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-da/strings.xml b/quickstep/res/values-da/strings.xml
index 4c3ec59..d10fd63 100644
--- a/quickstep/res/values-da/strings.xml
+++ b/quickstep/res/values-da/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Indstillinger for appforbrug"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Ryd alt"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Seneste apps"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-de/strings.xml b/quickstep/res/values-de/strings.xml
index fb428f7..0a65bee 100644
--- a/quickstep/res/values-de/strings.xml
+++ b/quickstep/res/values-de/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Einstellungen zur App-Nutzung"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Alle Apps schließen"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Zuletzt aktive Apps"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-el/strings.xml b/quickstep/res/values-el/strings.xml
index 54d4677..edb6381 100644
--- a/quickstep/res/values-el/strings.xml
+++ b/quickstep/res/values-el/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Ρυθμίσεις χρήσης εφαρμογής"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Διαγραφή όλων"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Πρόσφατες εφαρμογές"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-en-rAU/strings.xml b/quickstep/res/values-en-rAU/strings.xml
index df2fee8..de9a648 100644
--- a/quickstep/res/values-en-rAU/strings.xml
+++ b/quickstep/res/values-en-rAU/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Clear all"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Recent apps"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-en-rGB/strings.xml b/quickstep/res/values-en-rGB/strings.xml
index df2fee8..de9a648 100644
--- a/quickstep/res/values-en-rGB/strings.xml
+++ b/quickstep/res/values-en-rGB/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Clear all"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Recent apps"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-en-rIN/strings.xml b/quickstep/res/values-en-rIN/strings.xml
index df2fee8..de9a648 100644
--- a/quickstep/res/values-en-rIN/strings.xml
+++ b/quickstep/res/values-en-rIN/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Clear all"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Recent apps"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-es-rUS/strings.xml b/quickstep/res/values-es-rUS/strings.xml
index 9d98d6b..e9abdb5 100644
--- a/quickstep/res/values-es-rUS/strings.xml
+++ b/quickstep/res/values-es-rUS/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Configuración de uso de la app"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Borrar todo"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Apps recientes"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-es/strings.xml b/quickstep/res/values-es/strings.xml
index b39262b..8d05c1a 100644
--- a/quickstep/res/values-es/strings.xml
+++ b/quickstep/res/values-es/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Ajustes de uso de la aplicación"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Borrar todo"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Aplicaciones recientes"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-et/strings.xml b/quickstep/res/values-et/strings.xml
index 9b859fd..54b4af8 100644
--- a/quickstep/res/values-et/strings.xml
+++ b/quickstep/res/values-et/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Rakenduse kasutuse seaded"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Sule kõik"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Hiljutised rakendused"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-eu/strings.xml b/quickstep/res/values-eu/strings.xml
index 096a849..3dffe0f 100644
--- a/quickstep/res/values-eu/strings.xml
+++ b/quickstep/res/values-eu/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Aplikazioen erabileraren ezarpenak"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Garbitu guztiak"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Azken aplikazioak"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-fa/strings.xml b/quickstep/res/values-fa/strings.xml
index 3ae1bd8..67ebbc7 100644
--- a/quickstep/res/values-fa/strings.xml
+++ b/quickstep/res/values-fa/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"تنظیمات استفاده از برنامه"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"پاک کردن همه"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"برنامه‌های اخیر"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-fi/strings.xml b/quickstep/res/values-fi/strings.xml
index 79c2d6b..31ac3ca 100644
--- a/quickstep/res/values-fi/strings.xml
+++ b/quickstep/res/values-fi/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Sovelluksen käyttöasetukset"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Poista kaikki"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Viimeisimmät sovellukset"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-fr-rCA/strings.xml b/quickstep/res/values-fr-rCA/strings.xml
index e64985e..d090d10 100644
--- a/quickstep/res/values-fr-rCA/strings.xml
+++ b/quickstep/res/values-fr-rCA/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Paramètres d\'utilisation de l\'application"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Tout effacer"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Applications récentes"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-fr/strings.xml b/quickstep/res/values-fr/strings.xml
index 03b2f68..7659ed2 100644
--- a/quickstep/res/values-fr/strings.xml
+++ b/quickstep/res/values-fr/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Paramètres de consommation de l\'application"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Tout effacer"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Applications récentes"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-gl/strings.xml b/quickstep/res/values-gl/strings.xml
index 2fefd82..1358d7f 100644
--- a/quickstep/res/values-gl/strings.xml
+++ b/quickstep/res/values-gl/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Configuración do uso de aplicacións"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Borrar todo"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Aplicacións recentes"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-gu/strings.xml b/quickstep/res/values-gu/strings.xml
index 2e5fdde..746ec1a 100644
--- a/quickstep/res/values-gu/strings.xml
+++ b/quickstep/res/values-gu/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ઍપ વપરાશનું સેટિંગ"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"બધું સાફ કરો"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"તાજેતરની ઍપ"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-hi/strings.xml b/quickstep/res/values-hi/strings.xml
index a1107a2..55d8ccc 100644
--- a/quickstep/res/values-hi/strings.xml
+++ b/quickstep/res/values-hi/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ऐप्लिकेशन इस्तेमाल की सेटिंग"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"सभी ऐप्लिकेशन बंद करें"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"हाल ही में इस्तेमाल किए गए एेप्लिकेशन"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-hr/strings.xml b/quickstep/res/values-hr/strings.xml
index f7e0caa..213897c 100644
--- a/quickstep/res/values-hr/strings.xml
+++ b/quickstep/res/values-hr/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Postavke upotrebe aplikacija"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Izbriši sve"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Nedavne aplikacije"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-hu/strings.xml b/quickstep/res/values-hu/strings.xml
index c2f042c..40bea2a 100644
--- a/quickstep/res/values-hu/strings.xml
+++ b/quickstep/res/values-hu/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Alkalmazáshasználati beállítások"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Összes törlése"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Legutóbbi alkalmazások"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-hy/strings.xml b/quickstep/res/values-hy/strings.xml
index b8a12c9..7780fa8 100644
--- a/quickstep/res/values-hy/strings.xml
+++ b/quickstep/res/values-hy/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Հավելվածի օգտագործման կարգավորումներ"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Փակել բոլորը"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Վերջին օգտագործած հավելվածները"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-in/strings.xml b/quickstep/res/values-in/strings.xml
index 276c144..5bdb830 100644
--- a/quickstep/res/values-in/strings.xml
+++ b/quickstep/res/values-in/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Setelan penggunaan aplikasi"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Hapus semua"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Aplikasi baru-baru ini"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-is/strings.xml b/quickstep/res/values-is/strings.xml
index b4fb172..7c3bbd6 100644
--- a/quickstep/res/values-is/strings.xml
+++ b/quickstep/res/values-is/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Notkunarstillingar forrits"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Hreinsa allt"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Nýleg forrit"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-it/strings.xml b/quickstep/res/values-it/strings.xml
index c44cc01..314e768 100644
--- a/quickstep/res/values-it/strings.xml
+++ b/quickstep/res/values-it/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Impostazioni di utilizzo delle app"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Cancella tutto"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"App recenti"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-iw/strings.xml b/quickstep/res/values-iw/strings.xml
index f7a0384..4b6a045 100644
--- a/quickstep/res/values-iw/strings.xml
+++ b/quickstep/res/values-iw/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"הגדרות שימוש באפליקציה"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"ניקוי הכול"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"אפליקציות אחרונות"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ja/strings.xml b/quickstep/res/values-ja/strings.xml
index fff8a0d..626cb9a 100644
--- a/quickstep/res/values-ja/strings.xml
+++ b/quickstep/res/values-ja/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"アプリの使用状況の設定"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"すべてクリア"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"最近使ったアプリ"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ka/strings.xml b/quickstep/res/values-ka/strings.xml
index cc0d594..a61d670 100644
--- a/quickstep/res/values-ka/strings.xml
+++ b/quickstep/res/values-ka/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"აპების გამოყენების პარამეტრები"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"ყველას გასუფთავება"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"ბოლოდროინდელი აპები"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-kk/strings.xml b/quickstep/res/values-kk/strings.xml
index b0a1b4e..d3ada26 100644
--- a/quickstep/res/values-kk/strings.xml
+++ b/quickstep/res/values-kk/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Қолданбаны пайдалану параметрлері"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Барлығын өшіру"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Соңғы пайдаланылған қолданбалар"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-km/strings.xml b/quickstep/res/values-km/strings.xml
index 7aa407c..d4d6bfc 100644
--- a/quickstep/res/values-km/strings.xml
+++ b/quickstep/res/values-km/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ការកំណត់​ការប្រើប្រាស់​កម្មវិធី"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"សម្អាត​ទាំងអស់"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"កម្មវិធី​ថ្មីៗ"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-kn/strings.xml b/quickstep/res/values-kn/strings.xml
index e5ac0df..bfd53ba 100644
--- a/quickstep/res/values-kn/strings.xml
+++ b/quickstep/res/values-kn/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ಆ್ಯಪ್‌ ಬಳಕೆಯ ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"ಎಲ್ಲವನ್ನೂ ತೆರವುಗೊಳಿಸಿ"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"ಇತ್ತೀಚಿನ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ko/strings.xml b/quickstep/res/values-ko/strings.xml
index 8e7dcb1..1f49b01 100644
--- a/quickstep/res/values-ko/strings.xml
+++ b/quickstep/res/values-ko/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"앱 사용 설정"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"모두 삭제"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"최근 앱"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ky/strings.xml b/quickstep/res/values-ky/strings.xml
index 13d8399..25a9c3c 100644
--- a/quickstep/res/values-ky/strings.xml
+++ b/quickstep/res/values-ky/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Колдонмону пайдалануу жөндөөлөрү"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Баарын тазалоо"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Акыркы колдонмолор"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-lo/strings.xml b/quickstep/res/values-lo/strings.xml
index 622118c..26d53c9 100644
--- a/quickstep/res/values-lo/strings.xml
+++ b/quickstep/res/values-lo/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ການຕັ້ງຄ່າການນຳໃຊ້ແອັບ"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"ລຶບລ້າງທັງໝົດ"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"ແອັບຫຼ້າສຸດ"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-lt/strings.xml b/quickstep/res/values-lt/strings.xml
index 42e4fe8..49635c8 100644
--- a/quickstep/res/values-lt/strings.xml
+++ b/quickstep/res/values-lt/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Programos naudojimo nustatymai"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Išvalyti viską"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Naujausios programos"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-lv/strings.xml b/quickstep/res/values-lv/strings.xml
index c249928..a25b55c 100644
--- a/quickstep/res/values-lv/strings.xml
+++ b/quickstep/res/values-lv/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Lietotņu izmantošanas iestatījumi"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Notīrīt visu"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Pēdējās izmantotās lietotnes"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-mk/strings.xml b/quickstep/res/values-mk/strings.xml
index fa4e401..24e1d13 100644
--- a/quickstep/res/values-mk/strings.xml
+++ b/quickstep/res/values-mk/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Поставки за користење на апликациите"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Исчисти ги сите"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Неодамнешни апликации"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ml/strings.xml b/quickstep/res/values-ml/strings.xml
index 4be84c8..d76ef41 100644
--- a/quickstep/res/values-ml/strings.xml
+++ b/quickstep/res/values-ml/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ആപ്പ് ഉപയോഗ ക്രമീകരണം"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"എല്ലാം മായ്‌ക്കുക"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"സമീപകാല ആപ്പുകൾ"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-mn/strings.xml b/quickstep/res/values-mn/strings.xml
index b3e3d7f..53ff7bb 100644
--- a/quickstep/res/values-mn/strings.xml
+++ b/quickstep/res/values-mn/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Апп ашиглалтын тохиргоо"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Бүгдийг устгах"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Саяхны аппууд"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-mr/strings.xml b/quickstep/res/values-mr/strings.xml
index fa8574a..8e18643 100644
--- a/quickstep/res/values-mr/strings.xml
+++ b/quickstep/res/values-mr/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"अ‍ॅप वापर सेटिंग्ज"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"सर्व साफ करा"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"अलीकडील अॅप्स"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ms/strings.xml b/quickstep/res/values-ms/strings.xml
index 975e127..e390820 100644
--- a/quickstep/res/values-ms/strings.xml
+++ b/quickstep/res/values-ms/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Tetapan penggunaan apl"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Kosongkan semua"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Apl terbaharu"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-my/strings.xml b/quickstep/res/values-my/strings.xml
index 52d4dae..45a052f 100644
--- a/quickstep/res/values-my/strings.xml
+++ b/quickstep/res/values-my/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"အက်ပ်အသုံးပြုမှု ဆက်တင်များ"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"အားလုံးကို ရှင်းရန်"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"လတ်တလောသုံး အက်ပ်များ"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-nb/strings.xml b/quickstep/res/values-nb/strings.xml
index c681a1e..08b5093 100644
--- a/quickstep/res/values-nb/strings.xml
+++ b/quickstep/res/values-nb/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Innstillinger for appbruk"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Fjern alt"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Nylige apper"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ne/strings.xml b/quickstep/res/values-ne/strings.xml
index 46e0eaf..25da447 100644
--- a/quickstep/res/values-ne/strings.xml
+++ b/quickstep/res/values-ne/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"अनुप्रयोगको उपयोगका सेटिङहरू"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"सबै खाली गर्नुहोस्"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"हालसालैका अनुप्रयोगहरू"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-nl/strings.xml b/quickstep/res/values-nl/strings.xml
index dbbd2ec..2dbd58b 100644
--- a/quickstep/res/values-nl/strings.xml
+++ b/quickstep/res/values-nl/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Instellingen voor app-gebruik"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Alles wissen"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Recente apps"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-or/strings.xml b/quickstep/res/values-or/strings.xml
index 60f7823..a15196f 100644
--- a/quickstep/res/values-or/strings.xml
+++ b/quickstep/res/values-or/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ଆପ୍‍ ବ୍ୟବହାର ସେଟିଂସ୍‍"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"ସବୁ ଖାଲି କରନ୍ତୁ"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"ସାମ୍ପ୍ରତିକ ଆପ୍‌"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-pa/strings.xml b/quickstep/res/values-pa/strings.xml
index 18b0468..e9d7300 100644
--- a/quickstep/res/values-pa/strings.xml
+++ b/quickstep/res/values-pa/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ਐਪ ਵਰਤੋਂ ਦੀਆਂ ਸੈਟਿੰਗਾਂ"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"ਸਭ ਕਲੀਅਰ ਕਰੋ"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"ਹਾਲੀਆ ਐਪਾਂ"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-pl/strings.xml b/quickstep/res/values-pl/strings.xml
index 447cd60..b1c3f25 100644
--- a/quickstep/res/values-pl/strings.xml
+++ b/quickstep/res/values-pl/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Ustawienia użycia aplikacji"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Wyczyść wszystko"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Ostatnie aplikacje"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-pt-rPT/strings.xml b/quickstep/res/values-pt-rPT/strings.xml
index e7dfedf..153c34a 100644
--- a/quickstep/res/values-pt-rPT/strings.xml
+++ b/quickstep/res/values-pt-rPT/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Definições de utilização de aplicações"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Limpar tudo"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Aplicações recentes"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-pt/strings.xml b/quickstep/res/values-pt/strings.xml
index 5ebd0bd..24e56c2 100644
--- a/quickstep/res/values-pt/strings.xml
+++ b/quickstep/res/values-pt/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Configurações de uso do app"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Limpar tudo"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Apps recentes"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ro/strings.xml b/quickstep/res/values-ro/strings.xml
index 5d9887d..8e697eb 100644
--- a/quickstep/res/values-ro/strings.xml
+++ b/quickstep/res/values-ro/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Setări de utilizare a aplicației"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Ștergeți tot"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Aplicații recente"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ru/strings.xml b/quickstep/res/values-ru/strings.xml
index 992683c..0dc5ba1 100644
--- a/quickstep/res/values-ru/strings.xml
+++ b/quickstep/res/values-ru/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Настройки использования приложения"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Очистить все"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Недавние приложения"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-si/strings.xml b/quickstep/res/values-si/strings.xml
index 050a765..614ca2b 100644
--- a/quickstep/res/values-si/strings.xml
+++ b/quickstep/res/values-si/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"යෙදුම් භාවිත සැකසීම්"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"සියල්ල හිස් කරන්න"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"මෑත යෙදුම්"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-sk/strings.xml b/quickstep/res/values-sk/strings.xml
index 510220b..2766c0c 100644
--- a/quickstep/res/values-sk/strings.xml
+++ b/quickstep/res/values-sk/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Nastavenia využívania aplikácie"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Vymazať všetko"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Nedávne aplikácie"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-sl/strings.xml b/quickstep/res/values-sl/strings.xml
index 29d1eec..115bcf8 100644
--- a/quickstep/res/values-sl/strings.xml
+++ b/quickstep/res/values-sl/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Nastavitve uporabe aplikacij"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Počisti vse"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Nedavne aplikacije"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-sq/strings.xml b/quickstep/res/values-sq/strings.xml
index f2c955c..7107dcc 100644
--- a/quickstep/res/values-sq/strings.xml
+++ b/quickstep/res/values-sq/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Cilësimet e përdorimit të aplikacionit"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Pastroji të gjitha"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Aplikacionet e fundit"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-sr/strings.xml b/quickstep/res/values-sr/strings.xml
index e3837e7..3f87bab 100644
--- a/quickstep/res/values-sr/strings.xml
+++ b/quickstep/res/values-sr/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Подешавања коришћења апликације"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Обриши све"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Недавне апликације"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-sv/strings.xml b/quickstep/res/values-sv/strings.xml
index 4eda58c..cff4362 100644
--- a/quickstep/res/values-sv/strings.xml
+++ b/quickstep/res/values-sv/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Inställningar för appanvändning"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Rensa alla"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Senaste apparna"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-sw/strings.xml b/quickstep/res/values-sw/strings.xml
index 8888fef..eebd64b 100644
--- a/quickstep/res/values-sw/strings.xml
+++ b/quickstep/res/values-sw/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Mipangilio ya matumizi ya programu"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Ondoa zote"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Programu za hivi karibuni"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ta/strings.xml b/quickstep/res/values-ta/strings.xml
index 5187564..d8e0177 100644
--- a/quickstep/res/values-ta/strings.xml
+++ b/quickstep/res/values-ta/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ஆப்ஸ் உபயோக அமைப்புகள்"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"எல்லாம் அழி"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"சமீபத்திய ஆப்ஸ்"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-te/strings.xml b/quickstep/res/values-te/strings.xml
index ba9a558..77c3a40 100644
--- a/quickstep/res/values-te/strings.xml
+++ b/quickstep/res/values-te/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"యాప్ వినియోగ సెట్టింగ్‌లు"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"అన్నీ తీసివేయండి"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"ఇటీవలి యాప్‌లు"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-th/strings.xml b/quickstep/res/values-th/strings.xml
index c3a9a81..2d49b8a 100644
--- a/quickstep/res/values-th/strings.xml
+++ b/quickstep/res/values-th/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"การตั้งค่าการใช้แอป"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"ล้างทั้งหมด"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"แอปล่าสุด"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-tl/strings.xml b/quickstep/res/values-tl/strings.xml
index 6ceaf21..da5eb8b 100644
--- a/quickstep/res/values-tl/strings.xml
+++ b/quickstep/res/values-tl/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Mga setting ng paggamit ng app"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"I-clear lahat"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Mga kamakailang app"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-tr/strings.xml b/quickstep/res/values-tr/strings.xml
index cbebb26..a3c665a 100644
--- a/quickstep/res/values-tr/strings.xml
+++ b/quickstep/res/values-tr/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Uygulama kullanım ayarları"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Tümünü temizle"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Son uygulamalar"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-uk/strings.xml b/quickstep/res/values-uk/strings.xml
index b9666b2..956a16f 100644
--- a/quickstep/res/values-uk/strings.xml
+++ b/quickstep/res/values-uk/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Налаштування використання додатка"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Очистити все"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Нещодавні додатки"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ur/strings.xml b/quickstep/res/values-ur/strings.xml
index 69ff632..061aade 100644
--- a/quickstep/res/values-ur/strings.xml
+++ b/quickstep/res/values-ur/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ایپ کے استعمال کی ترتیبات"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"سبھی کو صاف کریں"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"حالیہ ایپس"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-uz/strings.xml b/quickstep/res/values-uz/strings.xml
index 9edb9a5..4c6e83f 100644
--- a/quickstep/res/values-uz/strings.xml
+++ b/quickstep/res/values-uz/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Ilovadan foydalanish sozlamalari"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Hammasini tozalash"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Yaqinda ishlatilgan ilovalar"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-vi/strings.xml b/quickstep/res/values-vi/strings.xml
index 649e44d..89d718e 100644
--- a/quickstep/res/values-vi/strings.xml
+++ b/quickstep/res/values-vi/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Cài đặt mức sử dụng ứng dụng"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Xóa tất cả"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Ứng dụng gần đây"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-zh-rCN/strings.xml b/quickstep/res/values-zh-rCN/strings.xml
index b27931c..48af192 100644
--- a/quickstep/res/values-zh-rCN/strings.xml
+++ b/quickstep/res/values-zh-rCN/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"应用使用设置"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"全部清除"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"最近用过的应用"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-zh-rHK/strings.xml b/quickstep/res/values-zh-rHK/strings.xml
index f8fe8de..80f648b 100644
--- a/quickstep/res/values-zh-rHK/strings.xml
+++ b/quickstep/res/values-zh-rHK/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"應用程式使用情況設定"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"全部清除"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"最近使用的應用程式"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-zh-rTW/strings.xml b/quickstep/res/values-zh-rTW/strings.xml
index ba51932..5e66514 100644
--- a/quickstep/res/values-zh-rTW/strings.xml
+++ b/quickstep/res/values-zh-rTW/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"應用程式使用情況設定"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"全部清除"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"最近使用的應用程式"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-zu/strings.xml b/quickstep/res/values-zu/strings.xml
index d04a026..5753e55 100644
--- a/quickstep/res/values-zu/strings.xml
+++ b/quickstep/res/values-zu/strings.xml
@@ -28,4 +28,12 @@
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Izilungiselelo zokusetshenziswa kohlelo lokusebenza"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Sula konke"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Izinhlelo zokusebenza zakamuva"</string>
+    <!-- no translation found for task_contents_description_with_remaining_time (4479688746574672685) -->
+    <skip />
+    <!-- no translation found for shorter_duration_less_than_one_minute (4722015666335015336) -->
+    <skip />
+    <!-- no translation found for app_in_grayscale (1108706002158384887) -->
+    <skip />
+    <!-- no translation found for time_left_for_app (3111996412933644358) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index e0c4e4b..04fd59c 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -20,6 +20,8 @@
     <dimen name="task_thumbnail_half_top_margin">12dp</dimen>
     <dimen name="task_thumbnail_icon_size">48dp</dimen>
     <dimen name="task_corner_radius">8dp</dimen>
+    <!-- For screens without rounded corners -->
+    <dimen name="task_corner_radius_small">2dp</dimen>
     <dimen name="recents_page_spacing">10dp</dimen>
     <dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
     <dimen name="quickscrub_adjacent_visible_width">20dp</dimen>
@@ -28,9 +30,16 @@
              loading full resolution screenshots. -->
     <dimen name="recents_fast_fling_velocity">600dp</dimen>
 
+    <!-- These velocities are in dp / s -->
     <dimen name="quickstep_fling_threshold_velocity">500dp</dimen>
     <dimen name="quickstep_fling_min_velocity">250dp</dimen>
 
+    <!-- These speeds are in dp / ms -->
+    <dimen name="motion_pause_detector_speed_very_slow">0.0285dp</dimen>
+    <dimen name="motion_pause_detector_speed_somewhat_fast">0.285dp</dimen>
+    <dimen name="motion_pause_detector_speed_fast">0.5dp</dimen>
+    <dimen name="motion_pause_detector_min_displacement">48dp</dimen>
+
     <!-- Launcher app transition -->
     <dimen name="content_trans_y">50dp</dimen>
     <dimen name="workspace_trans_y">50dp</dimen>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 7c47956..0c741a1 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -62,8 +62,4 @@
     <!-- Annotation shown on an app card in Recents, telling that the app has a usage limit set by
     the user, and a given time is left for it today [CHAR LIMIT=20] -->
     <string name="time_left_for_app"><xliff:g id="time" example="7 minutes">%1$s</xliff:g> left today</string>
-
-    <!-- Annotation shown on an app card in Recents, telling that the app is in a group that has a
-    usage limit set by the user, and a given time is left for the group today [CHAR LIMIT=20] -->
-    <string name="time_left_for_group"><xliff:g id="time" example="1 hour">%1$s</xliff:g> left for group</string>
 </resources>
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java
index 08b6bfc..c5c5323 100644
--- a/quickstep/src/com/android/launcher3/LauncherInitListener.java
+++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java
@@ -44,8 +44,8 @@
     @Override
     protected boolean init(Launcher launcher, boolean alreadyOnHome) {
         if (mRemoteAnimationProvider != null) {
-            LauncherAppTransitionManagerImpl appTransitionManager =
-                    (LauncherAppTransitionManagerImpl) launcher.getAppTransitionManager();
+            QuickstepAppTransitionManagerImpl appTransitionManager =
+                    (QuickstepAppTransitionManagerImpl) launcher.getAppTransitionManager();
 
             // Set a one-time animation provider. After the first call, this will get cleared.
             // TODO: Probably also check the intended target id.
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
similarity index 83%
rename from quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
rename to quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index cb5152a..4930dc5 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -22,7 +22,6 @@
 import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
@@ -30,8 +29,6 @@
 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS;
-import static com.android.quickstep.TaskUtils.findTaskViewToLaunch;
-import static com.android.quickstep.TaskUtils.getRecentsWindowAnimator;
 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
@@ -60,7 +57,6 @@
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.InsettableFrameLayout.LayoutParams;
 import com.android.launcher3.allapps.AllAppsTransitionController;
-import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.graphics.DrawableFactory;
@@ -68,12 +64,9 @@
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.quickstep.RecentsModel;
-import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.quickstep.util.MultiValueUpdateListener;
 import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
-import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.system.ActivityCompat;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
@@ -84,15 +77,19 @@
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 /**
- * Manages the opening and closing app transitions from Launcher.
+ * {@link LauncherAppTransitionManager} with Quickstep-specific app transitions for launching from
+ * home and/or all-apps.
  */
 @TargetApi(Build.VERSION_CODES.O)
 @SuppressWarnings("unused")
-public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManager
+public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTransitionManager
         implements OnDeviceProfileChangeListener {
 
-    private static final String TAG = "LauncherTransition";
+    private static final String TAG = "QuickstepTransition";
 
     /** Duration of status bar animations. */
     public static final int STATUS_BAR_TRANSITION_DURATION = 120;
@@ -119,10 +116,14 @@
     private static final int LAUNCHER_RESUME_START_DELAY = 100;
     private static final int CLOSING_TRANSITION_DURATION_MS = 250;
 
+    protected static final int CONTENT_ALPHA_DURATION = 217;
+    protected static final int CONTENT_TRANSLATION_DURATION = 350;
+
     // Progress = 0: All apps is fully pulled up, Progress = 1: All apps is fully pulled down.
     public static final float ALL_APPS_PROGRESS_OFF_SCREEN = 1.3059858f;
 
-    private final Launcher mLauncher;
+    protected final Launcher mLauncher;
+
     private final DragLayer mDragLayer;
     private final AlphaProperty mDragLayerAlpha;
 
@@ -150,7 +151,7 @@
         }
     };
 
-    public LauncherAppTransitionManagerImpl(Context context) {
+    public QuickstepAppTransitionManagerImpl(Context context) {
         mLauncher = Launcher.getLauncher(context);
         mDragLayer = mLauncher.getDragLayer();
         mDragLayerAlpha = mDragLayer.getAlphaProperty(ALPHA_INDEX_TRANSITIONS);
@@ -179,12 +180,9 @@
     @Override
     public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) {
         if (hasControlRemoteAppTransitionPermission()) {
-            boolean fromRecents = mLauncher.getStateManager().getState().overviewUi
-                    && findTaskViewToLaunch(launcher, v, null) != null;
-            RecentsView recentsView = mLauncher.getOverviewPanel();
-            if (fromRecents && recentsView.getQuickScrubController().isQuickSwitch()) {
-                return ActivityOptions.makeCustomAnimation(mLauncher, R.anim.no_anim,
-                        R.anim.no_anim);
+            boolean fromRecents = isLaunchingFromRecents(v, null /* targets */);
+            if (fromRecents && isQuickSwitchInProgress()) {
+                return getQuickSwitchActivityOptions();
             }
 
             RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mHandler,
@@ -198,34 +196,10 @@
                     boolean launcherClosing =
                             launcherIsATargetWithMode(targetCompats, MODE_CLOSING);
 
-                    if (!composeRecentsLaunchAnimator(v, targetCompats, anim)) {
-                        // Set the state animation first so that any state listeners are called
-                        // before our internal listeners.
-                        mLauncher.getStateManager().setCurrentAnimation(anim);
-
-                        Rect windowTargetBounds = getWindowTargetBounds(targetCompats);
-                        boolean isAllOpeningTargetTrs = true;
-                        for (int i = 0; i < targetCompats.length; i++) {
-                            RemoteAnimationTargetCompat target = targetCompats[i];
-                            if (target.mode == MODE_OPENING) {
-                                isAllOpeningTargetTrs &= target.isTranslucent;
-                            }
-                            if (!isAllOpeningTargetTrs) break;
-                        }
-                        playIconAnimators(anim, v, windowTargetBounds, !isAllOpeningTargetTrs);
-                        if (launcherClosing) {
-                            Pair<AnimatorSet, Runnable> launcherContentAnimator =
-                                    getLauncherContentAnimator(true /* isAppOpening */,
-                                            new float[] {0, mContentTransY});
-                            anim.play(launcherContentAnimator.first);
-                            anim.addListener(new AnimatorListenerAdapter() {
-                                @Override
-                                public void onAnimationEnd(Animator animation) {
-                                    launcherContentAnimator.second.run();
-                                }
-                            });
-                        }
-                        anim.play(getOpeningWindowAnimators(v, targetCompats, windowTargetBounds));
+                    if (isLaunchingFromRecents(v, targetCompats)) {
+                        composeRecentsLaunchAnimator(anim, v, targetCompats, launcherClosing);
+                    } else {
+                        composeIconLaunchAnimator(anim, v, targetCompats, launcherClosing);
                     }
 
                     if (launcherClosing) {
@@ -236,6 +210,8 @@
                 }
             };
 
+            // Note that this duration is a guess as we do not know if the animation will be a
+            // recents launch or not for sure until we know the opening app targets.
             int duration = fromRecents
                     ? RECENTS_LAUNCH_DURATION
                     : APP_LAUNCH_DURATION;
@@ -249,13 +225,90 @@
     }
 
     /**
+     * Whether the launch is a recents app transition and we should do a launch animation
+     * from the recents view. Note that if the remote animation targets are not provided, this
+     * may not always be correct as we may resolve the opening app to a task when the animation
+     * starts.
+     *
+     * @param v the view to launch from
+     * @param targets apps that are opening/closing
+     * @return true if the app is launching from recents, false if it most likely is not
+     */
+    protected abstract boolean isLaunchingFromRecents(@NonNull View v,
+            @Nullable RemoteAnimationTargetCompat[] targets);
+
+    /**
+     * Whether a quick scrub is in progress.
+     *
+     * @return true if in progress
+     */
+    protected abstract boolean isQuickSwitchInProgress();
+
+    /**
+     * Get activity options for a quick switch launch that include the launch animation.
+     *
+     * @return the activity options for a quick switch recents launch
+     */
+    protected abstract ActivityOptions getQuickSwitchActivityOptions();
+
+    /**
+     * Composes the animations for a launch from the recents list.
+     *
+     * @param anim the animator set to add to
+     * @param v the launching view
+     * @param targets the apps that are opening/closing
+     * @param launcherClosing true if the launcher app is closing
+     */
+    protected abstract void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
+            @NonNull RemoteAnimationTargetCompat[] targets, boolean launcherClosing);
+
+    /**
+     * Compose the animations for a launch from the app icon.
+     *
+     * @param anim the animation to add to
+     * @param v the launching view with the icon
+     * @param targets the list of opening/closing apps
+     * @param launcherClosing true if launcher is closing
+     */
+    private void composeIconLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
+            @NonNull RemoteAnimationTargetCompat[] targets, boolean launcherClosing) {
+        // Set the state animation first so that any state listeners are called
+        // before our internal listeners.
+        mLauncher.getStateManager().setCurrentAnimation(anim);
+
+        Rect windowTargetBounds = getWindowTargetBounds(targets);
+        boolean isAllOpeningTargetTrs = true;
+        for (int i = 0; i < targets.length; i++) {
+            RemoteAnimationTargetCompat target = targets[i];
+            if (target.mode == MODE_OPENING) {
+                isAllOpeningTargetTrs &= target.isTranslucent;
+            }
+            if (!isAllOpeningTargetTrs) break;
+        }
+        playIconAnimators(anim, v, windowTargetBounds, !isAllOpeningTargetTrs);
+        if (launcherClosing) {
+            Pair<AnimatorSet, Runnable> launcherContentAnimator =
+                    getLauncherContentAnimator(true /* isAppOpening */,
+                            new float[] {0, mContentTransY});
+            anim.play(launcherContentAnimator.first);
+            anim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    launcherContentAnimator.second.run();
+                }
+            });
+        }
+        anim.play(getOpeningWindowAnimators(v, targets, windowTargetBounds));
+    }
+
+    /**
      * Return the window bounds of the opening target.
      * In multiwindow mode, we need to get the final size of the opening app window target to help
      * figure out where the floating view should animate to.
      */
     private Rect getWindowTargetBounds(RemoteAnimationTargetCompat[] targets) {
         Rect bounds = new Rect(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx);
-        if (mLauncher.isInMultiWindowModeCompat()) {
+        if (mLauncher.isInMultiWindowMode()) {
             for (RemoteAnimationTargetCompat target : targets) {
                 if (target.mode == MODE_OPENING) {
                     bounds.set(target.sourceContainerBounds);
@@ -278,75 +331,6 @@
     }
 
     /**
-     * Composes the animations for a launch from the recents list if possible.
-     */
-    private boolean composeRecentsLaunchAnimator(View v,
-            RemoteAnimationTargetCompat[] targets, AnimatorSet target) {
-        // Ensure recents is actually visible
-        if (!mLauncher.getStateManager().getState().overviewUi) {
-            return false;
-        }
-
-        RecentsView recentsView = mLauncher.getOverviewPanel();
-        boolean launcherClosing = launcherIsATargetWithMode(targets, MODE_CLOSING);
-        boolean skipLauncherChanges = !launcherClosing;
-        boolean isLaunchingFromQuickscrub =
-                recentsView.getQuickScrubController().isWaitingForTaskLaunch();
-
-        TaskView taskView = findTaskViewToLaunch(mLauncher, v, targets);
-        if (taskView == null) {
-            return false;
-        }
-
-        int duration = isLaunchingFromQuickscrub
-                ? RECENTS_QUICKSCRUB_LAUNCH_DURATION
-                : RECENTS_LAUNCH_DURATION;
-
-        ClipAnimationHelper helper = new ClipAnimationHelper(mLauncher);
-        target.play(getRecentsWindowAnimator(taskView, skipLauncherChanges, targets, helper)
-                .setDuration(duration));
-
-        Animator childStateAnimation = null;
-        // Found a visible recents task that matches the opening app, lets launch the app from there
-        Animator launcherAnim;
-        final AnimatorListenerAdapter windowAnimEndListener;
-        if (launcherClosing) {
-            launcherAnim = recentsView.createAdjacentPageAnimForTaskLaunch(taskView, helper);
-            launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
-            launcherAnim.setDuration(duration);
-
-            // Make sure recents gets fixed up by resetting task alphas and scales, etc.
-            windowAnimEndListener = new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    mLauncher.getStateManager().moveToRestState();
-                    mLauncher.getStateManager().reapplyState();
-                }
-            };
-        } else {
-            AnimatorPlaybackController controller =
-                    mLauncher.getStateManager().createAnimationToNewWorkspace(NORMAL, duration);
-            controller.dispatchOnStart();
-            childStateAnimation = controller.getTarget();
-            launcherAnim = controller.getAnimationPlayer().setDuration(duration);
-            windowAnimEndListener = new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    mLauncher.getStateManager().goToState(NORMAL, false);
-                }
-            };
-        }
-        target.play(launcherAnim);
-
-        // Set the current animation first, before adding windowAnimEndListener. Setting current
-        // animation adds some listeners which need to be called before windowAnimEndListener
-        // (the ordering of listeners matter in this case).
-        mLauncher.getStateManager().setCurrentAnimation(target, childStateAnimation);
-        target.addListener(windowAnimEndListener);
-        return true;
-    }
-
-    /**
      * Content is everything on screen except the background and the floating view (if any).
      *
      * @param isAppOpening True when this is called when an app is opening.
@@ -371,7 +355,7 @@
             appsView.setTranslationY(trans[0]);
 
             ObjectAnimator alpha = ObjectAnimator.ofFloat(appsView, View.ALPHA, alphas);
-            alpha.setDuration(217);
+            alpha.setDuration(CONTENT_ALPHA_DURATION);
             alpha.setInterpolator(LINEAR);
             appsView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
             alpha.addListener(new AnimatorListenerAdapter() {
@@ -382,7 +366,7 @@
             });
             ObjectAnimator transY = ObjectAnimator.ofFloat(appsView, View.TRANSLATION_Y, trans);
             transY.setInterpolator(AGGRESSIVE_EASE);
-            transY.setDuration(350);
+            transY.setDuration(CONTENT_TRANSLATION_DURATION);
 
             launcherAnimator.play(alpha);
             launcherAnimator.play(transY);
@@ -396,32 +380,19 @@
             AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
             launcherAnimator.play(ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS,
                     allAppsController.getProgress(), ALL_APPS_PROGRESS_OFF_SCREEN));
-
-            RecentsView overview = mLauncher.getOverviewPanel();
-            ObjectAnimator alpha = ObjectAnimator.ofFloat(overview,
-                    RecentsView.CONTENT_ALPHA, alphas);
-            alpha.setDuration(217);
-            alpha.setInterpolator(LINEAR);
-            launcherAnimator.play(alpha);
-
-            ObjectAnimator transY = ObjectAnimator.ofFloat(overview, View.TRANSLATION_Y, trans);
-            transY.setInterpolator(AGGRESSIVE_EASE);
-            transY.setDuration(350);
-            launcherAnimator.play(transY);
-
-            endListener = mLauncher.getStateManager()::reapplyState;
+            endListener = composeViewContentAnimator(launcherAnimator, alphas, trans);
         } else {
             mDragLayerAlpha.setValue(alphas[0]);
             ObjectAnimator alpha =
                     ObjectAnimator.ofFloat(mDragLayerAlpha, MultiValueAlpha.VALUE, alphas);
-            alpha.setDuration(217);
+            alpha.setDuration(CONTENT_ALPHA_DURATION);
             alpha.setInterpolator(LINEAR);
             launcherAnimator.play(alpha);
 
             mDragLayer.setTranslationY(trans[0]);
             ObjectAnimator transY = ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y, trans);
             transY.setInterpolator(AGGRESSIVE_EASE);
-            transY.setDuration(350);
+            transY.setDuration(CONTENT_TRANSLATION_DURATION);
             launcherAnimator.play(transY);
 
             mDragLayer.getScrim().hideSysUiScrim(true);
@@ -429,15 +400,23 @@
             mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
             mDragLayer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
 
-            endListener = () -> {
-                resetContentView();
-                mDragLayer.getScrim().hideSysUiScrim(false);
-            };
+            endListener = this::resetContentView;
         }
         return new Pair<>(launcherAnimator, endListener);
     }
 
     /**
+     * Compose recents view alpha and translation Y animation when launcher opens/closes apps.
+     *
+     * @param anim the animator set to add to
+     * @param alphas the alphas to animate to over time
+     * @param trans the translation Y values to animator to over time
+     * @return listener to run when the animation ends
+     */
+    protected abstract Runnable composeViewContentAnimator(@NonNull AnimatorSet anim,
+            float[] alphas, float[] trans);
+
+    /**
      * Animators for the "floating view" of the view used to launch the target.
      */
     private void playIconAnimators(AnimatorSet appOpenAnimator, View v, Rect windowTargetBounds,
@@ -636,9 +615,12 @@
                 // Animate window corner radius from 100% to windowCornerRadius.
                 float windowCornerRadius = RecentsModel.INSTANCE.get(mLauncher)
                         .getWindowCornerRadius();
-                float circleRadius = iconWidth / 2f;
-                float windowRadius = Utilities.mapRange(easePercent, circleRadius,
-                        windowCornerRadius);
+                float windowRadius = 0;
+                if (RecentsModel.INSTANCE.get(mLauncher).supportsRoundedCornersOnWindows()) {
+                    float circleRadius = iconWidth / 2f;
+                    windowRadius = Utilities.mapRange(easePercent, circleRadius,
+                            windowCornerRadius);
+                }
 
                 // Animate the window crop so that it starts off as a square, and then reveals
                 // horizontally.
@@ -877,6 +859,8 @@
             workspaceAnimator.setDuration(333);
             workspaceAnimator.setInterpolator(Interpolators.DEACCEL_1_7);
 
+            mDragLayer.getScrim().hideSysUiScrim(true);
+
             // Pause page indicator animations as they lead to layer trashing.
             mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
             mDragLayer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
@@ -896,6 +880,7 @@
         mDragLayerAlpha.setValue(1f);
         mDragLayer.setLayerType(View.LAYER_TYPE_NONE, null);
         mDragLayer.setTranslationY(0f);
+        mDragLayer.getScrim().hideSysUiScrim(false);
     }
 
     private boolean hasControlRemoteAppTransitionPermission() {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
new file mode 100644
index 0000000..df9dbe4
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.uioverrides;
+
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
+import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
+import android.util.FloatProperty;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager.AnimationConfig;
+import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.PropertySetter;
+
+/**
+ * State handler for recents view. Manages UI changes and animations for recents view based off the
+ * current {@link LauncherState}.
+ *
+ * @param <T> the recents view
+ */
+public abstract class BaseRecentsViewStateController<T extends View>
+        implements StateHandler {
+    protected final T mRecentsView;
+    protected final Launcher mLauncher;
+
+    public BaseRecentsViewStateController(@NonNull Launcher launcher) {
+        mLauncher = launcher;
+        mRecentsView = launcher.getOverviewPanel();
+    }
+
+    @Override
+    public void setState(@NonNull LauncherState state) {
+        float[] scaleTranslationYFactor = state.getOverviewScaleAndTranslationYFactor(mLauncher);
+        SCALE_PROPERTY.set(mRecentsView, scaleTranslationYFactor[0]);
+        getTranslationYFactorProperty().set(mRecentsView, scaleTranslationYFactor[1]);
+        getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
+    }
+
+    @Override
+    public final void setStateWithAnimation(@NonNull final LauncherState toState,
+            @NonNull AnimatorSetBuilder builder, @NonNull AnimationConfig config) {
+        if (!config.playAtomicComponent()) {
+            // The entire recents animation is played atomically.
+            return;
+        }
+        setStateWithAnimationInternal(toState, builder, config);
+    }
+
+    /**
+     * Core logic for animating the recents view UI.
+     *
+     * @param toState state to animate to
+     * @param builder animator set builder
+     * @param config current animation config
+     */
+    void setStateWithAnimationInternal(@NonNull final LauncherState toState,
+            @NonNull AnimatorSetBuilder builder, @NonNull AnimationConfig config) {
+        PropertySetter setter = config.getPropertySetter(builder);
+        float[] scaleTranslationYFactor = toState.getOverviewScaleAndTranslationYFactor(mLauncher);
+        Interpolator scaleAndTransYInterpolator = getScaleAndTransYInterpolator(toState, builder);
+        setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleTranslationYFactor[0],
+                scaleAndTransYInterpolator);
+        setter.setFloat(mRecentsView, getTranslationYFactorProperty(), scaleTranslationYFactor[1],
+                scaleAndTransYInterpolator);
+        setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
+                builder.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
+    }
+
+    /**
+     * Get the interpolator to use for the scale and translation Y animation for the view.
+     *
+     * @param toState state to animate to
+     * @param builder animator set builder
+     * @return interpolator for scale and trans Y recents view animation
+     */
+    Interpolator getScaleAndTransYInterpolator(@NonNull final LauncherState toState,
+            @NonNull AnimatorSetBuilder builder) {
+        return builder.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR);
+    }
+
+    /**
+     * Get property for translation Y factor for the recents view.
+     *
+     * @return the float property for the recents view
+     */
+    abstract FloatProperty getTranslationYFactorProperty();
+
+    /**
+     * Get property for content alpha for the recents view.
+     *
+     * @return the float property for the view's content alpha
+     */
+    abstract FloatProperty getContentAlphaProperty();
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewToAllAppsTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewToAllAppsTouchController.java
index 0f9b57f..a069ed5 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewToAllAppsTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewToAllAppsTouchController.java
@@ -36,7 +36,7 @@
 public class OverviewToAllAppsTouchController extends PortraitStatesTouchController {
 
     public OverviewToAllAppsTouchController(Launcher l) {
-        super(l);
+        super(l, true /* allowDragToOverview */);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
index 330dc87..d20ffbb 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
@@ -25,6 +25,7 @@
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
 
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
@@ -47,8 +48,6 @@
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.TouchInteractionService;
 import com.android.quickstep.util.LayoutUtils;
-import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
 
 /**
  * Touch controller for handling various state transitions in portrait UI.
@@ -67,14 +66,19 @@
      */
     private static final float RECENTS_FADE_THRESHOLD = 0.88f;
 
-    private InterpolatorWrapper mAllAppsInterpolatorWrapper = new InterpolatorWrapper();
+    private final PortraitOverviewStateTouchHelper mOverviewPortraitStateTouchHelper;
+
+    private final InterpolatorWrapper mAllAppsInterpolatorWrapper = new InterpolatorWrapper();
+
+    private final boolean mAllowDragToOverview;
 
     // If true, we will finish the current animation instantly on second touch.
     private boolean mFinishFastOnSecondTouch;
 
-
-    public PortraitStatesTouchController(Launcher l) {
+    public PortraitStatesTouchController(Launcher l, boolean allowDragToOverview) {
         super(l, SwipeDetector.VERTICAL);
+        mOverviewPortraitStateTouchHelper = new PortraitOverviewStateTouchHelper(l);
+        mAllowDragToOverview = allowDragToOverview;
     }
 
     @Override
@@ -82,7 +86,7 @@
         if (mCurrentAnimation != null) {
             if (mFinishFastOnSecondTouch) {
                 // TODO: Animate to finish instead.
-                mCurrentAnimation.getAnimationPlayer().end();
+                mCurrentAnimation.skipToEnd();
             }
 
             AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
@@ -98,22 +102,18 @@
             }
             return false;
         }
-        RecentsView recentsView = mLauncher.getOverviewPanel();
         if (mLauncher.isInState(ALL_APPS)) {
             // In all-apps only listen if the container cannot scroll itself
             if (!mLauncher.getAppsView().shouldContainerScroll(ev)) {
                 return false;
             }
-        } else if (mLauncher.isInState(OVERVIEW) && recentsView.getChildCount() > 0) {
-            // Allow swiping up in the gap between the hotseat and overview.
-            if (ev.getY() < recentsView.getChildAt(0).getBottom()) {
+        } else if (mLauncher.isInState(OVERVIEW)) {
+            if (!mOverviewPortraitStateTouchHelper.canInterceptTouch(ev)) {
                 return false;
             }
         } else {
             // For all other states, only listen if the event originated below the hotseat height
-            DeviceProfile dp = mLauncher.getDeviceProfile();
-            int hotseatHeight = dp.hotseatBarSizePx + dp.getInsets().bottom;
-            if (ev.getY() < (mLauncher.getDragLayer().getHeight() - hotseatHeight)) {
+            if (!isTouchOverHotseat(mLauncher, ev)) {
                 return false;
             }
         }
@@ -132,7 +132,8 @@
         } else if (fromState == OVERVIEW) {
             return isDragTowardPositive ? ALL_APPS : NORMAL;
         } else if (fromState == NORMAL && isDragTowardPositive) {
-            return TouchInteractionService.isConnected() ? OVERVIEW : ALL_APPS;
+            return mAllowDragToOverview && TouchInteractionService.isConnected()
+                    ? OVERVIEW : ALL_APPS;
         }
         return fromState;
     }
@@ -197,13 +198,12 @@
 
         cancelPendingAnim();
 
-        RecentsView recentsView = mLauncher.getOverviewPanel();
-        TaskView taskView = recentsView.getTaskViewAt(recentsView.getNextPage());
-        if (recentsView.shouldSwipeDownLaunchApp() && mFromState == OVERVIEW && mToState == NORMAL
-                && taskView != null) {
+        if (mFromState == OVERVIEW && mToState == NORMAL
+                && mOverviewPortraitStateTouchHelper.shouldSwipeDownReturnToApp()) {
             // Reset the state manager, when changing the interaction mode
             mLauncher.getStateManager().goToState(OVERVIEW, false /* animate */);
-            mPendingAnimation = recentsView.createTaskLauncherAnimation(taskView, maxAccuracy);
+            mPendingAnimation = mOverviewPortraitStateTouchHelper
+                    .createSwipeDownToTaskAppAnimation(maxAccuracy);
             mPendingAnimation.anim.setInterpolator(Interpolators.LINEAR);
 
             Runnable onCancelRunnable = () -> {
@@ -246,7 +246,10 @@
     private void handleFirstSwipeToOverview(final ValueAnimator animator,
             final long expectedDuration, final LauncherState targetState, final float velocity,
             final boolean isFling) {
-        if (mFromState == NORMAL && mToState == OVERVIEW && targetState == OVERVIEW) {
+        if (QUICKSTEP_SPRINGS.get() && mFromState == OVERVIEW && mToState == ALL_APPS
+                && targetState == OVERVIEW) {
+            mFinishFastOnSecondTouch = true;
+        } else  if (mFromState == NORMAL && mToState == OVERVIEW && targetState == OVERVIEW) {
             mFinishFastOnSecondTouch = true;
             if (isFling && expectedDuration != 0) {
                 // Update all apps interpolator to add a bit of overshoot starting from currFraction
@@ -269,6 +272,19 @@
         }
     }
 
+    /**
+     * Whether the motion event is over the hotseat.
+     *
+     * @param launcher the launcher activity
+     * @param ev the event to check
+     * @return true if the event is over the hotseat
+     */
+    static boolean isTouchOverHotseat(Launcher launcher, MotionEvent ev) {
+        DeviceProfile dp = launcher.getDeviceProfile();
+        int hotseatHeight = dp.hotseatBarSizePx + dp.getInsets().bottom;
+        return (ev.getY() >= (launcher.getDragLayer().getHeight() - hotseatHeight));
+    }
+
     private static class InterpolatorWrapper implements Interpolator {
 
         public TimeInterpolator baseInterpolator = LINEAR;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
deleted file mode 100644
index abd2846..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.uioverrides;
-
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.LauncherState.FAST_OVERVIEW;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
-import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_START_INTERPOLATOR;
-import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_TRANSLATION_Y_FACTOR;
-import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_Y_FACTOR;
-import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
-
-import android.animation.ValueAnimator;
-import android.annotation.TargetApi;
-import android.os.Build;
-import android.view.animation.Interpolator;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.AnimationConfig;
-import com.android.launcher3.LauncherStateManager.StateHandler;
-import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.anim.PropertySetter;
-import com.android.quickstep.views.LauncherRecentsView;
-
-@TargetApi(Build.VERSION_CODES.O)
-public class RecentsViewStateController implements StateHandler {
-
-    private final Launcher mLauncher;
-    private final LauncherRecentsView mRecentsView;
-
-    public RecentsViewStateController(Launcher launcher) {
-        mLauncher = launcher;
-        mRecentsView = launcher.getOverviewPanel();
-    }
-
-    @Override
-    public void setState(LauncherState state) {
-        mRecentsView.setContentAlpha(state.overviewUi ? 1 : 0);
-        float[] scaleTranslationYFactor = state.getOverviewScaleAndTranslationYFactor(mLauncher);
-        SCALE_PROPERTY.set(mRecentsView, scaleTranslationYFactor[0]);
-        mRecentsView.setTranslationYFactor(scaleTranslationYFactor[1]);
-        if (state.overviewUi) {
-            mRecentsView.updateEmptyMessage();
-            mRecentsView.resetTaskVisuals();
-        }
-    }
-
-    @Override
-    public void setStateWithAnimation(final LauncherState toState,
-            AnimatorSetBuilder builder, AnimationConfig config) {
-        if (!config.playAtomicComponent()) {
-            // The entire recents animation is played atomically.
-            return;
-        }
-        PropertySetter setter = config.getPropertySetter(builder);
-        float[] scaleTranslationYFactor = toState.getOverviewScaleAndTranslationYFactor(mLauncher);
-        Interpolator scaleAndTransYInterpolator = builder.getInterpolator(
-                ANIM_OVERVIEW_SCALE, LINEAR);
-        if (mLauncher.getStateManager().getState() == OVERVIEW && toState == FAST_OVERVIEW) {
-            scaleAndTransYInterpolator = Interpolators.clampToProgress(
-                    QUICK_SCRUB_START_INTERPOLATOR, 0, QUICK_SCRUB_TRANSLATION_Y_FACTOR);
-        }
-        setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleTranslationYFactor[0],
-                scaleAndTransYInterpolator);
-        setter.setFloat(mRecentsView, TRANSLATION_Y_FACTOR, scaleTranslationYFactor[1],
-                scaleAndTransYInterpolator);
-        setter.setFloat(mRecentsView, CONTENT_ALPHA, toState.overviewUi ? 1 : 0,
-                builder.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
-
-        if (!toState.overviewUi) {
-            builder.addOnFinishRunnable(mRecentsView::resetTaskVisuals);
-        }
-
-        if (toState.overviewUi) {
-            ValueAnimator updateAnim = ValueAnimator.ofFloat(0, 1);
-            updateAnim.addUpdateListener(valueAnimator -> {
-                // While animating into recents, update the visible task data as needed
-                mRecentsView.loadVisibleTaskData();
-            });
-            updateAnim.setDuration(config.duration);
-            builder.play(updateAnim);
-            mRecentsView.updateEmptyMessage();
-        }
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
index 50af4a1..fb1828b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
@@ -18,6 +18,7 @@
 import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE;
 import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 
 import android.animation.Animator;
@@ -281,6 +282,9 @@
                 }
             });
         }
+        if (QUICKSTEP_SPRINGS.get()) {
+            mCurrentAnimation.dispatchOnStartWithVelocity(goingToEnd ? 1f : 0f, velocity);
+        }
         anim.start();
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index 3aa6482..70aae13 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -21,7 +21,10 @@
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.allapps.DiscoveryBounce.BOUNCE_MAX_COUNT;
+import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_COUNT;
 import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_SEEN;
+import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_COUNT;
 import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_SEEN;
 
 import android.animation.AnimatorSet;
@@ -32,68 +35,32 @@
 import android.util.Base64;
 
 import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppTransitionManagerImpl;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.QuickstepAppTransitionManagerImpl;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.util.TouchController;
-import com.android.launcher3.util.UiThreadHelper;
-import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
 import com.android.quickstep.OverviewInteractionState;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.util.RemoteFadeOutAnimationListener;
 import com.android.systemui.shared.system.ActivityCompat;
-import com.android.systemui.shared.system.WindowManagerWrapper;
 
 import java.io.ByteArrayOutputStream;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.zip.Deflater;
 
-public class UiFactory {
-
-    private static final AsyncCommand SET_SHELF_HEIGHT_CMD = (visible, height) ->
-            WindowManagerWrapper.getInstance().setShelfHeight(visible != 0, height);
-
-    public static TouchController[] createTouchControllers(Launcher launcher) {
-        boolean swipeUpEnabled = OverviewInteractionState.INSTANCE.get(launcher)
-                .isSwipeUpGestureEnabled();
-        ArrayList<TouchController> list = new ArrayList<>();
-        list.add(launcher.getDragController());
-
-        if (!swipeUpEnabled || launcher.getDeviceProfile().isVerticalBarLayout()) {
-            list.add(new OverviewToAllAppsTouchController(launcher));
-        }
-
-        if (launcher.getDeviceProfile().isVerticalBarLayout()) {
-            list.add(new LandscapeEdgeSwipeController(launcher));
-        } else {
-            list.add(new PortraitStatesTouchController(launcher));
-        }
-        if (FeatureFlags.PULL_DOWN_STATUS_BAR && Utilities.IS_DEBUG_DEVICE
-                && !launcher.getDeviceProfile().isMultiWindowMode
-                && !launcher.getDeviceProfile().isVerticalBarLayout()) {
-            list.add(new StatusBarTouchController(launcher));
-        }
-        TouchController taskSwipeController =
-                RecentsUiFactory.createTaskSwipeController(launcher);
-        if (taskSwipeController != null) {
-            list.add(taskSwipeController);
-        }
-        return list.toArray(new TouchController[list.size()]);
-    }
+public class UiFactory extends RecentsUiFactory {
 
     public static void setOnTouchControllersChangedListener(Context context, Runnable listener) {
         OverviewInteractionState.INSTANCE.get(context).setOnSwipeUpSettingChangedListener(listener);
     }
 
     public static StateHandler[] getStateHandler(Launcher launcher) {
-        return new StateHandler[] {launcher.getAllAppsController(), launcher.getWorkspace(),
-                RecentsUiFactory.createRecentsViewStateController(launcher),
+        return new StateHandler[] {
+                launcher.getAllAppsController(),
+                launcher.getWorkspace(),
+                createRecentsViewStateController(launcher),
                 new BackButtonAlphaHandler(launcher)};
     }
 
@@ -113,19 +80,10 @@
                 .setBackButtonAlpha(shouldBackButtonBeHidden ? 0 : 1, true /* animate */);
     }
 
-    public static void resetOverview(Launcher launcher) {
-        RecentsUiFactory.resetRecents(launcher);
-    }
-
     public static void onCreate(Launcher launcher) {
         if (!launcher.getSharedPrefs().getBoolean(HOME_BOUNCE_SEEN, false)) {
             launcher.getStateManager().addStateListener(new LauncherStateManager.StateListener() {
                 @Override
-                public void onStateSetImmediately(LauncherState state) {
-                    onStateTransitionComplete(state);
-                }
-
-                @Override
                 public void onStateTransitionStart(LauncherState toState) {
                 }
 
@@ -136,7 +94,8 @@
                     LauncherState prevState = launcher.getStateManager().getLastState();
 
                     if (((swipeUpEnabled && finalState == OVERVIEW) || (!swipeUpEnabled
-                            && finalState == ALL_APPS && prevState == NORMAL))) {
+                            && finalState == ALL_APPS && prevState == NORMAL) || BOUNCE_MAX_COUNT <=
+                            launcher.getSharedPrefs().getInt(HOME_BOUNCE_COUNT, 0))) {
                         launcher.getSharedPrefs().edit().putBoolean(HOME_BOUNCE_SEEN, true).apply();
                         launcher.getStateManager().removeStateListener(this);
                     }
@@ -147,11 +106,6 @@
         if (!launcher.getSharedPrefs().getBoolean(SHELF_BOUNCE_SEEN, false)) {
             launcher.getStateManager().addStateListener(new LauncherStateManager.StateListener() {
                 @Override
-                public void onStateSetImmediately(LauncherState state) {
-                    onStateTransitionComplete(state);
-                }
-
-                @Override
                 public void onStateTransitionStart(LauncherState toState) {
                 }
 
@@ -159,7 +113,8 @@
                 public void onStateTransitionComplete(LauncherState finalState) {
                     LauncherState prevState = launcher.getStateManager().getLastState();
 
-                    if (finalState == ALL_APPS && prevState == OVERVIEW) {
+                    if ((finalState == ALL_APPS && prevState == OVERVIEW) || BOUNCE_MAX_COUNT <=
+                            launcher.getSharedPrefs().getInt(SHELF_BOUNCE_COUNT, 0)) {
                         launcher.getSharedPrefs().edit().putBoolean(SHELF_BOUNCE_SEEN, true).apply();
                         launcher.getStateManager().removeStateListener(this);
                     }
@@ -176,19 +131,6 @@
                 .getHighResLoadingState().setVisible(true);
     }
 
-    public static void onLauncherStateOrResumeChanged(Launcher launcher) {
-        LauncherState state = launcher.getStateManager().getState();
-        if (!OverviewInteractionState.INSTANCE.get(launcher).swipeGestureInitializing()) {
-            DeviceProfile profile = launcher.getDeviceProfile();
-            boolean visible = (state == NORMAL || state == OVERVIEW) && launcher.isUserActive()
-                    && !profile.isVerticalBarLayout();
-            UiThreadHelper.runAsyncCommand(launcher, SET_SHELF_HEIGHT_CMD,
-                    visible ? 1 : 0, profile.hotseatBarSizePx);
-        }
-
-        RecentsUiFactory.onLauncherStateOrResumeChanged(launcher);
-    }
-
     public static void onTrimMemory(Context context, int level) {
         RecentsModel model = RecentsModel.INSTANCE.get(context);
         if (model != null) {
@@ -198,8 +140,8 @@
 
     public static void useFadeOutAnimationForLauncherStart(Launcher launcher,
             CancellationSignal cancellationSignal) {
-        LauncherAppTransitionManagerImpl appTransitionManager =
-                (LauncherAppTransitionManagerImpl) launcher.getAppTransitionManager();
+        QuickstepAppTransitionManagerImpl appTransitionManager =
+                (QuickstepAppTransitionManagerImpl) launcher.getAppTransitionManager();
         appTransitionManager.setRemoteAnimationProvider((targets) -> {
 
             // On the first call clear the reference.
@@ -238,8 +180,4 @@
                 out.toByteArray(), Base64.NO_WRAP | Base64.NO_PADDING));
         return true;
     }
-
-    public static void prepareToShowOverview(Launcher launcher) {
-        RecentsUiFactory.prepareToShowRecents(launcher);
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index c3df9c7..fbb3618 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -16,12 +16,15 @@
 package com.android.quickstep;
 
 import static android.view.View.TRANSLATION_Y;
+
 import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.FAST_OVERVIEW;
+import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS_SPRING;
+import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_DAMPING_RATIO;
+import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_STIFFNESS;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
 import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
@@ -31,6 +34,7 @@
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_ROTATION;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.annotation.TargetApi;
@@ -44,6 +48,9 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.view.View;
+import android.view.animation.Interpolator;
+
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
@@ -56,6 +63,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.TestProtocol;
 import com.android.launcher3.allapps.DiscoveryBounce;
+import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.SpringObjectAnimator;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
@@ -105,6 +113,8 @@
 
     void onSwipeUpComplete(T activity);
 
+    @NonNull HomeAnimationFactory prepareHomeUI(T activity);
+
     AnimationFactory prepareRecentsUI(T activity, boolean activityVisible,
             boolean animateActivity, Consumer<AnimatorPlaybackController> callback);
 
@@ -234,6 +244,32 @@
             DiscoveryBounce.showForOverviewIfNeeded(activity);
         }
 
+        @NonNull
+        @Override
+        public HomeAnimationFactory prepareHomeUI(Launcher activity) {
+            DeviceProfile dp = activity.getDeviceProfile();
+
+            return new HomeAnimationFactory() {
+                @NonNull
+                @Override
+                public RectF getWindowTargetRect() {
+                    int halfIconSize = dp.iconSizePx / 2;
+                    float targetCenterX = dp.availableWidthPx / 2;
+                    float targetCenterY = dp.availableHeightPx - dp.hotseatBarSizePx;
+                    return new RectF(targetCenterX - halfIconSize, targetCenterY - halfIconSize,
+                            targetCenterX + halfIconSize, targetCenterY + halfIconSize);
+                }
+
+                @NonNull
+                @Override
+                public Animator createActivityAnimationToHome() {
+                    long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
+                    return activity.getStateManager().createAnimationToNewWorkspace(
+                            NORMAL, accuracy).getTarget();
+                }
+            };
+        }
+
         @Override
         public AnimationFactory prepareRecentsUI(Launcher activity, boolean activityVisible,
                 boolean animateActivity, Consumer<AnimatorPlaybackController> callback) {
@@ -263,6 +299,9 @@
             }
 
             return new AnimationFactory() {
+                private Animator mShelfAnim;
+                private ShelfAnimState mShelfState;
+
                 @Override
                 public void createActivityController(long transitionLength,
                         @InteractionType int interactionType) {
@@ -274,6 +313,40 @@
                 public void onTransitionCancelled() {
                     activity.getStateManager().goToState(startState, false /* animate */);
                 }
+
+                @Override
+                public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator,
+                        long duration) {
+                    if (mShelfState == shelfState) {
+                        return;
+                    }
+                    mShelfState = shelfState;
+                    if (mShelfAnim != null) {
+                        mShelfAnim.cancel();
+                    }
+                    if (mShelfState == ShelfAnimState.CANCEL) {
+                        return;
+                    }
+                    float shelfHiddenProgress = BACKGROUND_APP.getVerticalProgress(activity);
+                    float shelfOverviewProgress = OVERVIEW.getVerticalProgress(activity);
+                    float shelfPeekingProgress = shelfHiddenProgress
+                            - (shelfHiddenProgress - shelfOverviewProgress) * 0.25f;
+                    float toProgress = mShelfState == ShelfAnimState.HIDE
+                            ? shelfHiddenProgress
+                            : mShelfState == ShelfAnimState.PEEK
+                                    ? shelfPeekingProgress
+                                    : shelfOverviewProgress;
+                    mShelfAnim = createShelfAnim(activity, toProgress);
+                    mShelfAnim.addListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            mShelfAnim = null;
+                        }
+                    });
+                    mShelfAnim.setInterpolator(interpolator);
+                    mShelfAnim.setDuration(duration);
+                    mShelfAnim.start();
+                }
             };
         }
 
@@ -295,13 +368,12 @@
             }
 
             AnimatorSet anim = new AnimatorSet();
-            if (!activity.getDeviceProfile().isVerticalBarLayout()) {
-                Animator shiftAnim = new SpringObjectAnimator<>(activity.getAllAppsController(),
-                        ALL_APPS_PROGRESS_SPRING, "allAppsSpringFromACH",
-                        activity.getAllAppsController().getShiftRange(),
+            if (!activity.getDeviceProfile().isVerticalBarLayout()
+                    && !FeatureFlags.SWIPE_HOME.get()) {
+                // Don't animate the shelf when SWIPE_HOME is true, because we update it atomically.
+                Animator shiftAnim = createShelfAnim(activity,
                         fromState.getVerticalProgress(activity),
                         endState.getVerticalProgress(activity));
-                shiftAnim.setInterpolator(LINEAR);
                 anim.play(shiftAnim);
             }
 
@@ -322,6 +394,14 @@
             callback.accept(controller);
         }
 
+        private Animator createShelfAnim(Launcher activity, float ... progressValues) {
+            Animator shiftAnim = new SpringObjectAnimator<>(activity.getAllAppsController(),
+                    "allAppsSpringFromACH", activity.getAllAppsController().getShiftRange(),
+                    SPRING_DAMPING_RATIO, SPRING_STIFFNESS, progressValues);
+            shiftAnim.setInterpolator(LINEAR);
+            return shiftAnim;
+        }
+
         /**
          * Scale down recents from the center task being full screen to being in overview.
          */
@@ -512,6 +592,35 @@
             // TODO:
         }
 
+        @NonNull
+        @Override
+        public HomeAnimationFactory prepareHomeUI(RecentsActivity activity) {
+            RecentsView recentsView = activity.getOverviewPanel();
+
+            return new HomeAnimationFactory() {
+                @NonNull
+                @Override
+                public RectF getWindowTargetRect() {
+                    float centerX = recentsView.getPivotX();
+                    float centerY = recentsView.getPivotY();
+                    return new RectF(centerX, centerY, centerX, centerY);
+                }
+
+                @NonNull
+                @Override
+                public Animator createActivityAnimationToHome() {
+                    Animator anim = ObjectAnimator.ofFloat(recentsView, CONTENT_ALPHA, 0);
+                    anim.addListener(new AnimationSuccessListener() {
+                        @Override
+                        public void onAnimationSuccess(Animator animator) {
+                            recentsView.startHome();
+                        }
+                    });
+                    return anim;
+                }
+            };
+        }
+
         @Override
         public AnimationFactory prepareRecentsUI(RecentsActivity activity, boolean activityVisible,
                 boolean animateActivity, Consumer<AnimatorPlaybackController> callback) {
@@ -524,12 +633,12 @@
 
             return new AnimationFactory() {
 
-                boolean isAnimatingHome = false;
+                boolean isAnimatingToRecents = false;
 
                 @Override
                 public void onRemoteAnimationReceived(RemoteAnimationTargetSet targets) {
-                    isAnimatingHome = targets != null && targets.isAnimatingHome();
-                    if (!isAnimatingHome) {
+                    isAnimatingToRecents = targets != null && targets.isAnimatingHome();
+                    if (!isAnimatingToRecents) {
                         rv.setContentAlpha(1);
                     }
                     createActivityController(getSwipeUpDestinationAndLength(
@@ -539,7 +648,7 @@
 
                 @Override
                 public void createActivityController(long transitionLength, int interactionType) {
-                    if (!isAnimatingHome) {
+                    if (!isAnimatingToRecents) {
                         return;
                     }
 
@@ -667,10 +776,30 @@
 
     interface AnimationFactory {
 
+        enum ShelfAnimState {
+            HIDE(true), PEEK(true), OVERVIEW(false), CANCEL(false);
+
+            ShelfAnimState(boolean shouldPreformHaptic) {
+                this.shouldPreformHaptic = shouldPreformHaptic;
+            }
+
+            public final boolean shouldPreformHaptic;
+        }
+
         default void onRemoteAnimationReceived(RemoteAnimationTargetSet targets) { }
 
         void createActivityController(long transitionLength, @InteractionType int interactionType);
 
         default void onTransitionCancelled() { }
+
+        default void setShelfState(ShelfAnimState animState, Interpolator interpolator,
+                long duration) { }
+    }
+
+    interface HomeAnimationFactory {
+
+        @NonNull RectF getWindowTargetRect();
+
+        @NonNull Animator createActivityAnimationToHome();
     }
 }
diff --git a/quickstep/src/com/android/quickstep/MultiStateCallback.java b/quickstep/src/com/android/quickstep/MultiStateCallback.java
index 98d723f..1592a97 100644
--- a/quickstep/src/com/android/quickstep/MultiStateCallback.java
+++ b/quickstep/src/com/android/quickstep/MultiStateCallback.java
@@ -17,7 +17,7 @@
 
 import android.util.SparseArray;
 
-import com.android.launcher3.Utilities.Consumer;
+import java.util.function.Consumer;
 
 /**
  * Utility class to help manage multiple callbacks based on different states.
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index 9712eb3..5755205 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -21,6 +21,7 @@
 import static android.view.MotionEvent.ACTION_POINTER_UP;
 import static android.view.MotionEvent.ACTION_UP;
 import static android.view.MotionEvent.INVALID_POINTER_ID;
+
 import static com.android.launcher3.util.RaceConditionTracker.ENTER;
 import static com.android.launcher3.util.RaceConditionTracker.EXIT;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
@@ -47,8 +48,10 @@
 import android.view.WindowManager;
 
 import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.RaceConditionTracker;
 import com.android.launcher3.util.TraceHelper;
+import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.AssistDataReceiver;
@@ -64,6 +67,8 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
+import androidx.annotation.Nullable;
+
 /**
  * Touch consumer for handling events originating from an activity other than Launcher
  */
@@ -72,6 +77,7 @@
 
     private static final long LAUNCHER_DRAW_TIMEOUT_MS = 150;
     public static final String DOWN_EVT = "OtherActivityTouchConsumer.DOWN";
+    private static final String UP_EVT = "OtherActivityTouchConsumer.UP";
 
     private final SparseArray<RecentsAnimationState> mAnimationStates = new SparseArray<>();
     private final RunningTaskInfo mRunningTask;
@@ -98,6 +104,7 @@
     private Rect mStableInsets = new Rect();
 
     private VelocityTracker mVelocityTracker;
+    private MotionPauseDetector mMotionPauseDetector;
     private MotionEventQueue mEventQueue;
     private boolean mIsGoingToLauncher;
 
@@ -113,6 +120,7 @@
         mRecentsModel = recentsModel;
         mHomeIntent = homeIntent;
         mVelocityTracker = velocityTracker;
+        mMotionPauseDetector = new MotionPauseDetector(base);
         mActivityControlHelper = activityControl;
         mMainThreadExecutor = mainThreadExecutor;
         mBackgroundThreadChoreographer = backgroundThreadChoreographer;
@@ -190,26 +198,32 @@
 
                 if (mPassedInitialSlop && mInteractionHandler != null) {
                     // Move
-                    dispatchMotion(ev, displacement - mStartDisplacement);
+                    dispatchMotion(ev, displacement - mStartDisplacement, null);
+
+                    if (FeatureFlags.SWIPE_HOME.get()) {
+                        mMotionPauseDetector.addPosition(displacement);
+                    }
                 }
                 break;
             }
             case ACTION_CANCEL:
                 // TODO: Should be different than ACTION_UP
             case ACTION_UP: {
+                RaceConditionTracker.onEvent(UP_EVT, ENTER);
                 TraceHelper.endSection("TouchInt");
 
                 finishTouchTracking(ev);
+                RaceConditionTracker.onEvent(UP_EVT, EXIT);
                 break;
             }
         }
     }
 
-    private void dispatchMotion(MotionEvent ev, float displacement) {
+    private void dispatchMotion(MotionEvent ev, float displacement, @Nullable Float velocityX) {
         mInteractionHandler.updateDisplacement(displacement);
         boolean isLandscape = isNavBarOnLeft() || isNavBarOnRight();
         if (!isLandscape) {
-            mInteractionHandler.dispatchMotionEventToRecentsView(ev);
+            mInteractionHandler.dispatchMotionEventToRecentsView(ev, velocityX);
         }
     }
 
@@ -247,6 +261,7 @@
         mRecentsModel.getTasks(null);
         mInteractionHandler = handler;
         handler.setGestureEndCallback(mEventQueue::reset);
+        mMotionPauseDetector.setOnMotionPauseListener(handler::onMotionPauseChanged);
 
         CountDownLatch drawWaitLock = new CountDownLatch(1);
         handler.setLauncherOnDrawCallback(() -> {
@@ -303,15 +318,16 @@
      */
     private void finishTouchTracking(MotionEvent ev) {
         if (mPassedInitialSlop && mInteractionHandler != null) {
-            dispatchMotion(ev, getDisplacement(ev) - mStartDisplacement);
 
             mVelocityTracker.computeCurrentVelocity(1000,
                     ViewConfiguration.get(this).getScaledMaximumFlingVelocity());
-
             float velocityX = mVelocityTracker.getXVelocity(mActivePointerId);
             float velocity = isNavBarOnRight() ? velocityX
                     : isNavBarOnLeft() ? -velocityX
                             : mVelocityTracker.getYVelocity(mActivePointerId);
+
+            dispatchMotion(ev, getDisplacement(ev) - mStartDisplacement, velocityX);
+
             mInteractionHandler.onGestureEnded(velocity, velocityX);
         } else {
             // Since we start touch tracking on DOWN, we may reach this state without actually
@@ -333,7 +349,7 @@
         if (mInteractionHandler != null) {
             final WindowTransformSwipeHandler handler = mInteractionHandler;
             mInteractionHandler = null;
-            mIsGoingToLauncher = handler.mIsGoingToRecents;
+            mIsGoingToLauncher = handler.mIsGoingToRecents || handler.mIsGoingToHome;
             mMainThreadExecutor.execute(handler::reset);
         }
     }
@@ -384,8 +400,10 @@
             // touch slop
             startTouchTrackingForWindowAnimation(ev.getEventTime());
         }
-        mPassedInitialSlop = true;
-        mStartDisplacement = getDisplacement(ev);
+        if (!mPassedInitialSlop) {
+            mPassedInitialSlop = true;
+            mStartDisplacement = getDisplacement(ev);
+        }
         notifyGestureStarted();
     }
 
@@ -411,6 +429,7 @@
            mVelocityTracker.addMovement(ev);
            if (ev.getActionMasked() == ACTION_POINTER_UP) {
                mVelocityTracker.clear();
+               mMotionPauseDetector.clear();
            }
         }
     }
@@ -428,6 +447,7 @@
 
     private class RecentsAnimationState implements RecentsAnimationListener {
 
+        private static final String ANIMATION_START_EVT = "RecentsAnimationState.onAnimationStart";
         private final int id;
 
         private RecentsAnimationControllerCompat mController;
@@ -446,11 +466,13 @@
                 RecentsAnimationControllerCompat controller,
                 RemoteAnimationTargetCompat[] apps, Rect homeContentInsets,
                 Rect minimizedHomeBounds) {
+            RaceConditionTracker.onEvent(ANIMATION_START_EVT, ENTER);
             mController = controller;
             mTargets = new RemoteAnimationTargetSet(apps, MODE_CLOSING);
             mHomeContentInsets = homeContentInsets;
             mMinimizedHomeBounds = minimizedHomeBounds;
             mEventQueue.onCommand(id);
+            RaceConditionTracker.onEvent(ANIMATION_START_EVT, EXIT);
         }
 
         @Override
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 9bbe57a..da4a3de 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -15,17 +15,11 @@
  */
 package com.android.quickstep;
 
-import static android.content.Intent.ACTION_PACKAGE_ADDED;
-import static android.content.Intent.ACTION_PACKAGE_CHANGED;
-import static android.content.Intent.ACTION_PACKAGE_REMOVED;
-
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
 import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
 import static com.android.systemui.shared.system.ActivityManagerWrapper
         .CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
-import static com.android.systemui.shared.system.PackageManagerWrapper
-        .ACTION_PREFERRED_ACTIVITY_CHANGED;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
 
@@ -33,15 +27,9 @@
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ResolveInfo;
 import android.graphics.Rect;
 import android.os.Build;
-import android.os.PatternMatcher;
 import android.os.SystemClock;
 import android.util.Log;
 import android.view.View;
@@ -56,21 +44,16 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
 import com.android.quickstep.ActivityControlHelper.AnimationFactory;
-import com.android.quickstep.ActivityControlHelper.FallbackActivityControllerHelper;
-import com.android.quickstep.ActivityControlHelper.LauncherActivityControllerHelper;
 import com.android.quickstep.util.ClipAnimationHelper;
-import com.android.quickstep.util.TransformedRect;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
+import com.android.quickstep.util.TransformedRect;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.LatencyTrackerCompat;
-import com.android.systemui.shared.system.PackageManagerWrapper;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
 import com.android.systemui.shared.system.TransactionCompat;
 
-import java.util.ArrayList;
-
 /**
  * Helper class to handle various atomic commands for switching between Overview.
  */
@@ -85,100 +68,16 @@
     private final ActivityManagerWrapper mAM;
     private final RecentsModel mRecentsModel;
     private final MainThreadExecutor mMainThreadExecutor;
-    private final ComponentName mMyHomeComponent;
-
-    private final BroadcastReceiver mUserPreferenceChangeReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            initOverviewTargets();
-        }
-    };
-    private final BroadcastReceiver mOtherHomeAppUpdateReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            initOverviewTargets();
-        }
-    };
-    private String mUpdateRegisteredPackage;
-
-    public Intent overviewIntent;
-    public ComponentName overviewComponent;
-    private ActivityControlHelper mActivityControlHelper;
+    private final OverviewComponentObserver mOverviewComponentObserver;
 
     private long mLastToggleTime;
 
-    public OverviewCommandHelper(Context context) {
+    public OverviewCommandHelper(Context context, OverviewComponentObserver observer) {
         mContext = context;
         mAM = ActivityManagerWrapper.getInstance();
         mMainThreadExecutor = new MainThreadExecutor();
         mRecentsModel = RecentsModel.INSTANCE.get(mContext);
-
-        Intent myHomeIntent = new Intent(Intent.ACTION_MAIN)
-                .addCategory(Intent.CATEGORY_HOME)
-                .setPackage(mContext.getPackageName());
-        ResolveInfo info = context.getPackageManager().resolveActivity(myHomeIntent, 0);
-        mMyHomeComponent = new ComponentName(context.getPackageName(), info.activityInfo.name);
-
-        mContext.registerReceiver(mUserPreferenceChangeReceiver,
-                new IntentFilter(ACTION_PREFERRED_ACTIVITY_CHANGED));
-        initOverviewTargets();
-    }
-
-    private void initOverviewTargets() {
-        ComponentName defaultHome = PackageManagerWrapper.getInstance()
-                .getHomeActivities(new ArrayList<>());
-
-        final String overviewIntentCategory;
-        if (defaultHome == null || mMyHomeComponent.equals(defaultHome)) {
-            // User default home is same as out home app. Use Overview integrated in Launcher.
-            overviewComponent = mMyHomeComponent;
-            mActivityControlHelper = new LauncherActivityControllerHelper();
-            overviewIntentCategory = Intent.CATEGORY_HOME;
-
-            if (mUpdateRegisteredPackage != null) {
-                // Remove any update listener as we don't care about other packages.
-                mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver);
-                mUpdateRegisteredPackage = null;
-            }
-        } else {
-            // The default home app is a different launcher. Use the fallback Overview instead.
-            overviewComponent = new ComponentName(mContext, RecentsActivity.class);
-            mActivityControlHelper = new FallbackActivityControllerHelper(defaultHome);
-            overviewIntentCategory = Intent.CATEGORY_DEFAULT;
-
-            // User's default home app can change as a result of package updates of this app (such
-            // as uninstalling the app or removing the "Launcher" feature in an update).
-            // Listen for package updates of this app (and remove any previously attached
-            // package listener).
-            if (!defaultHome.getPackageName().equals(mUpdateRegisteredPackage)) {
-                if (mUpdateRegisteredPackage != null) {
-                    mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver);
-                }
-
-                mUpdateRegisteredPackage = defaultHome.getPackageName();
-                IntentFilter updateReceiver = new IntentFilter(ACTION_PACKAGE_ADDED);
-                updateReceiver.addAction(ACTION_PACKAGE_CHANGED);
-                updateReceiver.addAction(ACTION_PACKAGE_REMOVED);
-                updateReceiver.addDataScheme("package");
-                updateReceiver.addDataSchemeSpecificPart(mUpdateRegisteredPackage,
-                        PatternMatcher.PATTERN_LITERAL);
-                mContext.registerReceiver(mOtherHomeAppUpdateReceiver, updateReceiver);
-            }
-        }
-
-        overviewIntent = new Intent(Intent.ACTION_MAIN)
-                .addCategory(overviewIntentCategory)
-                .setComponent(overviewComponent)
-                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-    }
-
-    public void onDestroy() {
-        mContext.unregisterReceiver(mUserPreferenceChangeReceiver);
-
-        if (mUpdateRegisteredPackage != null) {
-            mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver);
-            mUpdateRegisteredPackage = null;
-        }
+        mOverviewComponentObserver = observer;
     }
 
     public void onOverviewToggle() {
@@ -200,10 +99,6 @@
                 UserEventDispatcher.newInstance(mContext).logActionTip(actionType, viewType));
     }
 
-    public ActivityControlHelper getActivityControlHelper() {
-        return mActivityControlHelper;
-    }
-
     private class ShowRecentsCommand extends RecentsActivityCommand {
 
         @Override
@@ -225,7 +120,7 @@
         private boolean mUserEventLogged;
 
         public RecentsActivityCommand() {
-            mHelper = getActivityControlHelper();
+            mHelper = mOverviewComponentObserver.getActivityControlHelper();
             mCreateTime = SystemClock.elapsedRealtime();
             mRunningTaskId = RecentsModel.getRunningTaskId();
 
@@ -242,8 +137,10 @@
                 // Start overview
                 if (!mHelper.switchToRecentsIfVisible(true)) {
                     mListener = mHelper.createActivityInitListener(this::onActivityReady);
-                    mListener.registerAndStartActivity(overviewIntent, this::createWindowAnimation,
-                            mContext, mMainThreadExecutor.getHandler(), RECENTS_LAUNCH_DURATION);
+                    mListener.registerAndStartActivity(
+                            mOverviewComponentObserver.getOverviewIntent(),
+                            this::createWindowAnimation, mContext, mMainThreadExecutor.getHandler(),
+                            RECENTS_LAUNCH_DURATION);
                 }
             }
         }
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
new file mode 100644
index 0000000..e119e53
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep;
+
+import static android.content.Intent.ACTION_PACKAGE_ADDED;
+import static android.content.Intent.ACTION_PACKAGE_CHANGED;
+import static android.content.Intent.ACTION_PACKAGE_REMOVED;
+
+import static com.android.systemui.shared.system.PackageManagerWrapper
+        .ACTION_PREFERRED_ACTIVITY_CHANGED;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ResolveInfo;
+import android.os.PatternMatcher;
+
+import com.android.quickstep.ActivityControlHelper.FallbackActivityControllerHelper;
+import com.android.quickstep.ActivityControlHelper.LauncherActivityControllerHelper;
+import com.android.systemui.shared.system.PackageManagerWrapper;
+
+import java.util.ArrayList;
+
+/**
+ * Class to keep track of the current overview component based off user preferences and app updates
+ * and provide callers the relevant classes.
+ */
+public final class OverviewComponentObserver {
+    private final BroadcastReceiver mUserPreferenceChangeReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            updateOverviewTargets();
+        }
+    };
+    private final BroadcastReceiver mOtherHomeAppUpdateReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            updateOverviewTargets();
+        }
+    };
+    private final Context mContext;
+    private final ComponentName mMyHomeComponent;
+    private String mUpdateRegisteredPackage;
+    private ActivityControlHelper mActivityControlHelper;
+    private Intent mOverviewIntent;
+
+    public OverviewComponentObserver(Context context) {
+        mContext = context;
+
+        Intent myHomeIntent = new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_HOME)
+                .setPackage(mContext.getPackageName());
+        ResolveInfo info = context.getPackageManager().resolveActivity(myHomeIntent, 0);
+        mMyHomeComponent = new ComponentName(context.getPackageName(), info.activityInfo.name);
+
+        mContext.registerReceiver(mUserPreferenceChangeReceiver,
+                new IntentFilter(ACTION_PREFERRED_ACTIVITY_CHANGED));
+        updateOverviewTargets();
+    }
+
+    /**
+     * Update overview intent and {@link ActivityControlHelper} based off the current launcher home
+     * component.
+     */
+    private void updateOverviewTargets() {
+        ComponentName defaultHome = PackageManagerWrapper.getInstance()
+                .getHomeActivities(new ArrayList<>());
+
+        final String overviewIntentCategory;
+        ComponentName overviewComponent;
+        if (defaultHome == null || mMyHomeComponent.equals(defaultHome)) {
+            // User default home is same as out home app. Use Overview integrated in Launcher.
+            overviewComponent = mMyHomeComponent;
+            mActivityControlHelper = new LauncherActivityControllerHelper();
+            overviewIntentCategory = Intent.CATEGORY_HOME;
+
+            if (mUpdateRegisteredPackage != null) {
+                // Remove any update listener as we don't care about other packages.
+                mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver);
+                mUpdateRegisteredPackage = null;
+            }
+        } else {
+            // The default home app is a different launcher. Use the fallback Overview instead.
+            overviewComponent = new ComponentName(mContext, RecentsActivity.class);
+            mActivityControlHelper = new FallbackActivityControllerHelper(defaultHome);
+            overviewIntentCategory = Intent.CATEGORY_DEFAULT;
+
+            // User's default home app can change as a result of package updates of this app (such
+            // as uninstalling the app or removing the "Launcher" feature in an update).
+            // Listen for package updates of this app (and remove any previously attached
+            // package listener).
+            if (!defaultHome.getPackageName().equals(mUpdateRegisteredPackage)) {
+                if (mUpdateRegisteredPackage != null) {
+                    mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver);
+                }
+
+                mUpdateRegisteredPackage = defaultHome.getPackageName();
+                IntentFilter updateReceiver = new IntentFilter(ACTION_PACKAGE_ADDED);
+                updateReceiver.addAction(ACTION_PACKAGE_CHANGED);
+                updateReceiver.addAction(ACTION_PACKAGE_REMOVED);
+                updateReceiver.addDataScheme("package");
+                updateReceiver.addDataSchemeSpecificPart(mUpdateRegisteredPackage,
+                        PatternMatcher.PATTERN_LITERAL);
+                mContext.registerReceiver(mOtherHomeAppUpdateReceiver, updateReceiver);
+            }
+        }
+
+        mOverviewIntent = new Intent(Intent.ACTION_MAIN)
+                .addCategory(overviewIntentCategory)
+                .setComponent(overviewComponent)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+    }
+
+    /**
+     * Clean up any registered receivers.
+     */
+    public void onDestroy() {
+        mContext.unregisterReceiver(mUserPreferenceChangeReceiver);
+
+        if (mUpdateRegisteredPackage != null) {
+            mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver);
+            mUpdateRegisteredPackage = null;
+        }
+    }
+
+    /**
+     * Get the current intent for going to the overview activity.
+     *
+     * @return the overview intent
+     */
+    public Intent getOverviewIntent() {
+        return mOverviewIntent;
+    }
+
+    /**
+     * Get the current activity control helper for managing interactions to the overview activity.
+     *
+     * @return the current activity control helper
+     */
+    public ActivityControlHelper getActivityControlHelper() {
+        return mActivityControlHelper;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index deedd32..b76a1ab 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -18,9 +18,9 @@
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
 
-import static com.android.launcher3.LauncherAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
-import static com.android.launcher3.LauncherAppTransitionManagerImpl.STATUS_BAR_TRANSITION_DURATION;
-import static com.android.launcher3.LauncherAppTransitionManagerImpl.STATUS_BAR_TRANSITION_PRE_DELAY;
+import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
+import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_DURATION;
+import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_PRE_DELAY;
 import static com.android.quickstep.TaskUtils.getRecentsWindowAnimator;
 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
@@ -40,7 +40,6 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAnimationRunner;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
@@ -106,7 +105,7 @@
     }
 
     public void onRootViewSizeChanged() {
-        if (isInMultiWindowModeCompat()) {
+        if (isInMultiWindowMode()) {
             onHandleConfigChanged();
         }
     }
@@ -133,7 +132,7 @@
 
         // In case we are reusing IDP, create a copy so that we don't conflict with Launcher
         // activity.
-        mDeviceProfile = (mRecentsRootView != null) && isInMultiWindowModeCompat()
+        mDeviceProfile = (mRecentsRootView != null) && isInMultiWindowMode()
                 ? dp.getMultiWindowProfile(this, mRecentsRootView.getLastKnownSize())
                 : dp.copy(this);
         onDeviceProfileInitiated();
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 442b106..e61c00a 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -66,6 +66,7 @@
     private final TaskThumbnailCache mThumbnailCache;
 
     private float mWindowCornerRadius = -1;
+    private Boolean mSupportsRoundedCornersOnWindows;
 
     private RecentsModel(Context context) {
         mContext = context;
@@ -199,6 +200,26 @@
         return mWindowCornerRadius;
     }
 
+    public boolean supportsRoundedCornersOnWindows() {
+        if (mSupportsRoundedCornersOnWindows == null) {
+            if (mSystemUiProxy != null) {
+                try {
+                    mSupportsRoundedCornersOnWindows =
+                            mSystemUiProxy.supportsRoundedCornersOnWindows();
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Connection to ISystemUIProxy was lost, ignoring window corner "
+                            + "radius");
+                    return false;
+                }
+            } else {
+                Log.w(TAG, "ISystemUIProxy is null, ignoring window corner radius");
+                return false;
+            }
+        }
+
+        return mSupportsRoundedCornersOnWindows;
+    }
+
     public void onTrimMemory(int level) {
         if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
             mThumbnailCache.getHighResLoadingState().setVisible(false);
diff --git a/quickstep/src/com/android/quickstep/TouchConsumer.java b/quickstep/src/com/android/quickstep/TouchConsumer.java
index 225d29b..057a2ee 100644
--- a/quickstep/src/com/android/quickstep/TouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/TouchConsumer.java
@@ -20,12 +20,12 @@
 import android.view.Choreographer;
 import android.view.MotionEvent;
 
+import androidx.annotation.IntDef;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.function.Consumer;
 
-import androidx.annotation.IntDef;
-
 @TargetApi(Build.VERSION_CODES.O)
 @FunctionalInterface
 public interface TouchConsumer extends Consumer<MotionEvent> {
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 8b6867f..d5de3ff 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -183,6 +183,7 @@
     private MainThreadExecutor mMainThreadExecutor;
     private ISystemUiProxy mISystemUiProxy;
     private OverviewCommandHelper mOverviewCommandHelper;
+    private OverviewComponentObserver mOverviewComponentObserver;
     private OverviewInteractionState mOverviewInteractionState;
     private OverviewCallbacks mOverviewCallbacks;
     private TaskOverlayFactory mTaskOverlayFactory;
@@ -198,7 +199,8 @@
         mAM = ActivityManagerWrapper.getInstance();
         mRecentsModel = RecentsModel.INSTANCE.get(this);
         mMainThreadExecutor = new MainThreadExecutor();
-        mOverviewCommandHelper = new OverviewCommandHelper(this);
+        mOverviewComponentObserver = new OverviewComponentObserver(this);
+        mOverviewCommandHelper = new OverviewCommandHelper(this, mOverviewComponentObserver);
         mMainThreadChoreographer = Choreographer.getInstance();
         mEventQueue = new MotionEventQueue(mMainThreadChoreographer, TouchConsumer.NO_OP);
         mOverviewInteractionState = OverviewInteractionState.INSTANCE.get(this);
@@ -218,7 +220,7 @@
     @Override
     public void onDestroy() {
         mInputConsumer.unregisterInputConsumer();
-        mOverviewCommandHelper.onDestroy();
+        mOverviewComponentObserver.onDestroy();
         sConnected = false;
         super.onDestroy();
     }
@@ -250,21 +252,22 @@
         if (runningTaskInfo == null && !forceToLauncher) {
             return TouchConsumer.NO_OP;
         } else if (forceToLauncher ||
-                mOverviewCommandHelper.getActivityControlHelper().isResumed()) {
+                mOverviewComponentObserver.getActivityControlHelper().isResumed()) {
             return OverviewTouchConsumer.newInstance(
-                    mOverviewCommandHelper.getActivityControlHelper(), false, mTouchInteractionLog);
+                    mOverviewComponentObserver.getActivityControlHelper(), false,
+                    mTouchInteractionLog);
         } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
-                mOverviewCommandHelper.getActivityControlHelper().isInLiveTileMode()) {
+                mOverviewComponentObserver.getActivityControlHelper().isInLiveTileMode()) {
             return OverviewTouchConsumer.newInstance(
-                    mOverviewCommandHelper.getActivityControlHelper(), false, mTouchInteractionLog,
-                    false /* waitForWindowAvailable */);
+                    mOverviewComponentObserver.getActivityControlHelper(), false,
+                    mTouchInteractionLog, false /* waitForWindowAvailable */);
         } else {
             if (tracker == null) {
                 tracker = VelocityTracker.obtain();
             }
             return new OtherActivityTouchConsumer(this, runningTaskInfo, mRecentsModel,
-                    mOverviewCommandHelper.overviewIntent,
-                    mOverviewCommandHelper.getActivityControlHelper(), mMainThreadExecutor,
+                    mOverviewComponentObserver.getOverviewIntent(),
+                    mOverviewComponentObserver.getActivityControlHelper(), mMainThreadExecutor,
                     mBackgroundThreadChoreographer, downHitTarget, mOverviewCallbacks,
                     mTaskOverlayFactory, mInputConsumer, tracker, mTouchInteractionLog);
         }
@@ -344,7 +347,8 @@
                         startTouchTracking(ev, true /* updateLocationOffset */);
                         break;
                     case ACTION_MOVE: {
-                        float displacement = ev.getY() - mDownPos.y;
+                        float displacement = mActivity.getDeviceProfile().isLandscape ?
+                                ev.getX() - mDownPos.x : ev.getY() - mDownPos.y;
                         if (Math.abs(displacement) >= mTouchSlop) {
                             // Start tracking only when mTouchSlop is crossed.
                             startTouchTracking(ev, true /* updateLocationOffset */);
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index d7720ee..86a8081 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -20,19 +20,32 @@
 import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
 import static com.android.launcher3.Utilities.postAsyncCallback;
 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.LINEAR;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.util.RaceConditionTracker.ENTER;
+import static com.android.launcher3.util.RaceConditionTracker.EXIT;
+import static com.android.launcher3.config.FeatureFlags.SWIPE_HOME;
+import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.HIDE;
+import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.PEEK;
 import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_FROM_APP_START_DURATION;
 import static com.android.quickstep.QuickScrubController.QUICK_SWITCH_FROM_APP_START_DURATION;
 import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
 import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
+import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.HOME;
+import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.LAST_TASK;
+import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.NEW_TASK;
+import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.RECENTS;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
@@ -40,6 +53,7 @@
 import android.graphics.Canvas;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -55,6 +69,8 @@
 import android.view.animation.Interpolator;
 
 import androidx.annotation.AnyThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 import androidx.annotation.WorkerThread;
 
@@ -74,9 +90,11 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
+import com.android.launcher3.util.RaceConditionTracker;
 import com.android.launcher3.util.TraceHelper;
 import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
 import com.android.quickstep.ActivityControlHelper.AnimationFactory;
+import com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState;
 import com.android.quickstep.ActivityControlHelper.LayoutListener;
 import com.android.quickstep.TouchConsumer.InteractionType;
 import com.android.quickstep.TouchInteractionService.OverviewTouchConsumer;
@@ -86,6 +104,7 @@
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.utilities.RectFEvaluator;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.LatencyTrackerCompat;
@@ -112,27 +131,28 @@
     private static final int STATE_APP_CONTROLLER_RECEIVED = 1 << 4;
 
     // Interaction finish states
-    private static final int STATE_SCALED_CONTROLLER_RECENTS = 1 << 5;
-    private static final int STATE_SCALED_CONTROLLER_LAST_TASK = 1 << 6;
+    private static final int STATE_SCALED_CONTROLLER_HOME = 1 << 5;
+    private static final int STATE_SCALED_CONTROLLER_RECENTS = 1 << 6;
+    private static final int STATE_SCALED_CONTROLLER_LAST_TASK = 1 << 7;
 
-    private static final int STATE_HANDLER_INVALIDATED = 1 << 7;
-    private static final int STATE_GESTURE_STARTED_QUICKSTEP = 1 << 8;
-    private static final int STATE_GESTURE_STARTED_QUICKSCRUB = 1 << 9;
-    private static final int STATE_GESTURE_CANCELLED = 1 << 10;
-    private static final int STATE_GESTURE_COMPLETED = 1 << 11;
+    private static final int STATE_HANDLER_INVALIDATED = 1 << 8;
+    private static final int STATE_GESTURE_STARTED_QUICKSTEP = 1 << 9;
+    private static final int STATE_GESTURE_STARTED_QUICKSCRUB = 1 << 10;
+    private static final int STATE_GESTURE_CANCELLED = 1 << 11;
+    private static final int STATE_GESTURE_COMPLETED = 1 << 12;
 
     // States for quick switch/scrub
-    private static final int STATE_CURRENT_TASK_FINISHED = 1 << 12;
-    private static final int STATE_QUICK_SCRUB_START = 1 << 13;
-    private static final int STATE_QUICK_SCRUB_END = 1 << 14;
+    private static final int STATE_CURRENT_TASK_FINISHED = 1 << 13;
+    private static final int STATE_QUICK_SCRUB_START = 1 << 14;
+    private static final int STATE_QUICK_SCRUB_END = 1 << 15;
 
-    private static final int STATE_CAPTURE_SCREENSHOT = 1 << 15;
-    private static final int STATE_SCREENSHOT_CAPTURED = 1 << 16;
-    private static final int STATE_SCREENSHOT_VIEW_SHOWN = 1 << 17;
+    private static final int STATE_CAPTURE_SCREENSHOT = 1 << 16;
+    private static final int STATE_SCREENSHOT_CAPTURED = 1 << 17;
+    private static final int STATE_SCREENSHOT_VIEW_SHOWN = 1 << 18;
 
-    private static final int STATE_RESUME_LAST_TASK = 1 << 18;
-    private static final int STATE_START_NEW_TASK = 1 << 19;
-    private static final int STATE_ASSIST_DATA_RECEIVED = 1 << 20;
+    private static final int STATE_RESUME_LAST_TASK = 1 << 19;
+    private static final int STATE_START_NEW_TASK = 1 << 20;
+    private static final int STATE_ASSIST_DATA_RECEIVED = 1 << 21;
 
 
     private static final int LAUNCHER_UI_STATES =
@@ -157,6 +177,7 @@
             "STATE_LAUNCHER_DRAWN",
             "STATE_ACTIVITY_MULTIPLIER_COMPLETE",
             "STATE_APP_CONTROLLER_RECEIVED",
+            "STATE_SCALED_CONTROLLER_HOME",
             "STATE_SCALED_CONTROLLER_RECENTS",
             "STATE_SCALED_CONTROLLER_LAST_TASK",
             "STATE_HANDLER_INVALIDATED",
@@ -175,6 +196,30 @@
             "STATE_ASSIST_DATA_RECEIVED",
     };
 
+    enum GestureEndTarget {
+        HOME(1, STATE_SCALED_CONTROLLER_HOME, true, ContainerType.WORKSPACE),
+
+        RECENTS(1, STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
+                | STATE_SCREENSHOT_VIEW_SHOWN, true, ContainerType.TASKSWITCHER),
+
+        NEW_TASK(0, STATE_START_NEW_TASK, false, ContainerType.APP),
+
+        LAST_TASK(0, STATE_SCALED_CONTROLLER_LAST_TASK, false, ContainerType.APP);
+
+        GestureEndTarget(float endShift, int endState, boolean isLauncher, int containerType) {
+            this.endShift = endShift;
+            this.endState = endState;
+            this.isLauncher = isLauncher;
+            this.containerType = containerType;
+        }
+
+        // 0 is app, 1 is overview
+        public final float endShift;
+        public final int endState;
+        public final boolean isLauncher;
+        public final int containerType;
+    }
+
     public static final long MAX_SWIPE_DURATION = 350;
     public static final long MIN_SWIPE_DURATION = 80;
     public static final long MIN_OVERSHOOT_DURATION = 120;
@@ -182,12 +227,17 @@
     public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.7f;
     private static final float SWIPE_DURATION_MULTIPLIER =
             Math.min(1 / MIN_PROGRESS_FOR_OVERVIEW, 1 / (1 - MIN_PROGRESS_FOR_OVERVIEW));
+    private static final String SCREENSHOT_CAPTURED_EVT = "ScreenshotCaptured";
+
+    private static final long SHELF_ANIM_DURATION = 120;
 
     private final ClipAnimationHelper mClipAnimationHelper;
     private final ClipAnimationHelper.TransformParams mTransformParams;
 
     protected Runnable mGestureEndCallback;
     protected boolean mIsGoingToRecents;
+    protected boolean mIsGoingToHome;
+    private boolean mIsShelfPeeking;
     private DeviceProfile mDp;
     private int mTransitionDragLength;
 
@@ -321,6 +371,13 @@
                         | STATE_SCALED_CONTROLLER_RECENTS,
                 this::finishCurrentTransitionToRecents);
 
+        mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_COMPLETED
+                        | STATE_SCALED_CONTROLLER_HOME | STATE_APP_CONTROLLER_RECEIVED
+                        | STATE_ACTIVITY_MULTIPLIER_COMPLETE,
+                this::finishCurrentTransitionToHome);
+        mStateCallback.addCallback(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED,
+                this::reset);
+
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
                         | STATE_ACTIVITY_MULTIPLIER_COMPLETE | STATE_SCALED_CONTROLLER_RECENTS
                         | STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED
@@ -422,7 +479,7 @@
             mSyncTransactionApplier = applier;
         });
         mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
-            if (!mBgLongSwipeMode) {
+            if (!mBgLongSwipeMode && !mIsGoingToHome) {
                 updateFinalShift();
             }
         });
@@ -568,23 +625,37 @@
     }
 
     @WorkerThread
-    public void dispatchMotionEventToRecentsView(MotionEvent event) {
+    @SuppressWarnings("WrongThread")
+    public void dispatchMotionEventToRecentsView(MotionEvent event, @Nullable Float velocityX) {
         if (mRecentsView == null) {
             return;
         }
-        // Pass the motion events to RecentsView to allow scrolling during swipe up.
-        if (mDispatchedDownEvent) {
-            mRecentsView.dispatchTouchEvent(event);
+        if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
+            dispatchMotionEventToRecentsViewUi(event, velocityX);
         } else {
+            MotionEvent ev = MotionEvent.obtain(event);
+            postAsyncCallback(mMainThreadHandler, () -> {
+                dispatchMotionEventToRecentsViewUi(ev, velocityX);
+                ev.recycle();
+            });
+        }
+    }
+
+    @UiThread
+    private void dispatchMotionEventToRecentsViewUi(MotionEvent event, @Nullable Float velocityX) {
+        // Pass the motion events to RecentsView to allow scrolling during swipe up.
+        if (!mDispatchedDownEvent) {
             // The first event we dispatch should be ACTION_DOWN.
             mDispatchedDownEvent = true;
             MotionEvent downEvent = MotionEvent.obtain(event);
             downEvent.setAction(MotionEvent.ACTION_DOWN);
             int flags = downEvent.getEdgeFlags();
             downEvent.setEdgeFlags(flags | TouchInteractionService.EDGE_NAV_BAR);
-            mRecentsView.dispatchTouchEvent(downEvent);
+            mRecentsView.simulateTouchEvent(downEvent, velocityX);
             downEvent.recycle();
         }
+
+        mRecentsView.simulateTouchEvent(event, velocityX);
     }
 
     @WorkerThread
@@ -594,7 +665,7 @@
         if (displacement > mTransitionDragLength && mTransitionDragLength > 0) {
             mCurrentShift.updateValue(1);
 
-            if (!mBgLongSwipeMode) {
+            if (!mBgLongSwipeMode && !FeatureFlags.SWIPE_HOME.get()) {
                 mBgLongSwipeMode = true;
                 executeOnUiThread(this::onLongSwipeEnabledUi);
             }
@@ -611,6 +682,23 @@
         }
     }
 
+    public void onMotionPauseChanged(boolean isPaused) {
+        setShelfState(isPaused ? PEEK : HIDE, FAST_OUT_SLOW_IN, SHELF_ANIM_DURATION);
+    }
+
+    public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration) {
+        if (mInteractionType == INTERACTION_NORMAL) {
+            executeOnUiThread(() -> {
+                mAnimationFactory.setShelfState(shelfState, interpolator, duration);
+                mIsShelfPeeking = shelfState == PEEK;
+                if (mRecentsView != null && shelfState.shouldPreformHaptic) {
+                    mRecentsView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
+                            HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+                }
+            });
+        }
+    }
+
     /**
      * Called by {@link #mLayoutListener} when launcher layout changes
      */
@@ -672,7 +760,8 @@
         final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
         if (passed != mPassedOverviewThreshold) {
             mPassedOverviewThreshold = passed;
-            if (mInteractionType == INTERACTION_NORMAL && mRecentsView != null) {
+            if (mInteractionType == INTERACTION_NORMAL && mRecentsView != null
+                    && !SWIPE_HOME.get()) {
                 mRecentsView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
                     HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
             }
@@ -698,6 +787,9 @@
     }
 
     private void updateLauncherTransitionProgress() {
+        if (mIsGoingToHome) {
+            return;
+        }
         float progress = mCurrentShift.value;
         mLauncherTransitionController.setPlayFraction(
                 progress <= mShiftAtGestureStart || mShiftAtGestureStart >= 1
@@ -812,7 +904,7 @@
         float velocityXPxPerMs = velocityX / 1000;
         long duration = MAX_SWIPE_DURATION;
         float currentShift = mCurrentShift.value;
-        final boolean goingToRecents;
+        final GestureEndTarget endTarget;
         float endShift;
         final float startShift;
         Interpolator interpolator = DEACCEL;
@@ -821,24 +913,40 @@
         boolean goingToNewTask = mRecentsView != null && nextPage != runningTaskIndex;
         final boolean reachedOverviewThreshold = currentShift >= MIN_PROGRESS_FOR_OVERVIEW;
         if (!isFling) {
-            goingToRecents = reachedOverviewThreshold && mGestureStarted;
-            endShift = goingToRecents ? 1 : 0;
+            if (SWIPE_HOME.get()) {
+                if (mIsShelfPeeking) {
+                    endTarget = RECENTS;
+                } else if (goingToNewTask) {
+                    endTarget = NEW_TASK;
+                } else {
+                    endTarget = currentShift < MIN_PROGRESS_FOR_OVERVIEW ? LAST_TASK : HOME;
+                }
+            } else {
+                endTarget = reachedOverviewThreshold && mGestureStarted ? RECENTS : LAST_TASK;
+            }
+            endShift = endTarget.endShift;
             long expectedDuration = Math.abs(Math.round((endShift - currentShift)
                     * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
             duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
             startShift = currentShift;
-            interpolator = goingToRecents ? OVERSHOOT_1_2 : DEACCEL;
+            interpolator = endTarget == RECENTS ? OVERSHOOT_1_2 : DEACCEL;
         } else {
-            // If user scrolled to a new task, only go to recents if they already passed
-            // the overview threshold. Otherwise, we'll snap to the new task and launch it.
-            goingToRecents = endVelocity < 0 && (!goingToNewTask || reachedOverviewThreshold);
-            endShift = goingToRecents ? 1 : 0;
+            if (SWIPE_HOME.get() && endVelocity < 0 && !mIsShelfPeeking) {
+                endTarget = HOME;
+            } else if (endVelocity < 0 && (!goingToNewTask || reachedOverviewThreshold)) {
+                // If user scrolled to a new task, only go to recents if they already passed
+                // the overview threshold. Otherwise, we'll snap to the new task and launch it.
+                endTarget = RECENTS;
+            } else {
+                endTarget = goingToNewTask ? NEW_TASK : LAST_TASK;
+            }
+            endShift = endTarget.endShift;
             startShift = Utilities.boundToRange(currentShift - velocityPxPerMs
                     * SINGLE_FRAME_MS / mTransitionDragLength, 0, 1);
             float minFlingVelocity = mContext.getResources()
                     .getDimension(R.dimen.quickstep_fling_min_velocity);
             if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) {
-                if (goingToRecents) {
+                if (endTarget == RECENTS) {
                     Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams(
                             startShift, endShift, endShift, velocityPxPerMs, mTransitionDragLength);
                     endShift = overshoot.end;
@@ -856,12 +964,19 @@
                 }
             }
         }
-        if (goingToRecents) {
+
+        if (endTarget == HOME) {
+            setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
+            duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
+        } else if (endTarget == RECENTS) {
             mRecentsAnimationWrapper.enableTouchProxy();
-        } else if (goingToNewTask) {
+            if (SWIPE_HOME.get()) {
+                setShelfState(ShelfAnimState.OVERVIEW, interpolator, duration);
+            }
+        } else if (endTarget == NEW_TASK) {
             // We aren't goingToRecents, and user scrolled/flung to a new task; snap to the closest
             // task in that direction and launch it (in startNewTask()).
-            int taskToLaunch = runningTaskIndex + (nextPage > runningTaskIndex ? 1 : - 1);
+            int taskToLaunch = runningTaskIndex + (nextPage > runningTaskIndex ? 1 : -1);
             if (taskToLaunch >= mRecentsView.getTaskViewCount()) {
                 // Scrolled to Clear all button, snap back to current task and resume it.
                 mRecentsView.snapToPage(runningTaskIndex, Math.toIntExact(duration));
@@ -878,17 +993,16 @@
                 duration = Math.max(duration, durationX);
             }
         }
-
-        animateToProgress(startShift, endShift, duration, interpolator, goingToRecents,
-                goingToNewTask, velocityPxPerMs);
+        animateToProgress(startShift, endShift, duration, interpolator, endTarget, velocityPxPerMs);
     }
 
-    private void doLogGesture(boolean toLauncher) {
+    private void doLogGesture(GestureEndTarget endTarget) {
         DeviceProfile dp = mDp;
         if (dp == null) {
             // We probably never received an animation controller, skip logging.
             return;
         }
+        boolean toLauncher = endTarget.isLauncher;
         final int direction;
         if (dp.isVerticalBarLayout()) {
             direction = (dp.isSeascape() ^ toLauncher) ? Direction.LEFT : Direction.RIGHT;
@@ -896,60 +1010,92 @@
             direction = toLauncher ? Direction.UP : Direction.DOWN;
         }
 
-        int dstContainerType = toLauncher ? ContainerType.TASKSWITCHER : ContainerType.APP;
         UserEventDispatcher.newInstance(mContext).logStateChangeAction(
                 mLogAction, direction,
                 ContainerType.NAVBAR, ContainerType.APP,
-                dstContainerType,
+                endTarget.containerType,
                 0);
     }
 
     /** Animates to the given progress, where 0 is the current app and 1 is overview. */
     private void animateToProgress(float start, float end, long duration, Interpolator interpolator,
-            boolean goingToRecents, boolean goingToNewTask, float velocityPxPerMs) {
+            GestureEndTarget target, float velocityPxPerMs) {
         mRecentsAnimationWrapper.runOnInit(() -> animateToProgressInternal(start, end, duration,
-                interpolator, goingToRecents, goingToNewTask, velocityPxPerMs));
+                interpolator, target, velocityPxPerMs));
     }
 
     private void animateToProgressInternal(float start, float end, long duration,
-            Interpolator interpolator, boolean goingToRecents, boolean goingToNewTask,
-            float velocityPxPerMs) {
-        mIsGoingToRecents = goingToRecents;
-        ObjectAnimator anim = mCurrentShift.animateToValue(start, end).setDuration(duration);
-        anim.setInterpolator(interpolator);
-        anim.addListener(new AnimationSuccessListener() {
+            Interpolator interpolator, GestureEndTarget target, float velocityPxPerMs) {
+        mIsGoingToHome = target == HOME;
+        mIsGoingToRecents = target == RECENTS;
+        ActivityControlHelper.HomeAnimationFactory homeAnimFactory;
+        Animator windowAnim;
+        if (mIsGoingToHome) {
+            if (mActivity != null) {
+                homeAnimFactory = mActivityControlHelper.prepareHomeUI(mActivity);
+            } else {
+                homeAnimFactory = new ActivityControlHelper.HomeAnimationFactory() {
+                    @NonNull
+                    @Override
+                    public RectF getWindowTargetRect() {
+                        RectF fallbackTarget = new RectF(mClipAnimationHelper.getTargetRect());
+                        Utilities.scaleRectFAboutCenter(fallbackTarget, 0.25f);
+                        return fallbackTarget;
+                    }
+
+                    @NonNull
+                    @Override
+                    public Animator createActivityAnimationToHome() {
+                        return new AnimatorSet();
+                    }
+                };
+                mStateCallback.addChangeHandler(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
+                        isPresent -> mRecentsView.startHome());
+            }
+            windowAnim = createWindowAnimationToHome(start, homeAnimFactory.getWindowTargetRect());
+            mLauncherTransitionController = null;
+        } else {
+            windowAnim = mCurrentShift.animateToValue(start, end);
+            homeAnimFactory = null;
+        }
+        windowAnim.setDuration(duration).setInterpolator(interpolator);
+        windowAnim.addListener(new AnimationSuccessListener() {
             @Override
             public void onAnimationSuccess(Animator animator) {
-                int recentsState = STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
-                        | STATE_SCREENSHOT_VIEW_SHOWN;
-                setStateOnUiThread(mIsGoingToRecents
-                        ? recentsState
-                        : goingToNewTask
-                            ? STATE_START_NEW_TASK
-                            : STATE_SCALED_CONTROLLER_LAST_TASK);
+                setStateOnUiThread(target.endState);
             }
         });
-        anim.start();
+        windowAnim.start();
         long startMillis = SystemClock.uptimeMillis();
+        // Always play the entire launcher animation when going home, since it is separate from
+        // the animation that has been controlled thus far.
+        final float finalStart = mIsGoingToHome ? 0 : start;
         executeOnUiThread(() -> {
             // Animate the launcher components at the same time as the window, always on UI thread.
+            // Adjust start progress and duration in case we are on a different thread.
+            long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
+            elapsedMillis = Utilities.boundToRange(elapsedMillis, 0, duration);
+            float elapsedProgress = (float) elapsedMillis / duration;
+            float adjustedStart = Utilities.mapRange(elapsedProgress, finalStart, end);
+            long adjustedDuration = duration - elapsedMillis;
+            // We want to use the same interpolator as the window, but need to adjust it to
+            // interpolate over the remaining progress (end - start).
+            TimeInterpolator adjustedInterpolator = Interpolators.mapToProgress(
+                    interpolator, adjustedStart, end);
+            if (homeAnimFactory != null) {
+                Animator homeAnim = homeAnimFactory.createActivityAnimationToHome();
+                homeAnim.setDuration(adjustedDuration).setInterpolator(adjustedInterpolator);
+                homeAnim.start();
+                mLauncherTransitionController = null;
+            }
             if (mLauncherTransitionController == null) {
                 return;
             }
-            if (start == end || duration <= 0) {
+            if (finalStart == end || duration <= 0) {
                 mLauncherTransitionController.dispatchSetInterpolator(t -> end);
                 mLauncherTransitionController.getAnimationPlayer().end();
             } else {
-                // Adjust start progress and duration in case we are on a different thread.
-                long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
-                elapsedMillis = Utilities.boundToRange(elapsedMillis, 0, duration);
-                float elapsedProgress = (float) elapsedMillis / duration;
-                float adjustedStart = Utilities.mapRange(elapsedProgress, start, end);
-                long adjustedDuration = duration - elapsedMillis;
-                // We want to use the same interpolator as the window, but need to adjust it to
-                // interpolate over the remaining progress (end - start).
-                mLauncherTransitionController.dispatchSetInterpolator(Interpolators.mapToProgress(
-                        interpolator, adjustedStart, end));
+                mLauncherTransitionController.dispatchSetInterpolator(adjustedInterpolator);
                 mLauncherTransitionController.getAnimationPlayer().setDuration(adjustedDuration);
 
                 if (QUICKSTEP_SPRINGS.get()) {
@@ -961,10 +1107,49 @@
         });
     }
 
+    /**
+     * Creates an Animator that transforms the current app window into the home app.
+     * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
+     * @param endTarget Where to animate the window towards.
+     */
+    private Animator createWindowAnimationToHome(float startProgress, RectF endTarget) {
+        final RemoteAnimationTargetSet targetSet = mRecentsAnimationWrapper.targetSet;
+        RectF startRect = new RectF(mClipAnimationHelper.applyTransform(targetSet,
+                mTransformParams.setProgress(startProgress)));
+        RectF originalTarget = new RectF(mClipAnimationHelper.getTargetRect());
+        final RectF finalTarget = endTarget;
+
+        final RectFEvaluator rectFEvaluator = new RectFEvaluator();
+        final RectF targetRect = new RectF();
+        final RectF currentRect = new RectF();
+
+        ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+        anim.addUpdateListener(animation -> {
+            float progress = animation.getAnimatedFraction();
+            float interpolatedProgress = Interpolators.ACCEL_2.getInterpolation(progress);
+            // Initially go towards original target (task view in recents),
+            // but accelerate towards the final target.
+            // TODO: This is technically not correct. Instead, motion should continue at
+            // the released velocity but accelerate towards the target.
+            targetRect.set(rectFEvaluator.evaluate(interpolatedProgress,
+                    originalTarget, finalTarget));
+            currentRect.set(rectFEvaluator.evaluate(progress, startRect, targetRect));
+            float alpha = 1 - interpolatedProgress;
+            SyncRtSurfaceTransactionApplierCompat syncTransactionApplier
+                    = Looper.myLooper() == mMainThreadHandler.getLooper()
+                            ? mSyncTransactionApplier
+                            : null;
+            mTransformParams.setCurrentRectAndTargetAlpha(currentRect, alpha)
+                    .setSyncTransactionApplier(syncTransactionApplier);
+            mClipAnimationHelper.applyTransform(targetSet, mTransformParams);
+        });
+        return anim;
+    }
+
     @UiThread
     private void resumeLastTaskForQuickstep() {
         setStateOnUiThread(STATE_RESUME_LAST_TASK);
-        doLogGesture(false /* toLauncher */);
+        doLogGesture(LAST_TASK);
         reset();
     }
 
@@ -983,7 +1168,7 @@
                     mMainThreadHandler);
         });
         mTouchInteractionLog.finishRecentsAnimation(false);
-        doLogGesture(false /* toLauncher */);
+        doLogGesture(NEW_TASK);
     }
 
     public void reset() {
@@ -1003,10 +1188,6 @@
 
         mActivityInitListener.unregister();
         mTaskSnapshot = null;
-
-        if (mRecentsView != null) {
-            mRecentsView.setOnScrollChangeListener(null);
-        }
     }
 
     private void invalidateHandlerWithLauncher() {
@@ -1015,6 +1196,7 @@
         mActivityControlHelper.getAlphaProperty(mActivity).setValue(1);
 
         mRecentsView.setRunningTaskIconScaledDown(false);
+        mRecentsView.setOnScrollChangeListener(null);
         mQuickScrubController.cancelActiveQuickscrub();
     }
 
@@ -1078,7 +1260,9 @@
             }
             if (!finishTransitionPosted) {
                 // If we haven't posted a draw callback, set the state immediately.
+                RaceConditionTracker.onEvent(SCREENSHOT_CAPTURED_EVT, ENTER);
                 setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
+                RaceConditionTracker.onEvent(SCREENSHOT_CAPTURED_EVT, EXIT);
             }
         }
     }
@@ -1095,6 +1279,15 @@
         mTouchInteractionLog.finishRecentsAnimation(true);
     }
 
+    private void finishCurrentTransitionToHome() {
+        synchronized (mRecentsAnimationWrapper) {
+            mRecentsAnimationWrapper.finish(true /* toRecents */,
+                    () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
+        }
+        mTouchInteractionLog.finishRecentsAnimation(true);
+        doLogGesture(HOME);
+    }
+
     private void setupLauncherUiAfterSwipeUpAnimation() {
         if (mLauncherTransitionController != null) {
             mLauncherTransitionController.getAnimationPlayer().end();
@@ -1108,7 +1301,7 @@
 
         RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG);
 
-        doLogGesture(true /* toLauncher */);
+        doLogGesture(RECENTS);
         reset();
     }
 
@@ -1126,8 +1319,7 @@
         long duration = FeatureFlags.QUICK_SWITCH.get()
                 ? QUICK_SWITCH_FROM_APP_START_DURATION
                 : QUICK_SCRUB_FROM_APP_START_DURATION;
-        animateToProgress(mCurrentShift.value, 1f, duration, LINEAR, true /* goingToRecents */,
-                false /* goingToNewTask */, 1f);
+        animateToProgress(mCurrentShift.value, 1f, duration, LINEAR, RECENTS, 1f);
     }
 
     private void onQuickScrubStartUi() {
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 261f45d..9679b81 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -39,7 +39,7 @@
     }
 
     @Override
-    protected void startHome() {
+    public void startHome() {
         mActivity.startHome();
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
index 84033cb..c612b05 100644
--- a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -91,6 +91,8 @@
     private final float mWindowCornerRadius;
     // Corner radius of windows when they're in overview mode.
     private final float mTaskCornerRadius;
+    // If windows can have real time rounded corners.
+    private final boolean mSupportsRoundedCornersOnWindows;
 
     // Corner radius currently applied to transformed window.
     private float mCurrentCornerRadius;
@@ -107,8 +109,12 @@
             (t, a1) -> a1;
 
     public ClipAnimationHelper(Context context) {
-        mTaskCornerRadius = context.getResources().getDimension(R.dimen.task_corner_radius);
-        mWindowCornerRadius  = RecentsModel.INSTANCE.get(context).getWindowCornerRadius();
+        mWindowCornerRadius = RecentsModel.INSTANCE.get(context).getWindowCornerRadius();
+        mSupportsRoundedCornersOnWindows = RecentsModel.INSTANCE.get(context)
+                .supportsRoundedCornersOnWindows();
+        int taskCornerRadiusRes = mSupportsRoundedCornersOnWindows ?
+                R.dimen.task_corner_radius : R.dimen.task_corner_radius_small;
+        mTaskCornerRadius = context.getResources().getDimension(taskCornerRadiusRes);
     }
 
     private void updateSourceStack(RemoteAnimationTargetCompat target) {
@@ -197,9 +203,10 @@
                     mTmpMatrix.setRectToRect(mSourceRect, params.currentRect, ScaleToFit.FILL);
                     mTmpMatrix.postTranslate(app.position.x, app.position.y);
                     mClipRectF.roundOut(crop);
-                    cornerRadius = Utilities.mapRange(params.progress, mWindowCornerRadius,
-                            mTaskCornerRadius);
-                    mCurrentCornerRadius = cornerRadius;
+                    if (mSupportsRoundedCornersOnWindows) {
+                        cornerRadius = Utilities.mapRange(params.progress, mWindowCornerRadius,
+                                mTaskCornerRadius);
+                    }
                 }
                 alpha = mTaskAlphaCallback.apply(app, params.targetAlpha);
             } else if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
@@ -357,8 +364,8 @@
 
     public static class TransformParams {
         float progress;
-        float offsetX;
-        float offsetScale;
+        public float offsetX;
+        public float offsetScale;
         @Nullable RectF currentRect;
         float targetAlpha;
         boolean forLiveTile;
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
new file mode 100644
index 0000000..7969eec
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.SystemClock;
+import android.view.MotionEvent;
+
+import com.android.launcher3.R;
+
+/**
+ * Given positions along x- or y-axis, tracks velocity and acceleration and determines when there is
+ * a pause in motion.
+ */
+public class MotionPauseDetector {
+
+    // The percentage of the previous speed that determines whether this is a rapid deceleration.
+    // The bigger this number, the easier it is to trigger the first pause.
+    private static final float RAPID_DECELERATION_FACTOR = 0.6f;
+
+    private final float mSpeedVerySlow;
+    private final float mSpeedSomewhatFast;
+    private final float mSpeedFast;
+    private final float mMinDisplacementForPause;
+
+    private Long mPreviousTime = null;
+    private Float mPreviousPosition = null;
+    private Float mPreviousVelocity = null;
+
+    private Float mFirstPosition = null;
+
+    private OnMotionPauseListener mOnMotionPauseListener;
+    private boolean mIsPaused;
+    // Bias more for the first pause to make it feel extra responsive.
+    private boolean mHasEverBeenPaused;
+
+    public MotionPauseDetector(Context context) {
+        Resources res = context.getResources();
+        mSpeedVerySlow = res.getDimension(R.dimen.motion_pause_detector_speed_very_slow);
+        mSpeedSomewhatFast = res.getDimension(R.dimen.motion_pause_detector_speed_somewhat_fast);
+        mSpeedFast = res.getDimension(R.dimen.motion_pause_detector_speed_fast);
+        mMinDisplacementForPause = res.getDimension(R.dimen.motion_pause_detector_min_displacement);
+    }
+
+    /**
+     * Get callbacks for when motion pauses and resumes, including an
+     * immediate callback with the current pause state.
+     */
+    public void setOnMotionPauseListener(OnMotionPauseListener listener) {
+        mOnMotionPauseListener = listener;
+        if (mOnMotionPauseListener != null) {
+            mOnMotionPauseListener.onMotionPauseChanged(mIsPaused);
+        }
+    }
+
+    /**
+     * Computes velocity and acceleration to determine whether the motion is paused.
+     * @param position The x or y component of the motion being tracked.
+     *
+     * TODO: Use historical positions as well, e.g. {@link MotionEvent#getHistoricalY(int, int)}.
+     */
+    public void addPosition(float position) {
+        if (mFirstPosition == null) {
+            mFirstPosition = position;
+        }
+        long time = SystemClock.uptimeMillis();
+        if (mPreviousTime != null && mPreviousPosition != null) {
+            long changeInTime = Math.max(1, time - mPreviousTime);
+            float changeInPosition = position - mPreviousPosition;
+            float velocity = changeInPosition / changeInTime;
+            if (mPreviousVelocity != null) {
+                checkMotionPaused(velocity, mPreviousVelocity, Math.abs(position - mFirstPosition));
+            }
+            mPreviousVelocity = velocity;
+        }
+        mPreviousTime = time;
+        mPreviousPosition = position;
+    }
+
+    private void checkMotionPaused(float velocity, float prevVelocity, float totalDisplacement) {
+        float speed = Math.abs(velocity);
+        float previousSpeed = Math.abs(prevVelocity);
+        boolean isPaused;
+        if (mIsPaused) {
+            // Continue to be paused until moving at a fast speed.
+            isPaused = speed < mSpeedFast || previousSpeed < mSpeedFast;
+        } else {
+            if (velocity < 0 != prevVelocity < 0) {
+                // We're just changing directions, not necessarily stopping.
+                isPaused = false;
+            } else {
+                isPaused = speed < mSpeedVerySlow && previousSpeed < mSpeedVerySlow;
+                if (!isPaused && !mHasEverBeenPaused) {
+                    // We want to be more aggressive about detecting the first pause to ensure it
+                    // feels as responsive as possible; getting two very slow speeds back to back
+                    // takes too long, so also check for a rapid deceleration.
+                    boolean isRapidDeceleration = speed < previousSpeed * RAPID_DECELERATION_FACTOR;
+                    isPaused = isRapidDeceleration && speed < mSpeedSomewhatFast;
+                }
+            }
+        }
+        boolean passedMinDisplacement = totalDisplacement >= mMinDisplacementForPause;
+        isPaused &= passedMinDisplacement;
+        if (mIsPaused != isPaused) {
+            mIsPaused = isPaused;
+            if (mIsPaused) {
+                mHasEverBeenPaused = true;
+            }
+            if (mOnMotionPauseListener != null) {
+                mOnMotionPauseListener.onMotionPauseChanged(mIsPaused);
+            }
+        }
+    }
+
+    public void clear() {
+        mPreviousTime = null;
+        mPreviousPosition = null;
+        mPreviousVelocity = null;
+        mFirstPosition = null;
+        setOnMotionPauseListener(null);
+        mIsPaused = mHasEverBeenPaused = false;
+    }
+
+    public boolean isPaused() {
+        return mIsPaused;
+    }
+
+    public interface OnMotionPauseListener {
+        void onMotionPauseChanged(boolean isPaused);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index cc10009..9ad750b 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -20,14 +20,18 @@
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.LauncherApps;
 import android.content.res.Resources;
 import android.icu.text.MeasureFormat;
 import android.icu.text.MeasureFormat.FormatWidth;
 import android.icu.util.Measure;
 import android.icu.util.MeasureUnit;
+import android.os.UserHandle;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import androidx.annotation.StringRes;
@@ -38,11 +42,11 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.systemui.shared.recents.model.Task;
 
+import java.lang.reflect.Method;
 import java.time.Duration;
 import java.util.Locale;
 
-public final class DigitalWellBeingToast extends TextView {
-
+public final class DigitalWellBeingToast extends LinearLayout {
     public interface InitializeCallback {
         void call(float saturation, String contentDescription);
     }
@@ -50,33 +54,68 @@
     private static final String TAG = DigitalWellBeingToast.class.getSimpleName();
 
     private Task mTask;
+    private ImageView mImage;
+    private TextView mText;
 
     public DigitalWellBeingToast(Context context, AttributeSet attrs) {
         super(context, attrs);
         setLayoutDirection(Utilities.isRtl(getResources()) ?
                 View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
         setOnClickListener((view) -> openAppUsageSettings());
+    }
 
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mText = findViewById(R.id.digital_well_being_remaining_time);
+        mImage = findViewById(R.id.digital_well_being_hourglass);
     }
 
     public void initialize(Task task, InitializeCallback callback) {
         mTask = task;
         Utilities.THREAD_POOL_EXECUTOR.execute(() -> {
-            final long appUsageLimitTimeMs = -1;
-            final long appRemainingTimeMs = -1;
-            final boolean isGroupLimit = true;
+            long appUsageLimitTimeMs = -1;
+            long appRemainingTimeMs = -1;
+
+            try {
+                final Method getAppUsageLimit = LauncherApps.class.getMethod(
+                        "getAppUsageLimit",
+                        String.class,
+                        UserHandle.class);
+                final Object usageLimit = getAppUsageLimit.invoke(
+                        getContext().getSystemService(LauncherApps.class),
+                        task.getTopComponent().getPackageName(),
+                        UserHandle.of(task.key.userId));
+
+                if (usageLimit != null) {
+                    final Class appUsageLimitClass = usageLimit.getClass();
+                    appUsageLimitTimeMs = (long) appUsageLimitClass.getMethod("getTotalUsageLimit").
+                            invoke(usageLimit);
+                    appRemainingTimeMs = (long) appUsageLimitClass.getMethod("getUsageRemaining").
+                            invoke(usageLimit);
+                }
+            } catch (Exception e) {
+                // Do nothing
+            }
+
+            final long appUsageLimitTimeMsFinal = appUsageLimitTimeMs;
+            final long appRemainingTimeMsFinal = appRemainingTimeMs;
+
             post(() -> {
-                if (appUsageLimitTimeMs < 0) {
+                if (appUsageLimitTimeMsFinal < 0) {
                     setVisibility(GONE);
                 } else {
                     setVisibility(VISIBLE);
-                    setText(getText(appRemainingTimeMs, isGroupLimit));
+                    mText.setText(getText(appRemainingTimeMsFinal));
+                    mImage.setImageResource(appRemainingTimeMsFinal > 0 ?
+                            R.drawable.hourglass_top : R.drawable.hourglass_bottom);
                 }
 
                 callback.call(
-                        appUsageLimitTimeMs >= 0 && appRemainingTimeMs < 0 ? 0 : 1,
+                        appUsageLimitTimeMsFinal >= 0 && appRemainingTimeMsFinal <= 0 ? 0 : 1,
                         getContentDescriptionForTask(
-                                task, appUsageLimitTimeMs, appRemainingTimeMs, isGroupLimit));
+                                task, appUsageLimitTimeMsFinal, appRemainingTimeMsFinal));
             });
         });
     }
@@ -140,12 +179,12 @@
                 duration, FormatWidth.NARROW, R.string.shorter_duration_less_than_one_minute);
     }
 
-    private String getText(long remainingTime, boolean isGroupLimit) {
+    private String getText(long remainingTime) {
         final Resources resources = getResources();
-        return (remainingTime < 0) ?
+        return (remainingTime <= 0) ?
                 resources.getString(R.string.app_in_grayscale) :
                 resources.getString(
-                        isGroupLimit ? R.string.time_left_for_group : R.string.time_left_for_app,
+                        R.string.time_left_for_app,
                         getShorterReadableDuration(Duration.ofMillis(remainingTime)));
     }
 
@@ -169,12 +208,12 @@
     }
 
     private String getContentDescriptionForTask(
-            Task task, long appUsageLimitTimeMs, long appRemainingTimeMs, boolean isGroupLimit) {
-        return appUsageLimitTimeMs > 0 ?
+            Task task, long appUsageLimitTimeMs, long appRemainingTimeMs) {
+        return appUsageLimitTimeMs >= 0 ?
                 getResources().getString(
                         R.string.task_contents_description_with_remaining_time,
                         task.titleDescription,
-                        getText(appRemainingTimeMs, isGroupLimit)) :
+                        getText(appRemainingTimeMs)) :
                 task.titleDescription;
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 7389d65..ff85003 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -16,9 +16,9 @@
 package com.android.quickstep.views;
 
 import static com.android.launcher3.AbstractFloatingView.TYPE_QUICKSTEP_PREVIEW;
-import static com.android.launcher3.LauncherAppTransitionManagerImpl.ALL_APPS_PROGRESS_OFF_SCREEN;
 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.QuickstepAppTransitionManagerImpl.ALL_APPS_PROGRESS_OFF_SCREEN;
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 
@@ -67,6 +67,10 @@
                 }
             };
 
+    /**
+     * A ratio representing the view's relative placement within its padded space. For example, 0
+     * is top aligned and 0.5 is centered vertically.
+     */
     @ViewDebug.ExportedProperty(category = "launcher")
     private float mTranslationYFactor;
 
@@ -88,7 +92,7 @@
     }
 
     @Override
-    protected void startHome() {
+    public void startHome() {
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             takeScreenshotAndFinishRecentsAnimation(true,
                     () -> mActivity.getStateManager().goToState(NORMAL));
@@ -183,7 +187,7 @@
 
     @Override
     public boolean shouldUseMultiWindowTaskSizeStrategy() {
-        return mActivity.isInMultiWindowModeCompat();
+        return mActivity.isInMultiWindowMode();
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 5cbae65..c6f293d 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.uioverrides.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS;
 import static com.android.quickstep.util.ClipAnimationHelper.TransformParams;
@@ -66,16 +67,19 @@
 import android.widget.ListView;
 
 import androidx.annotation.Nullable;
+import androidx.dynamicanimation.animation.SpringForce;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAnimUtils.ViewProgressProperty;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PropertyListBuilder;
+import com.android.launcher3.anim.SpringObjectAnimator;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -113,6 +117,10 @@
 
     private static final String TAG = RecentsView.class.getSimpleName();
 
+    public static final float SPRING_MIN_VISIBLE_CHANGE = 0.001f;
+    public static final float SPRING_DAMPING_RATIO = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY;
+    public static final float SPRING_STIFFNESS = SpringForce.STIFFNESS_LOW;
+
     public static final FloatProperty<RecentsView> CONTENT_ALPHA =
             new FloatProperty<RecentsView>("contentAlpha") {
                 @Override
@@ -159,6 +167,8 @@
 
     private final ViewPool<TaskView> mTaskViewPool;
 
+    @Nullable Float mSimulatedVelocityX = null;
+
     /**
      * TODO: Call reloadIdNeeded in onTaskStackChanged.
      */
@@ -719,7 +729,7 @@
         }
     }
 
-    protected abstract void startHome();
+    public abstract void startHome();
 
     public void reset() {
         setRunningTaskViewShowScreenshot(false);
@@ -939,8 +949,15 @@
 
     private void addDismissedTaskAnimations(View taskView, AnimatorSet anim, long duration) {
         addAnim(ObjectAnimator.ofFloat(taskView, ALPHA, 0), duration, ACCEL_2, anim);
-        addAnim(ObjectAnimator.ofFloat(taskView, TRANSLATION_Y, -taskView.getHeight()),
-                duration, LINEAR, anim);
+        if (QUICKSTEP_SPRINGS.get() && taskView instanceof TaskView)
+            addAnim(new SpringObjectAnimator<>(new ViewProgressProperty(taskView,
+                            View.TRANSLATION_Y), "taskViewTransY", SPRING_MIN_VISIBLE_CHANGE,
+                            SPRING_DAMPING_RATIO, SPRING_STIFFNESS, 0, -taskView.getHeight()),
+                    duration, LINEAR, anim);
+        else {
+            addAnim(ObjectAnimator.ofFloat(taskView, TRANSLATION_Y, -taskView.getHeight()),
+                    duration, LINEAR, anim);
+        }
     }
 
     private void removeTask(Task task, int index, PendingAnimation.OnEndListener onEndListener,
@@ -1010,8 +1027,16 @@
                 }
                 int scrollDiff = newScroll[i] - oldScroll[i] + offset;
                 if (scrollDiff != 0) {
-                    addAnim(ObjectAnimator.ofFloat(child, TRANSLATION_X, scrollDiff),
-                            duration, ACCEL, anim);
+                    if (QUICKSTEP_SPRINGS.get() && child instanceof TaskView) {
+                        addAnim(new SpringObjectAnimator<>(
+                                new ViewProgressProperty(child, View.TRANSLATION_X),
+                                "taskViewTransX", SPRING_MIN_VISIBLE_CHANGE, SPRING_DAMPING_RATIO,
+                                SPRING_STIFFNESS, 0, scrollDiff), duration, ACCEL, anim);
+                    } else {
+                        addAnim(ObjectAnimator.ofFloat(child, TRANSLATION_X, scrollDiff), duration,
+                                ACCEL, anim);
+                    }
+
                     needsCurveUpdates = true;
                 }
             }
@@ -1092,7 +1117,7 @@
         return pendingAnimation;
     }
 
-    private static void addAnim(ObjectAnimator anim, long duration,
+    private static void addAnim(Animator anim, long duration,
             TimeInterpolator interpolator, AnimatorSet set) {
         anim.setDuration(duration).setInterpolator(interpolator);
         set.play(anim);
@@ -1610,4 +1635,18 @@
             }
         }
     }
+
+    public void simulateTouchEvent(MotionEvent event, @Nullable Float velocityX) {
+        mSimulatedVelocityX = velocityX;
+        dispatchTouchEvent(event);
+        mSimulatedVelocityX = null;
+    }
+
+    @Override
+    protected int computeXVelocity() {
+        if (mSimulatedVelocityX != null) {
+            return mSimulatedVelocityX.intValue();
+        }
+        return super.computeXVelocity();
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
index d2b3bcc..fe05c4f 100644
--- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
+++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
@@ -15,6 +15,7 @@
  */
 package com.android.quickstep.views;
 
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -33,6 +34,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ScrimView;
 
@@ -135,6 +137,11 @@
         if (mProgress >= 1) {
             mRemainingScreenColor = 0;
             mShelfColor = 0;
+            if (FeatureFlags.SWIPE_HOME.get()
+                    && mLauncher.getStateManager().getState() == BACKGROUND_APP) {
+                // Show the shelf background when peeking during swipe up.
+                mShelfColor = setColorAlphaBound(mEndScrim, mMidAlpha);
+            }
         } else if (mProgress >= mMidProgress) {
             mRemainingScreenColor = 0;
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index 8169d73..d0289d0 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -291,7 +291,7 @@
                 // Rotate the screenshot if not in multi-window mode
                 mIsRotated = FeatureFlags.OVERVIEW_USE_SCREENSHOT_ORIENTATION &&
                         configuration.orientation != mThumbnailData.orientation &&
-                        !mActivity.isInMultiWindowModeCompat() &&
+                        !mActivity.isInMultiWindowMode() &&
                         mThumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN;
                 // Scale the screenshot to always fit the width of the card.
                 thumbnailScale = mIsRotated
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTests.java b/quickstep/tests/src/com/android/quickstep/TaplTests.java
deleted file mode 100644
index 6a1123e..0000000
--- a/quickstep/tests/src/com/android/quickstep/TaplTests.java
+++ /dev/null
@@ -1,462 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep;
-
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Intent;
-import android.os.RemoteException;
-import android.util.Log;
-
-import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.Until;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.tapl.AllApps;
-import com.android.launcher3.tapl.AllAppsFromOverview;
-import com.android.launcher3.tapl.AppIcon;
-import com.android.launcher3.tapl.Background;
-import com.android.launcher3.tapl.Overview;
-import com.android.launcher3.tapl.OverviewTask;
-import com.android.launcher3.tapl.TestHelpers;
-import com.android.launcher3.tapl.Widgets;
-import com.android.launcher3.tapl.Workspace;
-import com.android.launcher3.views.OptionsPopupView;
-import com.android.launcher3.widget.WidgetsFullSheet;
-import com.android.launcher3.widget.WidgetsRecyclerView;
-import com.android.quickstep.QuickStepOnOffRule.QuickstepOnOff;
-import com.android.quickstep.views.RecentsView;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestWatcher;
-import org.junit.runner.Description;
-import org.junit.runner.RunWith;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class TaplTests extends AbstractQuickStepTest {
-    private static final String TAG = "TaplTests";
-
-    private static int sScreenshotCount = 0;
-
-    @Rule
-    public TestWatcher mFailureWatcher = new TestWatcher() {
-        private void dumpViewHierarchy() {
-            final ByteArrayOutputStream stream = new ByteArrayOutputStream();
-            try {
-                mDevice.dumpWindowHierarchy(stream);
-                stream.flush();
-                stream.close();
-                for (String line : stream.toString().split("\\r?\\n")) {
-                    Log.e(TaplTests.TAG, line.trim());
-                }
-            } catch (IOException e) {
-                Log.e(TaplTests.TAG, "error dumping XML to logcat", e);
-            }
-        }
-
-        @Override
-        protected void failed(Throwable e, Description description) {
-            if (mDevice == null) return;
-            final String pathname = getInstrumentation().getTargetContext().
-                    getFilesDir().getPath() + "/TaplTestScreenshot" + sScreenshotCount++ + ".png";
-            Log.e(TaplTests.TAG, "Failed test " + description.getMethodName() +
-                    ", screenshot will be saved to " + pathname +
-                    ", track trace is below, UI object dump is further below:\n" +
-                    Log.getStackTraceString(e));
-            dumpViewHierarchy();
-            mDevice.takeScreenshot(new File(pathname));
-        }
-    };
-
-    @Before
-    public void setUp() throws Exception {
-        super.setUp();
-
-        clearLauncherData();
-
-        mDevice.pressHome();
-        waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL);
-        waitForResumed("Launcher internal state is still Background");
-    }
-
-    private boolean isInState(LauncherState state) {
-        if (!TestHelpers.isInLauncherProcess()) return true;
-        return getFromLauncher(launcher -> launcher.getStateManager().getState() == state);
-    }
-
-    // Please don't add negative test cases for methods that fail only after a long wait.
-    private void expectFail(String message, Runnable action) {
-        boolean failed = false;
-        try {
-            action.run();
-        } catch (AssertionError e) {
-            failed = true;
-        }
-        assertTrue(message, failed);
-    }
-
-    private boolean isWorkspaceScrollable(Launcher launcher) {
-        return launcher.getWorkspace().getPageCount() > 1;
-    }
-
-    private boolean isInBackground(Launcher launcher) {
-        return !launcher.hasBeenResumed();
-    }
-
-    private void startTestApps() throws Exception {
-        startAppFast(resolveSystemApp(Intent.CATEGORY_APP_MESSAGING));
-        startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
-        startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CONTACTS));
-
-        executeOnLauncher(launcher -> assertTrue(
-                "Launcher activity is the top activity; expecting another activity to be the top "
-                        + "one",
-                isInBackground(launcher)));
-    }
-
-    private int getCurrentWorkspacePage(Launcher launcher) {
-        return launcher.getWorkspace().getCurrentPage();
-    }
-
-    private WidgetsRecyclerView getWidgetsView(Launcher launcher) {
-        return WidgetsFullSheet.getWidgetsView(launcher);
-    }
-
-    @Test
-    public void testDevicePressMenu() throws Exception {
-        mDevice.pressMenu();
-        mDevice.waitForIdle();
-        executeOnLauncher(
-                launcher -> assertTrue("Launcher internal state didn't switch to Showing Menu",
-                        OptionsPopupView.getOptionsPopup(launcher) != null));
-    }
-
-    @Test
-    @PortraitLandscape
-    public void testPressRecentAppsLauncherAndGetOverview() throws RemoteException {
-        mDevice.pressRecentApps();
-        waitForState("Launcher internal state didn't switch to Overview", LauncherState.OVERVIEW);
-
-        assertNotNull("getOverview() returned null", mLauncher.getOverview());
-    }
-
-    private void runAllAppsTest(AllApps allApps) throws Exception {
-        assertNotNull("allApps parameter is null", allApps);
-
-        assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS));
-
-        // Test flinging forward and backward.
-        executeOnLauncher(launcher -> assertEquals("All Apps started in already scrolled state", 0,
-                getAllAppsScroll(launcher)));
-
-        allApps.flingForward();
-        assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS));
-        final Integer flingForwardY = getFromLauncher(launcher -> getAllAppsScroll(launcher));
-        executeOnLauncher(
-                launcher -> assertTrue("flingForward() didn't scroll App Apps", flingForwardY > 0));
-
-        allApps.flingBackward();
-        assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS));
-        final Integer flingBackwardY = getFromLauncher(launcher -> getAllAppsScroll(launcher));
-        executeOnLauncher(launcher -> assertTrue("flingBackward() didn't scroll App Apps",
-                flingBackwardY < flingForwardY));
-
-        // Test scrolling down to YouTube.
-        assertNotNull("All apps: can't fine YouTube", allApps.getAppIcon("YouTube"));
-        // Test scrolling up to Camera.
-        assertNotNull("All apps: can't fine Camera", allApps.getAppIcon("Camera"));
-        // Test failing to find a non-existing app.
-        final AllApps allAppsFinal = allApps;
-        expectFail("All apps: could find a non-existing app",
-                () -> allAppsFinal.getAppIcon("NO APP"));
-
-        assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS));
-    }
-
-    private int getAllAppsScroll(Launcher launcher) {
-        return launcher.getAppsView().getActiveRecyclerView().getCurrentScrollY();
-    }
-
-    @Test
-    @PortraitLandscape
-    public void testAllAppsFromHome() throws Exception {
-        // Test opening all apps
-        assertNotNull("switchToAllApps() returned null",
-                mLauncher.getWorkspace().switchToAllApps());
-
-        runAllAppsTest(mLauncher.getAllApps());
-
-        // Testing pressHome.
-        assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS));
-        assertNotNull("pressHome returned null", mLauncher.pressHome());
-        assertTrue("Launcher internal state is not Home", isInState(LauncherState.NORMAL));
-        assertNotNull("getHome returned null", mLauncher.getWorkspace());
-    }
-
-    @Test
-    @QuickstepOnOff
-    @PortraitLandscape
-    public void testWorkspaceSwitchToAllApps() {
-        assertNotNull("switchToAllApps() returned null",
-                mLauncher.getWorkspace().switchToAllApps());
-        assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS));
-    }
-
-    @Test
-    public void testAllAppsFromOverview() throws Exception {
-        // Test opening all apps from Overview.
-        assertNotNull("switchToAllApps() returned null",
-                mLauncher.getWorkspace().switchToOverview().switchToAllApps());
-
-        runAllAppsTest(mLauncher.getAllAppsFromOverview());
-    }
-
-    @Test
-    public void testWorkspace() throws Exception {
-        final Workspace workspace = mLauncher.getWorkspace();
-
-        // Test that ensureWorkspaceIsScrollable adds a page by dragging an icon there.
-        executeOnLauncher(launcher -> assertFalse("Initial workspace state is scrollable",
-                isWorkspaceScrollable(launcher)));
-        assertNull("Messages app was found on empty workspace",
-                workspace.tryGetWorkspaceAppIcon("Messages"));
-
-        workspace.ensureWorkspaceIsScrollable();
-
-        executeOnLauncher(
-                launcher -> assertEquals("Ensuring workspace scrollable didn't switch to page #1",
-                        1, getCurrentWorkspacePage(launcher)));
-        executeOnLauncher(
-                launcher -> assertTrue("ensureScrollable didn't make workspace scrollable",
-                        isWorkspaceScrollable(launcher)));
-        assertNotNull("ensureScrollable didn't add Messages app",
-                workspace.tryGetWorkspaceAppIcon("Messages"));
-
-        // Test flinging workspace.
-        workspace.flingBackward();
-        assertTrue("Launcher internal state is not Home", isInState(LauncherState.NORMAL));
-        executeOnLauncher(
-                launcher -> assertEquals("Flinging back didn't switch workspace to page #0",
-                        0, getCurrentWorkspacePage(launcher)));
-
-        workspace.flingForward();
-        executeOnLauncher(
-                launcher -> assertEquals("Flinging forward didn't switch workspace to page #1",
-                        1, getCurrentWorkspacePage(launcher)));
-        assertTrue("Launcher internal state is not Home", isInState(LauncherState.NORMAL));
-
-        // Test starting a workspace app.
-        final AppIcon app = workspace.tryGetWorkspaceAppIcon("Messages");
-        assertNotNull("No Messages app in workspace", app);
-        assertNotNull("AppIcon.launch returned null",
-                app.launch(resolveSystemApp(Intent.CATEGORY_APP_MESSAGING)));
-        executeOnLauncher(launcher -> assertTrue(
-                "Launcher activity is the top activity; expecting another activity to be the top "
-                        + "one",
-                isInBackground(launcher)));
-    }
-
-    @Test
-    @PortraitLandscape
-    public void testOverview() throws Exception {
-        startTestApps();
-        Overview overview = mLauncher.pressHome().switchToOverview();
-        assertTrue("Launcher internal state didn't switch to Overview",
-                isInState(LauncherState.OVERVIEW));
-        executeOnLauncher(
-                launcher -> assertTrue("Don't have at least 3 tasks", getTaskCount(launcher) >= 3));
-
-        // Test flinging forward and backward.
-        executeOnLauncher(launcher -> assertEquals("Current task in Overview is not 0",
-                0, getCurrentOverviewPage(launcher)));
-
-        overview.flingForward();
-        assertTrue("Launcher internal state is not Overview", isInState(LauncherState.OVERVIEW));
-        final Integer currentTaskAfterFlingForward = getFromLauncher(
-                launcher -> getCurrentOverviewPage(launcher));
-        executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0",
-                currentTaskAfterFlingForward > 0));
-
-        overview.flingBackward();
-        assertTrue("Launcher internal state is not Overview", isInState(LauncherState.OVERVIEW));
-        executeOnLauncher(launcher -> assertTrue("Flinging back in Overview did nothing",
-                getCurrentOverviewPage(launcher) < currentTaskAfterFlingForward));
-
-        // Test opening a task.
-        OverviewTask task = mLauncher.pressHome().switchToOverview().getCurrentTask();
-        assertNotNull("overview.getCurrentTask() returned null (1)", task);
-        assertNotNull("OverviewTask.open returned null", task.open());
-        assertTrue("Contacts app didn't open from Overview", mDevice.wait(Until.hasObject(
-                By.pkg(resolveSystemApp(Intent.CATEGORY_APP_CONTACTS)).depth(0)),
-                LONG_WAIT_TIME_MS));
-        executeOnLauncher(launcher -> assertTrue(
-                "Launcher activity is the top activity; expecting another activity to be the top "
-                        + "one",
-                isInBackground(launcher)));
-
-        // Test dismissing a task.
-        overview = mLauncher.pressHome().switchToOverview();
-        assertTrue("Launcher internal state didn't switch to Overview",
-                isInState(LauncherState.OVERVIEW));
-        final Integer numTasks = getFromLauncher(launcher -> getTaskCount(launcher));
-        task = overview.getCurrentTask();
-        assertNotNull("overview.getCurrentTask() returned null (2)", task);
-        task.dismiss();
-        executeOnLauncher(
-                launcher -> assertEquals("Dismissing a task didn't remove 1 task from Overview",
-                        numTasks - 1, getTaskCount(launcher)));
-
-        if (!TestHelpers.isInLauncherProcess() ||
-                getFromLauncher(launcher -> !launcher.getDeviceProfile().isLandscape)) {
-            // Test switching to all apps and back.
-            final AllAppsFromOverview allApps = overview.switchToAllApps();
-            assertNotNull("overview.switchToAllApps() returned null (1)", allApps);
-            assertTrue("Launcher internal state is not All Apps (1)",
-                    isInState(LauncherState.ALL_APPS));
-
-            overview = allApps.switchBackToOverview();
-            assertNotNull("allApps.switchBackToOverview() returned null", overview);
-            assertTrue("Launcher internal state didn't switch to Overview",
-                    isInState(LauncherState.OVERVIEW));
-
-            // Test UIDevice.pressBack()
-            overview.switchToAllApps();
-            assertNotNull("overview.switchToAllApps() returned null (2)", allApps);
-            assertTrue("Launcher internal state is not All Apps (2)",
-                    isInState(LauncherState.ALL_APPS));
-            mDevice.pressBack();
-            mLauncher.getOverview();
-        }
-
-        // Test UIDevice.pressHome, once we are in AllApps.
-        mDevice.pressHome();
-        waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL);
-    }
-
-    private int getCurrentOverviewPage(Launcher launcher) {
-        return launcher.<RecentsView>getOverviewPanel().getCurrentPage();
-    }
-
-    private int getTaskCount(Launcher launcher) {
-        return launcher.<RecentsView>getOverviewPanel().getTaskViewCount();
-    }
-
-    private void runIconLaunchFromAllAppsTest(AllApps allApps) throws Exception {
-        final AppIcon app = allApps.getAppIcon("Calculator");
-        assertNotNull("AppIcon.launch returned null", app.launch(
-                resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)));
-        executeOnLauncher(launcher -> assertTrue(
-                "Launcher activity is the top activity; expecting another activity to be the top "
-                        + "one",
-                isInBackground(launcher)));
-    }
-
-    @Test
-    @PortraitLandscape
-    public void testAppIconLaunchFromAllAppsFromHome() throws Exception {
-        final AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
-        assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS));
-
-        runIconLaunchFromAllAppsTest(allApps);
-    }
-
-    @Test
-    public void testAppIconLaunchFromAllAppsFromOverview() throws Exception {
-        final AllApps allApps =
-                mLauncher.getWorkspace().switchToOverview().switchToAllApps();
-        assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS));
-
-        runIconLaunchFromAllAppsTest(allApps);
-    }
-
-    @Test
-    @PortraitLandscape
-    public void testWidgets() throws Exception {
-        // Test opening widgets.
-        executeOnLauncher(launcher ->
-                assertTrue("Widgets is initially opened", getWidgetsView(launcher) == null));
-        Widgets widgets = mLauncher.getWorkspace().openAllWidgets();
-        assertNotNull("openAllWidgets() returned null", widgets);
-        widgets = mLauncher.getAllWidgets();
-        assertNotNull("getAllWidgets() returned null", widgets);
-        executeOnLauncher(launcher ->
-                assertTrue("Widgets is not shown", getWidgetsView(launcher).isShown()));
-        executeOnLauncher(launcher -> assertEquals("Widgets is scrolled upon opening",
-                0, getWidgetsScroll(launcher)));
-
-        // Test flinging widgets.
-        widgets.flingForward();
-        Integer flingForwardY = getFromLauncher(launcher -> getWidgetsScroll(launcher));
-        executeOnLauncher(launcher -> assertTrue("Flinging forward didn't scroll widgets",
-                flingForwardY > 0));
-
-        widgets.flingBackward();
-        executeOnLauncher(launcher -> assertTrue("Flinging backward didn't scroll widgets",
-                getWidgetsScroll(launcher) < flingForwardY));
-
-        mDevice.pressHome();
-        waitForLauncherCondition("Widgets were not closed",
-                launcher -> getWidgetsView(launcher) == null);
-    }
-
-    private int getWidgetsScroll(Launcher launcher) {
-        return getWidgetsView(launcher).getCurrentScrollY();
-    }
-
-    @Test
-    @QuickstepOnOff
-    @PortraitLandscape
-    public void testSwitchToOverview() throws Exception {
-        assertNotNull("Workspace.switchToOverview() returned null",
-                mLauncher.pressHome().switchToOverview());
-        assertTrue("Launcher internal state didn't switch to Overview",
-                isInState(LauncherState.OVERVIEW));
-    }
-
-    @Test
-    @QuickstepOnOff
-    @PortraitLandscape
-    public void testBackground() throws Exception {
-        startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
-        final Background background = mLauncher.getBackground();
-        assertNotNull("Launcher.getBackground() returned null", background);
-        executeOnLauncher(launcher -> assertTrue(
-                "Launcher activity is the top activity; expecting another activity to be the top "
-                        + "one",
-                isInBackground(launcher)));
-
-        assertNotNull("Background.switchToOverview() returned null", background.switchToOverview());
-        assertTrue("Launcher internal state didn't switch to Overview",
-                isInState(LauncherState.OVERVIEW));
-    }
-}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
new file mode 100644
index 0000000..e9d8bce
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Intent;
+import android.os.RemoteException;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.Until;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.tapl.AllApps;
+import com.android.launcher3.tapl.AllAppsFromOverview;
+import com.android.launcher3.tapl.Background;
+import com.android.launcher3.tapl.Overview;
+import com.android.launcher3.tapl.OverviewTask;
+import com.android.launcher3.tapl.TestHelpers;
+import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.quickstep.QuickStepOnOffRule.QuickstepOnOff;
+import com.android.quickstep.views.RecentsView;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.RunWith;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class TaplTestsQuickstep extends AbstractQuickStepTest {
+    @Rule
+    public TestWatcher mFailureWatcher = new TaplTestsLauncher3.FailureWatcher(mDevice);
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        clearLauncherData();
+
+        mLauncher.pressHome();
+        waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL);
+        waitForResumed("Launcher internal state is still Background");
+    }
+
+    private void startTestApps() throws Exception {
+        startAppFast(resolveSystemApp(Intent.CATEGORY_APP_MESSAGING));
+        startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+        startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CONTACTS));
+
+        executeOnLauncher(launcher -> assertTrue(
+                "Launcher activity is the top activity; expecting another activity to be the top "
+                        + "one",
+                isInBackground(launcher)));
+    }
+
+    @Test
+    @PortraitLandscape
+    public void testPressRecentAppsLauncherAndGetOverview() throws RemoteException {
+        mDevice.pressRecentApps();
+        waitForState("Launcher internal state didn't switch to Overview", LauncherState.OVERVIEW);
+
+        assertNotNull("getOverview() returned null", mLauncher.getOverview());
+    }
+
+    @Test
+    @QuickstepOnOff
+    @PortraitLandscape
+    public void testWorkspaceSwitchToAllApps() {
+        assertNotNull("switchToAllApps() returned null",
+                mLauncher.getWorkspace().switchToAllApps());
+        assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS));
+    }
+
+    @Test
+    public void testAllAppsFromOverview() throws Exception {
+        // Test opening all apps from Overview.
+        assertNotNull("switchToAllApps() returned null",
+                mLauncher.getWorkspace().switchToOverview().switchToAllApps());
+
+        TaplTestsLauncher3.runAllAppsTest(this, mLauncher.getAllAppsFromOverview());
+    }
+
+    @Test
+    @PortraitLandscape
+    public void testOverview() throws Exception {
+        startTestApps();
+        Overview overview = mLauncher.pressHome().switchToOverview();
+        assertTrue("Launcher internal state didn't switch to Overview",
+                isInState(LauncherState.OVERVIEW));
+        executeOnLauncher(
+                launcher -> assertTrue("Don't have at least 3 tasks", getTaskCount(launcher) >= 3));
+
+        // Test flinging forward and backward.
+        executeOnLauncher(launcher -> assertEquals("Current task in Overview is not 0",
+                0, getCurrentOverviewPage(launcher)));
+
+        overview.flingForward();
+        assertTrue("Launcher internal state is not Overview", isInState(LauncherState.OVERVIEW));
+        final Integer currentTaskAfterFlingForward = getFromLauncher(
+                launcher -> getCurrentOverviewPage(launcher));
+        executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0",
+                currentTaskAfterFlingForward > 0));
+
+        overview.flingBackward();
+        assertTrue("Launcher internal state is not Overview", isInState(LauncherState.OVERVIEW));
+        executeOnLauncher(launcher -> assertTrue("Flinging back in Overview did nothing",
+                getCurrentOverviewPage(launcher) < currentTaskAfterFlingForward));
+
+        // Test opening a task.
+        OverviewTask task = mLauncher.pressHome().switchToOverview().getCurrentTask();
+        assertNotNull("overview.getCurrentTask() returned null (1)", task);
+        assertNotNull("OverviewTask.open returned null", task.open());
+        assertTrue("Contacts app didn't open from Overview", mDevice.wait(Until.hasObject(
+                By.pkg(resolveSystemApp(Intent.CATEGORY_APP_CONTACTS)).depth(0)),
+                LONG_WAIT_TIME_MS));
+        executeOnLauncher(launcher -> assertTrue(
+                "Launcher activity is the top activity; expecting another activity to be the top "
+                        + "one",
+                isInBackground(launcher)));
+
+        // Test dismissing a task.
+        overview = mLauncher.pressHome().switchToOverview();
+        assertTrue("Launcher internal state didn't switch to Overview",
+                isInState(LauncherState.OVERVIEW));
+        final Integer numTasks = getFromLauncher(launcher -> getTaskCount(launcher));
+        task = overview.getCurrentTask();
+        assertNotNull("overview.getCurrentTask() returned null (2)", task);
+        task.dismiss();
+        executeOnLauncher(
+                launcher -> assertEquals("Dismissing a task didn't remove 1 task from Overview",
+                        numTasks - 1, getTaskCount(launcher)));
+
+        if (!TestHelpers.isInLauncherProcess() ||
+                getFromLauncher(launcher -> !launcher.getDeviceProfile().isLandscape)) {
+            // Test switching to all apps and back.
+            final AllAppsFromOverview allApps = overview.switchToAllApps();
+            assertNotNull("overview.switchToAllApps() returned null (1)", allApps);
+            assertTrue("Launcher internal state is not All Apps (1)",
+                    isInState(LauncherState.ALL_APPS));
+
+            overview = allApps.switchBackToOverview();
+            assertNotNull("allApps.switchBackToOverview() returned null", overview);
+            assertTrue("Launcher internal state didn't switch to Overview",
+                    isInState(LauncherState.OVERVIEW));
+
+            // Test UIDevice.pressBack()
+            overview.switchToAllApps();
+            assertNotNull("overview.switchToAllApps() returned null (2)", allApps);
+            assertTrue("Launcher internal state is not All Apps (2)",
+                    isInState(LauncherState.ALL_APPS));
+            mDevice.pressBack();
+            mLauncher.getOverview();
+        }
+
+        // Test UIDevice.pressHome, once we are in AllApps.
+        mDevice.pressHome();
+        waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL);
+
+        // Test dismissing all tasks.
+        mLauncher.getWorkspace().switchToOverview().dismissAllTasks();
+        waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL);
+        executeOnLauncher(
+                launcher -> assertEquals("Still have tasks after dismissing all",
+                        0, getTaskCount(launcher)));
+    }
+
+    private int getCurrentOverviewPage(Launcher launcher) {
+        return launcher.<RecentsView>getOverviewPanel().getCurrentPage();
+    }
+
+    private int getTaskCount(Launcher launcher) {
+        return launcher.<RecentsView>getOverviewPanel().getTaskViewCount();
+    }
+
+    @Test
+    public void testAppIconLaunchFromAllAppsFromOverview() throws Exception {
+        final AllApps allApps =
+                mLauncher.getWorkspace().switchToOverview().switchToAllApps();
+        assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS));
+
+        TaplTestsLauncher3.runIconLaunchFromAllAppsTest(this, allApps);
+    }
+
+    @Test
+    @QuickstepOnOff
+    @PortraitLandscape
+    public void testSwitchToOverview() throws Exception {
+        assertNotNull("Workspace.switchToOverview() returned null",
+                mLauncher.pressHome().switchToOverview());
+        assertTrue("Launcher internal state didn't switch to Overview",
+                isInState(LauncherState.OVERVIEW));
+    }
+
+    @Test
+    @QuickstepOnOff
+    @PortraitLandscape
+    public void testBackground() throws Exception {
+        startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+        final Background background = mLauncher.getBackground();
+        assertNotNull("Launcher.getBackground() returned null", background);
+        executeOnLauncher(launcher -> assertTrue(
+                "Launcher activity is the top activity; expecting another activity to be the top "
+                        + "one",
+                isInBackground(launcher)));
+
+        assertNotNull("Background.switchToOverview() returned null", background.switchToOverview());
+        assertTrue("Launcher internal state didn't switch to Overview",
+                isInState(LauncherState.OVERVIEW));
+    }
+
+    @Test
+    @PortraitLandscape
+    public void testAllAppsFromHome() throws Exception {
+        // Test opening all apps
+        assertNotNull("switchToAllApps() returned null",
+                mLauncher.getWorkspace().switchToAllApps());
+
+        TaplTestsLauncher3.runAllAppsTest(this, mLauncher.getAllApps());
+
+        // Testing pressHome.
+        assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS));
+        assertNotNull("pressHome returned null", mLauncher.pressHome());
+        assertTrue("Launcher internal state is not Home", isInState(LauncherState.NORMAL));
+        assertNotNull("getHome returned null", mLauncher.getWorkspace());
+    }
+}
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 08abb4b..49d5f56 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -33,7 +33,7 @@
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"ダブルタップ後に押し続けてウィジェットを選択するか、カスタム操作を使用してください。"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$dx%2$d"</string>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"幅 %1$d、高さ %2$d"</string>
-    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"押し続けると、手動で追加できます"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"長押しすると、手動で追加できます"</string>
     <string name="place_automatically" msgid="8064208734425456485">"自動的に追加"</string>
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"アプリを検索"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"アプリを読み込んでいます…"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 9f7e503..7bdf9c4 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -33,7 +33,7 @@
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Miniaplikáciu pridáte dvojitým klepnutím a pridržaním alebo pomocou vlastných akcií."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"šírka %1$d, výška %2$d"</string>
-    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Položku umiestnite ručne klepnutím a podržaním"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Ak chcete položku umiestniť ručne, pridržte ju"</string>
     <string name="place_automatically" msgid="8064208734425456485">"Pridať automaticky"</string>
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Hľadať aplikácie"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Načítavajú sa aplikácie…"</string>
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 1b953d4..7f72242 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -17,6 +17,7 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
+
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.app.Activity;
@@ -25,7 +26,6 @@
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.view.ContextThemeWrapper;
-import android.view.View.AccessibilityDelegate;
 
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.logging.StatsLogManager;
@@ -125,10 +125,6 @@
         return mUserEventDispatcher;
     }
 
-    public boolean isInMultiWindowModeCompat() {
-        return Utilities.ATLEAST_NOUGAT && isInMultiWindowMode();
-    }
-
     public SystemUiController getSystemUiController() {
         if (mSystemUiController == null) {
             mSystemUiController = new SystemUiController(getWindow());
@@ -226,7 +222,7 @@
     /**
      * Used to set the override visibility state, used only to handle the transition home with the
      * recents animation.
-     * @see LauncherAppTransitionManagerImpl#getWallpaperOpenRunner()
+     * @see QuickstepAppTransitionManagerImpl#getWallpaperOpenRunner()
      */
     public void addForceInvisibleFlag(@InvisibilityFlags int flag) {
         mForceInvisible |= flag;
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 12d443b..5b9b172 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -174,8 +174,7 @@
             intent.setSourceBounds(getViewBounds(v));
         }
         try {
-            boolean isShortcut = Utilities.ATLEAST_MARSHMALLOW
-                    && (item instanceof ShortcutInfo)
+            boolean isShortcut = (item instanceof ShortcutInfo)
                     && (item.itemType == Favorites.ITEM_TYPE_SHORTCUT
                     || item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
                     && !((ShortcutInfo) item).isPromise();
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index dafd5bb..6582df2 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -36,6 +36,7 @@
 import android.view.Display;
 import android.view.WindowManager;
 
+import com.android.launcher3.folder.FolderShape;
 import com.android.launcher3.util.ConfigMonitor;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.MainThreadInitializedObject;
@@ -285,6 +286,10 @@
                 !iconShapePath.equals(oldProfile.iconShapePath)) {
             changeFlags |= CHANGE_FLAG_ICON_PARAMS;
         }
+        if (!iconShapePath.equals(oldProfile.iconShapePath)) {
+            FolderShape.init(context);
+        }
+
         apply(context, changeFlags);
     }
 
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 177e780..fc3af7e 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -74,8 +74,6 @@
 import android.view.animation.OvershootInterpolator;
 import android.widget.Toast;
 
-import androidx.annotation.Nullable;
-
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.allapps.AllAppsContainerView;
@@ -149,6 +147,8 @@
 import java.util.List;
 import java.util.Set;
 
+import androidx.annotation.Nullable;
+
 /**
  * Default launcher application.
  */
@@ -190,6 +190,8 @@
     // Type: SparseArray<Parcelable>
     private static final String RUNTIME_STATE_WIDGET_PANEL = "launcher.widget_panel";
     public static final String ON_CREATE_EVT = "Launcher.onCreate";
+    private static final String ON_START_EVT = "Launcher.onStart";
+    private static final String ON_RESUME_EVT = "Launcher.onResume";
 
     private LauncherStateManager mStateManager;
 
@@ -415,7 +417,7 @@
     private void initDeviceProfile(InvariantDeviceProfile idp) {
         // Load configuration-specific DeviceProfile
         mDeviceProfile = idp.getDeviceProfile(this);
-        if (isInMultiWindowModeCompat()) {
+        if (isInMultiWindowMode()) {
             Display display = getWindowManager().getDefaultDisplay();
             Point mwSize = new Point();
             display.getSize(mwSize);
@@ -770,12 +772,14 @@
 
     @Override
     protected void onStart() {
+        RaceConditionTracker.onEvent(ON_START_EVT, ENTER);
         super.onStart();
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onStart();
         }
         mAppWidgetHost.setListenIfResumed(true);
         NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
+        RaceConditionTracker.onEvent(ON_START_EVT, EXIT);
     }
 
     private void logOnDelayedResume() {
@@ -788,6 +792,7 @@
 
     @Override
     protected void onResume() {
+        RaceConditionTracker.onEvent(ON_RESUME_EVT, ENTER);
         TraceHelper.beginSection("ON_RESUME");
         super.onResume();
         TraceHelper.partitionSection("ON_RESUME", "superCall");
@@ -810,6 +815,7 @@
         UiFactory.onLauncherStateOrResumeChanged(this);
 
         TraceHelper.endSection("ON_RESUME");
+        RaceConditionTracker.onEvent(ON_RESUME_EVT, EXIT);
     }
 
     @Override
@@ -2308,7 +2314,6 @@
     }
 
     @Override
-    @TargetApi(Build.VERSION_CODES.N)
     public void onProvideKeyboardShortcuts(
             List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
 
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index aad3449..04f2b52 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -91,4 +91,24 @@
                     lp.height = height;
                 }
             };
+
+    public static class ViewProgressProperty implements ProgressInterface {
+        View mView;
+        Property<View, Float> mProperty;
+
+        public ViewProgressProperty(View view, Property<View, Float> property) {
+            mView = view;
+            mProperty = property;
+        }
+
+        @Override
+        public void setProgress(float progress) {
+            mProperty.set(mView, progress);
+        }
+
+        @Override
+        public float getProgress() {
+            return mProperty.get(mView);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/LauncherAppTransitionManager.java b/src/com/android/launcher3/LauncherAppTransitionManager.java
index 970e558..fb50dfb 100644
--- a/src/com/android/launcher3/LauncherAppTransitionManager.java
+++ b/src/com/android/launcher3/LauncherAppTransitionManager.java
@@ -36,28 +36,19 @@
     }
 
     public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) {
-        if (Utilities.ATLEAST_MARSHMALLOW) {
-            int left = 0, top = 0;
-            int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
-            if (v instanceof BubbleTextView) {
-                // Launch from center of icon, not entire view
-                Drawable icon = ((BubbleTextView) v).getIcon();
-                if (icon != null) {
-                    Rect bounds = icon.getBounds();
-                    left = (width - bounds.width()) / 2;
-                    top = v.getPaddingTop();
-                    width = bounds.width();
-                    height = bounds.height();
-                }
+        int left = 0, top = 0;
+        int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
+        if (v instanceof BubbleTextView) {
+            // Launch from center of icon, not entire view
+            Drawable icon = ((BubbleTextView) v).getIcon();
+            if (icon != null) {
+                Rect bounds = icon.getBounds();
+                left = (width - bounds.width()) / 2;
+                top = v.getPaddingTop();
+                width = bounds.width();
+                height = bounds.height();
             }
-            return ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);
-        } else if (Utilities.ATLEAST_LOLLIPOP_MR1) {
-            // On L devices, we use the device default slide-up transition.
-            // On L MR1 devices, we use a custom version of the slide-up transition which
-            // doesn't have the delay present in the device default.
-            return ActivityOptions.makeCustomAnimation(launcher, R.anim.task_open_enter,
-                    R.anim.no_anim);
         }
-        return null;
+        return ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);
     }
 }
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
index 56671a1..7f5ac52 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -26,7 +26,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.Handler;
-import android.util.Log;
 import android.util.SparseArray;
 import android.view.LayoutInflater;
 import android.widget.Toast;
@@ -135,9 +134,6 @@
      * @see #setResumed(boolean)
      */
     public void setListenIfResumed(boolean listenIfResumed) {
-        if (!Utilities.ATLEAST_NOUGAT_MR1) {
-            return;
-        }
         if (listenIfResumed == ((mFlags & FLAG_LISTEN_IF_RESUMED) != 0)) {
             return;
         }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index ebca2ea..5ab6eea 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -54,7 +54,6 @@
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.Provider;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.ViewOnDrawExecutor;
 import com.android.launcher3.widget.WidgetListRowEntry;
@@ -68,6 +67,7 @@
 import java.util.List;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.Executor;
+import java.util.function.Supplier;
 
 import androidx.annotation.Nullable;
 
@@ -208,11 +208,6 @@
 
     static void checkItemInfoLocked(
             final int itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
-        if (com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
-                && com.android.launcher3.Utilities.IS_DEBUG_DEVICE) {
-            android.util.Log.d("b/117332845",
-                    "Checking item: " + android.util.Log.getStackTraceString(new Throwable()));
-        }
         ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId);
         if (modelItem != null && item != modelItem) {
             // check all the data is consistent
@@ -408,11 +403,6 @@
      * @return true if the page could be bound synchronously.
      */
     public boolean startLoader(int synchronousBindPage) {
-        if (com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
-                && com.android.launcher3.Utilities.IS_DEBUG_DEVICE) {
-            android.util.Log.d("b/117332845",
-                    android.util.Log.getStackTraceString(new Throwable()));
-        }
         // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
         InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_LOADER_RUNNING);
         synchronized (mLock) {
@@ -512,11 +502,6 @@
             synchronized (mLock) {
                 // Everything loaded bind the data.
                 mModelLoaded = true;
-                if (com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
-                        && com.android.launcher3.Utilities.IS_DEBUG_DEVICE) {
-                    android.util.Log.d("b/117332845",
-                            android.util.Log.getStackTraceString(new Throwable()));
-                }
             }
         }
 
@@ -542,10 +527,8 @@
      * use partial updates similar to {@link UserManagerCompat}
      */
     public void refreshShortcutsIfRequired() {
-        if (Utilities.ATLEAST_NOUGAT_MR1) {
-            sWorker.removeCallbacks(mShortcutPermissionCheckRunnable);
-            sWorker.post(mShortcutPermissionCheckRunnable);
-        }
+        sWorker.removeCallbacks(mShortcutPermissionCheckRunnable);
+        sWorker.post(mShortcutPermissionCheckRunnable);
     }
 
     /**
@@ -611,7 +594,7 @@
     /**
      * Utility method to update a shortcut on the background thread.
      */
-    public void updateAndBindShortcutInfo(final Provider<ShortcutInfo> shortcutProvider) {
+    public void updateAndBindShortcutInfo(final Supplier<ShortcutInfo> shortcutProvider) {
         enqueueModelUpdateTask(new BaseModelUpdateTask() {
             @Override
             public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 24dc17a..fb33694 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -189,7 +189,7 @@
     }
 
     private void reloadLauncherIfExternal() {
-        if (Utilities.ATLEAST_MARSHMALLOW && Binder.getCallingPid() != Process.myPid()) {
+        if (Binder.getCallingPid() != Process.myPid()) {
             LauncherAppState app = LauncherAppState.getInstanceNoCreate();
             if (app != null) {
                 app.getModel().forceReload();
@@ -217,21 +217,7 @@
 
         uri = ContentUris.withAppendedId(uri, rowId);
         notifyListeners();
-
-        if (Utilities.ATLEAST_MARSHMALLOW) {
-            reloadLauncherIfExternal();
-        } else {
-            // Deprecated behavior to support legacy devices which rely on provider callbacks.
-            LauncherAppState app = LauncherAppState.getInstanceNoCreate();
-            if (app != null && "true".equals(uri.getQueryParameter("isExternalAdd"))) {
-                app.getModel().forceReload();
-            }
-
-            String notify = uri.getQueryParameter("notify");
-            if (notify == null || "true".equals(notify)) {
-                getContext().getContentResolver().notifyChange(uri, null);
-            }
-        }
+        reloadLauncherIfExternal();
         return uri;
     }
 
@@ -301,7 +287,7 @@
             throws OperationApplicationException {
         createDbIfNotExists();
         try (SQLiteTransaction t = new SQLiteTransaction(mOpenHelper.getWritableDatabase())) {
-            boolean isAddOrDelete = !Utilities.ATLEAST_MARSHMALLOW;
+            boolean isAddOrDelete = false;
 
             final int numOperations = operations.size();
             final ContentProviderResult[] results = new ContentProviderResult[numOperations];
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index 3cf6d62..9f6e5cd 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -51,7 +51,7 @@
     protected boolean fitSystemWindows(Rect insets) {
         mConsumedInsets.setEmpty();
         boolean drawInsetBar = false;
-        if (mLauncher.isInMultiWindowModeCompat()
+        if (mLauncher.isInMultiWindowMode()
                 && (insets.left > 0 || insets.right > 0 || insets.bottom > 0)) {
             mConsumedInsets.left = insets.left;
             mConsumedInsets.right = insets.right;
@@ -59,8 +59,7 @@
             insets = new Rect(0, insets.top, 0, 0);
             drawInsetBar = true;
         } else  if ((insets.right > 0 || insets.left > 0) &&
-                (!Utilities.ATLEAST_MARSHMALLOW ||
-                        getContext().getSystemService(ActivityManager.class).isLowRamDevice())) {
+                getContext().getSystemService(ActivityManager.class).isLowRamDevice()) {
             mConsumedInsets.left = insets.left;
             mConsumedInsets.right = insets.right;
             insets = new Rect(0, insets.top, 0, insets.bottom);
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 5d3ff53..df8ac99 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -233,9 +233,6 @@
                 handler.setState(state);
             }
 
-            for (int i = mListeners.size() - 1; i >= 0; i--) {
-                mListeners.get(i).onStateSetImmediately(state);
-            }
             onStateTransitionEnd(state);
 
             // Run any queued runnable
@@ -368,9 +365,6 @@
             public void onAnimationStart(Animator animation) {
                 // Change the internal state only when the transition actually starts
                 onStateTransitionStart(state);
-                for (int i = mListeners.size() - 1; i >= 0; i--) {
-                    mListeners.get(i).onStateTransitionStart(state);
-                }
             }
 
             @Override
@@ -380,9 +374,6 @@
                     onCompleteRunnable.run();
                 }
                 onStateTransitionEnd(state);
-                for (int i = mListeners.size() - 1; i >= 0; i--) {
-                    mListeners.get(i).onStateTransitionComplete(state);
-                }
             }
         });
         mConfig.setAnimation(animation, state);
@@ -402,6 +393,10 @@
             mLauncher.getWorkspace().setClipChildren(false);
         }
         UiFactory.onLauncherStateOrResumeChanged(mLauncher);
+
+        for (int i = mListeners.size() - 1; i >= 0; i--) {
+            mListeners.get(i).onStateTransitionStart(state);
+        }
     }
 
     private void onStateTransitionEnd(LauncherState state) {
@@ -420,6 +415,10 @@
         }
 
         UiFactory.onLauncherStateOrResumeChanged(mLauncher);
+
+        for (int i = mListeners.size() - 1; i >= 0; i--) {
+            mListeners.get(i).onStateTransitionComplete(state);
+        }
     }
 
     public void onWindowFocusChanged() {
@@ -598,11 +597,6 @@
 
     public interface StateListener {
 
-        /**
-         * Called when the state is set without an animation.
-         */
-        void onStateSetImmediately(LauncherState state);
-
         void onStateTransitionStart(LauncherState toState);
         void onStateTransitionComplete(LauncherState finalState);
     }
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 9470635..8f9e7c8 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -18,6 +18,7 @@
 
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
 import static com.android.launcher3.touch.OverScroll.OVERSCROLL_DAMP_FACTOR;
 
 import android.animation.LayoutTransition;
@@ -25,6 +26,7 @@
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.provider.Settings;
@@ -121,6 +123,8 @@
 
     protected boolean mIsPageInTransition = false;
 
+    protected float mSpringOverScrollX;
+
     protected boolean mWasInOverscroll = false;
 
     protected int mUnboundedScrollX;
@@ -349,6 +353,11 @@
 
         boolean isXBeforeFirstPage = mIsRtl ? (x > mMaxScrollX) : (x < 0);
         boolean isXAfterLastPage = mIsRtl ? (x < 0) : (x > mMaxScrollX);
+
+        if (!isXBeforeFirstPage && !isXAfterLastPage) {
+            mSpringOverScrollX = 0;
+        }
+
         if (isXBeforeFirstPage) {
             super.scrollTo(mIsRtl ? mMaxScrollX : 0, y);
             if (mAllowOverScroll) {
@@ -988,12 +997,35 @@
         }
     }
 
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        if (mScroller.isSpringing() && mSpringOverScrollX != 0) {
+            int saveCount = canvas.save();
+
+            canvas.translate(-mSpringOverScrollX, 0);
+            super.dispatchDraw(canvas);
+
+            canvas.restoreToCount(saveCount);
+        } else {
+            super.dispatchDraw(canvas);
+        }
+    }
+
     protected void dampedOverScroll(int amount) {
-        if (amount == 0) return;
+        mSpringOverScrollX = amount;
+        if (amount == 0) {
+            return;
+        }
 
         int overScrollAmount = OverScroll.dampedScroll(amount, getMeasuredWidth());
+        mSpringOverScrollX = overScrollAmount;
+        if (mScroller.isSpringing()) {
+            invalidate();
+            return;
+        }
+
         if (amount < 0) {
-            super.scrollTo(overScrollAmount, getScrollY());
+            super.scrollTo(amount, getScrollY());
         } else {
             super.scrollTo(mMaxScrollX + overScrollAmount, getScrollY());
         }
@@ -1001,6 +1033,12 @@
     }
 
     protected void overScroll(int amount) {
+        mSpringOverScrollX = amount;
+        if (mScroller.isSpringing()) {
+            invalidate();
+            return;
+        }
+
         if (amount == 0) return;
 
         if (mFreeScroll && !mScroller.isFinished()) {
@@ -1096,9 +1134,7 @@
                 final int activePointerId = mActivePointerId;
                 final int pointerIndex = ev.findPointerIndex(activePointerId);
                 final float x = ev.getX(pointerIndex);
-                final VelocityTracker velocityTracker = mVelocityTracker;
-                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
-                int velocityX = (int) velocityTracker.getXVelocity(activePointerId);
+                int velocityX = computeXVelocity();
                 final int deltaX = (int) (x - mDownMotionX);
                 final int pageWidth = getPageAt(mCurrentPage).getMeasuredWidth();
                 boolean isSignificantMove = Math.abs(deltaX) > pageWidth *
@@ -1202,6 +1238,12 @@
         return true;
     }
 
+    protected int computeXVelocity() {
+        final VelocityTracker velocityTracker = mVelocityTracker;
+        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+        return (int) velocityTracker.getXVelocity(mActivePointerId);
+    }
+
     protected boolean shouldFlingForVelocity(int velocityX) {
         return Math.abs(velocityX) > mFlingThresholdVelocity;
     }
@@ -1372,7 +1414,12 @@
         // interpolator at zero, ie. 5. We use 4 to make it a little slower.
         duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
 
-        return snapToPage(whichPage, delta, duration);
+        if (QUICKSTEP_SPRINGS.get()) {
+            return snapToPage(whichPage, delta, duration, false, null,
+                    velocity * Math.signum(newX - getUnboundedScrollX()), true);
+        } else {
+            return snapToPage(whichPage, delta, duration);
+        }
     }
 
     public boolean snapToPage(int whichPage) {
@@ -1397,15 +1444,15 @@
 
         int newX = getScrollForPage(whichPage);
         final int delta = newX - getUnboundedScrollX();
-        return snapToPage(whichPage, delta, duration, immediate, interpolator);
+        return snapToPage(whichPage, delta, duration, immediate, interpolator, 0, false);
     }
 
     protected boolean snapToPage(int whichPage, int delta, int duration) {
-        return snapToPage(whichPage, delta, duration, false, null);
+        return snapToPage(whichPage, delta, duration, false, null, 0, false);
     }
 
     protected boolean snapToPage(int whichPage, int delta, int duration, boolean immediate,
-            TimeInterpolator interpolator) {
+            TimeInterpolator interpolator, float velocity, boolean spring) {
         if (mFirstLayout) {
             setCurrentPage(whichPage);
             return false;
@@ -1441,7 +1488,11 @@
             mScroller.setInterpolator(mDefaultInterpolator);
         }
 
-        mScroller.startScroll(getUnboundedScrollX(), delta, duration);
+        if (spring && QUICKSTEP_SPRINGS.get()) {
+            mScroller.startScrollSpring(getUnboundedScrollX(), delta, duration, velocity);
+        } else {
+            mScroller.startScroll(getUnboundedScrollX(), delta, duration);
+        }
 
         updatePageIndicator();
 
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index c2c3287..e53d59c 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -16,11 +16,9 @@
 
 package com.android.launcher3;
 
-import android.annotation.TargetApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.os.Build;
 import android.text.TextUtils;
 
 import com.android.launcher3.LauncherSettings.Favorites;
@@ -111,7 +109,6 @@
     /**
      * Creates a {@link ShortcutInfo} from a {@link ShortcutInfoCompat}.
      */
-    @TargetApi(Build.VERSION_CODES.N)
     public ShortcutInfo(ShortcutInfoCompat shortcutInfo, Context context) {
         user = shortcutInfo.getUserHandle();
         itemType = Favorites.ITEM_TYPE_DEEP_SHORTCUT;
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index a0f005c..c847120 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -28,7 +28,6 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
-import android.graphics.Bitmap;
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Point;
@@ -55,7 +54,6 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.IntArray;
 
-import java.io.ByteArrayOutputStream;
 import java.io.Closeable;
 import java.io.IOException;
 import java.lang.reflect.Method;
@@ -98,18 +96,6 @@
     public static final boolean ATLEAST_OREO =
             Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
 
-    public static final boolean ATLEAST_NOUGAT_MR1 =
-            Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1;
-
-    public static final boolean ATLEAST_NOUGAT =
-            Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
-
-    public static final boolean ATLEAST_MARSHMALLOW =
-            Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
-
-    public static final boolean ATLEAST_LOLLIPOP_MR1 =
-            Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1;
-
     public static final int SINGLE_FRAME_MS = 16;
 
     /**
@@ -123,9 +109,6 @@
     // An intent extra to indicate the horizontal scroll of the wallpaper.
     public static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET";
 
-    public static final int COLOR_EXTRACTION_JOB_ID = 1;
-    public static final int WALLPAPER_COMPAT_JOB_ID = 2;
-
     // These values are same as that in {@link AsyncTask}.
     private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
     private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
@@ -512,19 +495,11 @@
             // Battery saver mode no longer prevents animations.
             return false;
         }
-        PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-        return powerManager.isPowerSaveMode();
+        return context.getSystemService(PowerManager.class).isPowerSaveMode();
     }
 
     public static boolean isWallpaperAllowed(Context context) {
-        if (ATLEAST_NOUGAT) {
-            try {
-                WallpaperManager wm = context.getSystemService(WallpaperManager.class);
-                return (Boolean) wm.getClass().getDeclaredMethod("isSetWallpaperAllowed")
-                        .invoke(wm);
-            } catch (Exception e) { }
-        }
-        return true;
+        return context.getSystemService(WallpaperManager.class).isSetWallpaperAllowed();
     }
 
     public static void closeSilently(Closeable c) {
@@ -602,8 +577,4 @@
     public static String getPointString(int x, int y) {
         return String.format(Locale.ENGLISH, "%d,%d", x, y);
     }
-
-    public interface Consumer<T> {
-        void accept(T var1);
-    }
 }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index eb26961..3438a26 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -996,7 +996,7 @@
 
     @Override
     protected void overScroll(int amount) {
-        boolean shouldScrollOverlay = mLauncherOverlay != null &&
+        boolean shouldScrollOverlay = mLauncherOverlay != null && !mScroller.isSpringing() &&
                 ((amount <= 0 && !mIsRtl) || (amount >= 0 && mIsRtl));
 
         boolean shouldZeroOverlay = mLauncherOverlay != null && mLastOverlayScroll != 0 &&
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index cf0f2a3..52d7d28 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -30,6 +30,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Set;
+import java.util.function.Consumer;
 
 /**
  * A utility class to maintain the collection of all apps.
@@ -140,7 +141,7 @@
         });
     }
 
-    private void updateAllIcons(IconAction action) {
+    private void updateAllIcons(Consumer<BubbleTextView> action) {
         for (int i = mIconContainers.size() - 1; i >= 0; i--) {
             ViewGroup parent = mIconContainers.get(i);
             int childCount = parent.getChildCount();
@@ -148,7 +149,7 @@
             for (int j = 0; j < childCount; j++) {
                 View child = parent.getChildAt(j);
                 if (child instanceof BubbleTextView) {
-                    action.apply((BubbleTextView) child);
+                    action.accept((BubbleTextView) child);
                 }
             }
         }
@@ -157,8 +158,4 @@
     public interface OnUpdateListener {
         void onAppsUpdated();
     }
-
-    public interface IconAction {
-        void apply(BubbleTextView icon);
-    }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index e8e93fe..bcb5eec 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -47,6 +47,9 @@
 public class AllAppsTransitionController implements StateHandler, OnDeviceProfileChangeListener,
         ProgressInterface {
 
+    public static final float SPRING_DAMPING_RATIO = 0.9f;
+    public static final float SPRING_STIFFNESS = 600f;
+
     public static final Property<AllAppsTransitionController, Float> ALL_APPS_PROGRESS =
             new Property<AllAppsTransitionController, Float>(Float.class, "allAppsProgress") {
 
@@ -61,19 +64,6 @@
         }
     };
 
-    public static final FloatPropertyCompat<AllAppsTransitionController> ALL_APPS_PROGRESS_SPRING
-            = new FloatPropertyCompat<AllAppsTransitionController>("allAppsProgressSpring") {
-        @Override
-        public float getValue(AllAppsTransitionController controller) {
-            return controller.mProgress;
-        }
-
-        @Override
-        public void setValue(AllAppsTransitionController controller, float progress) {
-            controller.setProgress(progress);
-        }
-    };
-
     private AllAppsContainerView mAppsView;
     private ScrimView mScrimView;
 
@@ -191,8 +181,8 @@
         Interpolator interpolator = config.userControlled ? LINEAR : toState == OVERVIEW
                 ? builder.getInterpolator(ANIM_OVERVIEW_SCALE, FAST_OUT_SLOW_IN)
                 : FAST_OUT_SLOW_IN;
-        Animator anim = new SpringObjectAnimator<>(this, ALL_APPS_PROGRESS_SPRING,
-                "allAppsSpringFromAATC", 1f / mShiftRange, mProgress, targetProgress);
+        Animator anim = new SpringObjectAnimator<>(this, "allAppsSpringFromAATC", 1f / mShiftRange,
+                SPRING_DAMPING_RATIO, SPRING_STIFFNESS, mProgress, targetProgress);
         anim.setDuration(config.duration);
         anim.setInterpolator(builder.getInterpolator(ANIM_VERTICAL_PROGRESS, interpolator));
         anim.addListener(getProgressAnimatorListener());
diff --git a/src/com/android/launcher3/allapps/DiscoveryBounce.java b/src/com/android/launcher3/allapps/DiscoveryBounce.java
index 76b2565..7467119 100644
--- a/src/com/android/launcher3/allapps/DiscoveryBounce.java
+++ b/src/com/android/launcher3/allapps/DiscoveryBounce.java
@@ -25,6 +25,7 @@
 import android.animation.AnimatorInflater;
 import android.animation.AnimatorListenerAdapter;
 import android.app.ActivityManager;
+import android.content.SharedPreferences;
 import android.os.Handler;
 import android.view.MotionEvent;
 
@@ -43,6 +44,10 @@
 
     public static final String HOME_BOUNCE_SEEN = "launcher.apps_view_shown";
     public static final String SHELF_BOUNCE_SEEN = "launcher.shelf_bounce_seen";
+    public static final String HOME_BOUNCE_COUNT = "launcher.home_bounce_count";
+    public static final String SHELF_BOUNCE_COUNT = "launcher.shelf_bounce_count";
+
+    public static final int BOUNCE_MAX_COUNT = 3;
 
     private final Launcher mLauncher;
     private final Animator mDiscoBounceAnimation;
@@ -137,6 +142,7 @@
             new Handler().postDelayed(() -> showForHomeIfNeeded(launcher, false), DELAY_MS);
             return;
         }
+        incrementHomeBounceCount(launcher);
 
         new DiscoveryBounce(launcher, 0).show(HOTSEAT);
     }
@@ -165,6 +171,7 @@
             // TODO: Move these checks to the top and call this method after invalidate handler.
             return;
         }
+        incrementShelfBounceCount(launcher);
 
         new DiscoveryBounce(launcher, (1 - OVERVIEW.getVerticalProgress(launcher)))
                 .show(PREDICTION);
@@ -197,4 +204,22 @@
                 PersonalWorkSlidingTabStrip.KEY_SHOWED_PEEK_WORK_TAB, false)
                 && UserManagerCompat.getInstance(launcher).hasWorkProfile();
     }
+
+    private static void incrementShelfBounceCount(Launcher launcher) {
+        SharedPreferences sharedPrefs = launcher.getSharedPrefs();
+        int count = sharedPrefs.getInt(SHELF_BOUNCE_COUNT, 0);
+        if (count > BOUNCE_MAX_COUNT) {
+            return;
+        }
+        sharedPrefs.edit().putInt(SHELF_BOUNCE_COUNT, count + 1).apply();
+    }
+
+    private static void incrementHomeBounceCount(Launcher launcher) {
+        SharedPreferences sharedPrefs = launcher.getSharedPrefs();
+        int count = sharedPrefs.getInt(HOME_BOUNCE_COUNT, 0);
+        if (count > BOUNCE_MAX_COUNT) {
+            return;
+        }
+        sharedPrefs.edit().putInt(HOME_BOUNCE_COUNT, count + 1).apply();
+    }
 }
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index 62f59e4..cf070c5 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -80,6 +80,9 @@
 
     private OnAnimationEndDispatcher mEndListener;
     private DynamicAnimation.OnAnimationEndListener mSpringEndListener;
+    // We need this variable to ensure the end listener is called immediately, otherwise we run into
+    // issues where the callback interferes with the states of the swipe detector.
+    private boolean mSkipToEnd = false;
 
     protected AnimatorPlaybackController(AnimatorSet anim, long duration,
             Runnable onCancelRunnable) {
@@ -232,7 +235,11 @@
     }
 
     private void dispatchOnStartRecursively(Animator animator) {
-        for (AnimatorListener l : nonNullList(animator.getListeners())) {
+        List<AnimatorListener> listeners = animator instanceof SpringObjectAnimator
+                ? nonNullList(((SpringObjectAnimator) animator).getSuperListeners())
+                : nonNullList(animator.getListeners());
+
+        for (AnimatorListener l : listeners) {
             l.onAnimationStart(animator);
         }
 
@@ -280,6 +287,17 @@
         return mOnCancelRunnable;
     }
 
+    public void skipToEnd() {
+        mSkipToEnd = true;
+        for (SpringAnimation spring : mSprings) {
+            if (spring.canSkipToEnd()) {
+                spring.skipToEnd();
+            }
+        }
+        mAnimationPlayer.end();
+        mSkipToEnd = false;
+    }
+
     public static class AnimatorPlaybackControllerVL extends AnimatorPlaybackController {
 
         private final ValueAnimator[] mChildAnimations;
@@ -343,19 +361,34 @@
      */
     private class OnAnimationEndDispatcher extends AnimationSuccessListener {
 
+        boolean mAnimatorDone = false;
+        boolean mSpringsDone = false;
+        boolean mDispatched = false;
+
         @Override
         public void onAnimationStart(Animator animation) {
             mCancelled = false;
+            mDispatched = false;
         }
 
         @Override
         public void onAnimationSuccess(Animator animator) {
+            if (mSprings.isEmpty()) {
+                mSpringsDone = mAnimatorDone = true;
+            }
+            if (isAnySpringRunning()) {
+                mAnimatorDone = true;
+            } else {
+                mSpringsDone = true;
+            }
+
             // We wait for the spring (if any) to finish running before completing the end callback.
-            if (mSprings.isEmpty() || !isAnySpringRunning()) {
+            if (!mDispatched && (mSkipToEnd || (mAnimatorDone && mSpringsDone))) {
                 dispatchOnEndRecursively(mAnim);
                 if (mEndAction != null) {
                     mEndAction.run();
                 }
+                mDispatched = true;
             }
         }
 
diff --git a/src/com/android/launcher3/anim/RevealOutlineAnimation.java b/src/com/android/launcher3/anim/RevealOutlineAnimation.java
index afb8875..f99dabc 100644
--- a/src/com/android/launcher3/anim/RevealOutlineAnimation.java
+++ b/src/com/android/launcher3/anim/RevealOutlineAnimation.java
@@ -8,8 +8,6 @@
 import android.view.View;
 import android.view.ViewOutlineProvider;
 
-import com.android.launcher3.Utilities;
-
 /**
  * A {@link ViewOutlineProvider} that has helper functions to create reveal animations.
  * This class should be extended so that subclasses can define the reveal shape as the
@@ -58,16 +56,10 @@
 
         });
 
-        va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator arg0) {
-                float progress = (Float) arg0.getAnimatedValue();
-                setProgress(progress);
-                revealView.invalidateOutline();
-                if (!Utilities.ATLEAST_LOLLIPOP_MR1) {
-                    revealView.invalidate();
-                }
-            }
+        va.addUpdateListener(v -> {
+            float progress = (Float) v.getAnimatedValue();
+            setProgress(progress);
+            revealView.invalidateOutline();
         });
         return va;
     }
diff --git a/src/com/android/launcher3/anim/SpringObjectAnimator.java b/src/com/android/launcher3/anim/SpringObjectAnimator.java
index 4ece909..4f45c05 100644
--- a/src/com/android/launcher3/anim/SpringObjectAnimator.java
+++ b/src/com/android/launcher3/anim/SpringObjectAnimator.java
@@ -55,17 +55,27 @@
     private boolean mAnimatorEnded = false;
     private boolean mEnded = false;
 
-    private static final float SPRING_DAMPING_RATIO = 0.9f;
-    private static final float SPRING_STIFFNESS = 600f;
+    private static final FloatPropertyCompat<ProgressInterface> sFloatProperty =
+            new FloatPropertyCompat<ProgressInterface>("springObjectAnimator") {
+        @Override
+        public float getValue(ProgressInterface object) {
+            return object.getProgress();
+        }
 
-    public SpringObjectAnimator(T object, FloatPropertyCompat<T> floatProperty,
-            String name, float minimumVisibleChange, float... values) {
+        @Override
+        public void setValue(ProgressInterface object, float progress) {
+            object.setProgress(progress);
+        }
+    };
+
+    public SpringObjectAnimator(T object, String name, float minimumVisibleChange, float damping,
+            float stiffness, float... values) {
         mObject = object;
-        mSpring = new SpringAnimation(object, floatProperty);
+        mSpring = new SpringAnimation(object, sFloatProperty);
         mSpring.setMinimumVisibleChange(minimumVisibleChange);
         mSpring.setSpring(new SpringForce(0)
-                .setDampingRatio(SPRING_DAMPING_RATIO)
-                .setStiffness(SPRING_STIFFNESS));
+                .setDampingRatio(damping)
+                .setStiffness(stiffness));
         mSpring.setStartVelocity(0.01f);
         mProperty = new SpringProperty<T>(name, mSpring);
         mObjectAnimator = ObjectAnimator.ofFloat(object, mProperty, values);
@@ -73,7 +83,9 @@
         mListeners = new ArrayList<>();
         setFloatValues(values);
 
-        mObjectAnimator.addListener(new AnimatorListenerAdapter() {
+        // We use this listener and track mListeners so that we can sync the animator and spring
+        // listeners.
+        super.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
                 mAnimatorEnded = false;
@@ -94,7 +106,7 @@
                 for (AnimatorListener l : mListeners) {
                     l.onAnimationCancel(animation);
                 }
-                mSpring.animateToFinalPosition(mObject.getProgress());
+                mSpring.cancel();
             }
         });
 
@@ -145,6 +157,10 @@
         mListeners.add(listener);
     }
 
+    public ArrayList<AnimatorListener> getSuperListeners() {
+        return super.getListeners();
+    }
+
     @Override
     public ArrayList<AnimatorListener> getListeners() {
         return mListeners;
@@ -167,8 +183,8 @@
 
     @Override
     public void cancel() {
-        mSpring.animateToFinalPosition(mObject.getProgress());
         mObjectAnimator.cancel();
+        mSpring.cancel();
     }
 
     @Override
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 02da861..6feb1e9 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -26,6 +26,8 @@
 import com.android.launcher3.TestProtocol;
 import com.android.launcher3.Utilities;
 
+import java.util.function.Consumer;
+
 public class AccessibilityManagerCompat {
 
     public static boolean isAccessibilityEnabled(Context context) {
@@ -85,7 +87,7 @@
     }
 
     public static boolean processTestRequest(Context context, String eventTag, int action,
-            Bundle request, Utilities.Consumer<Bundle> responseFiller) {
+            Bundle request, Consumer<Bundle> responseFiller) {
         final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
         if (accessibilityManager == null) return false;
 
diff --git a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
index a7c0a47..dfdcc70 100644
--- a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
+++ b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
@@ -1,15 +1,12 @@
 package com.android.launcher3.compat;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.icu.text.AlphabeticIndex;
-import android.os.Build;
 import android.os.LocaleList;
 import android.util.Log;
 
 import com.android.launcher3.Utilities;
 
-import java.lang.reflect.Method;
 import java.util.Locale;
 
 import androidx.annotation.NonNull;
@@ -25,19 +22,10 @@
         BaseIndex index = null;
 
         try {
-            if (Utilities.ATLEAST_NOUGAT) {
-                index = new AlphabeticIndexVN(context);
-            }
+            index = new AlphabeticIndexVN(context);
         } catch (Exception e) {
             Log.d(TAG, "Unable to load the system index", e);
         }
-        if (index == null) {
-            try {
-                index = new AlphabeticIndexV16(context);
-            } catch (Exception e) {
-                Log.d(TAG, "Unable to load the system index", e);
-            }
-        }
 
         mBaseIndex = index == null ? new BaseIndex() : index;
 
@@ -111,59 +99,8 @@
     }
 
     /**
-     * Reflected libcore.icu.AlphabeticIndex implementation, falls back to the base
-     * alphabetic index.
-     */
-    private static class AlphabeticIndexV16 extends BaseIndex {
-
-        private Object mAlphabeticIndex;
-        private Method mGetBucketIndexMethod;
-        private Method mGetBucketLabelMethod;
-
-        public AlphabeticIndexV16(Context context) throws Exception {
-            Locale curLocale = context.getResources().getConfiguration().locale;
-            Class clazz = Class.forName("libcore.icu.AlphabeticIndex");
-            mGetBucketIndexMethod = clazz.getDeclaredMethod("getBucketIndex", String.class);
-            mGetBucketLabelMethod = clazz.getDeclaredMethod("getBucketLabel", int.class);
-            mAlphabeticIndex = clazz.getConstructor(Locale.class).newInstance(curLocale);
-
-            if (!curLocale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
-                clazz.getDeclaredMethod("addLabels", Locale.class)
-                        .invoke(mAlphabeticIndex, Locale.ENGLISH);
-            }
-        }
-
-        /**
-         * Returns the index of the bucket in which {@param s} should appear.
-         * Function is synchronized because underlying routine walks an iterator
-         * whose state is maintained inside the index object.
-         */
-        protected int getBucketIndex(String s) {
-            try {
-                return (Integer) mGetBucketIndexMethod.invoke(mAlphabeticIndex, s);
-            } catch (Exception e) {
-                e.printStackTrace();
-            }
-            return super.getBucketIndex(s);
-        }
-
-        /**
-         * Returns the label for the bucket at the given index (as returned by getBucketIndex).
-         */
-        protected String getBucketLabel(int index) {
-            try {
-                return (String) mGetBucketLabelMethod.invoke(mAlphabeticIndex, index);
-            } catch (Exception e) {
-                e.printStackTrace();
-            }
-            return super.getBucketLabel(index);
-        }
-    }
-
-    /**
      * Implementation based on {@link AlphabeticIndex}.
      */
-    @TargetApi(Build.VERSION_CODES.N)
     private static class AlphabeticIndexVN extends BaseIndex {
 
         private final AlphabeticIndex.ImmutableIndex mAlphabeticIndex;
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java
index 407355c..e7d4679 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompat.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java
@@ -83,8 +83,4 @@
             UserHandle user);
     public abstract List<ShortcutConfigActivityInfo> getCustomShortcutActivityList(
             @Nullable PackageUserKey packageUser);
-
-    public void showAppDetailsForProfile(ComponentName component, UserHandle user) {
-        showAppDetailsForProfile(component, user, null, null);
-    }
 }
diff --git a/src/com/android/launcher3/compat/UserManagerCompat.java b/src/com/android/launcher3/compat/UserManagerCompat.java
index e13d2a6..2c0088e 100644
--- a/src/com/android/launcher3/compat/UserManagerCompat.java
+++ b/src/com/android/launcher3/compat/UserManagerCompat.java
@@ -35,14 +35,8 @@
             if (sInstance == null) {
                 if (Utilities.ATLEAST_P) {
                     sInstance = new UserManagerCompatVP(context.getApplicationContext());
-                } else if (Utilities.ATLEAST_NOUGAT_MR1) {
-                    sInstance = new UserManagerCompatVNMr1(context.getApplicationContext());
-                } else if (Utilities.ATLEAST_NOUGAT) {
-                    sInstance = new UserManagerCompatVN(context.getApplicationContext());
-                } else if (Utilities.ATLEAST_MARSHMALLOW) {
-                    sInstance = new UserManagerCompatVM(context.getApplicationContext());
                 } else {
-                    sInstance = new UserManagerCompatVL(context.getApplicationContext());
+                    sInstance = new UserManagerCompatVNMr1(context.getApplicationContext());
                 }
             }
             return sInstance;
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVL.java b/src/com/android/launcher3/compat/UserManagerCompatVL.java
deleted file mode 100644
index 4688052..0000000
--- a/src/com/android/launcher3/compat/UserManagerCompatVL.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import android.content.Context;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.ArrayMap;
-import android.util.LongSparseArray;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-public class UserManagerCompatVL extends UserManagerCompat {
-
-    protected final UserManager mUserManager;
-
-    protected LongSparseArray<UserHandle> mUsers;
-    // Create a separate reverse map as LongSparseArray.indexOfValue checks if objects are same
-    // and not {@link Object#equals}
-    protected ArrayMap<UserHandle, Long> mUserToSerialMap;
-
-    UserManagerCompatVL(Context context) {
-        mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-    }
-
-    @Override
-    public long getSerialNumberForUser(UserHandle user) {
-        synchronized (this) {
-            if (mUserToSerialMap != null) {
-                Long serial = mUserToSerialMap.get(user);
-                return serial == null ? 0 : serial;
-            }
-        }
-        return mUserManager.getSerialNumberForUser(user);
-    }
-
-    @Override
-    public UserHandle getUserForSerialNumber(long serialNumber) {
-        synchronized (this) {
-            if (mUsers != null) {
-                return mUsers.get(serialNumber);
-            }
-        }
-        return mUserManager.getUserForSerialNumber(serialNumber);
-    }
-
-    @Override
-    public boolean isQuietModeEnabled(UserHandle user) {
-        return false;
-    }
-
-    @Override
-    public boolean isUserUnlocked(UserHandle user) {
-        return true;
-    }
-
-    @Override
-    public boolean isDemoUser() {
-        return false;
-    }
-
-    @Override
-    public boolean requestQuietModeEnabled(boolean enableQuietMode, UserHandle user) {
-        return false;
-    }
-
-    @Override
-    public boolean isAnyProfileQuietModeEnabled() {
-        return false;
-    }
-
-    @Override
-    public void enableAndResetCache() {
-        synchronized (this) {
-            mUsers = new LongSparseArray<>();
-            mUserToSerialMap = new ArrayMap<>();
-            List<UserHandle> users = mUserManager.getUserProfiles();
-            if (users != null) {
-                for (UserHandle user : users) {
-                    long serial = mUserManager.getSerialNumberForUser(user);
-                    mUsers.put(serial, user);
-                    mUserToSerialMap.put(user, serial);
-                }
-            }
-        }
-    }
-
-    @Override
-    public List<UserHandle> getUserProfiles() {
-        synchronized (this) {
-            if (mUsers != null) {
-                return new ArrayList<>(mUserToSerialMap.keySet());
-            }
-        }
-
-        List<UserHandle> users = mUserManager.getUserProfiles();
-        return users == null ? Collections.<UserHandle>emptyList() : users;
-    }
-
-    @Override
-    public boolean hasWorkProfile() {
-        synchronized (this) {
-            if (mUsers != null) {
-                return mUsers.size() > 1;
-            }
-        }
-        return getUserProfiles().size() > 1;
-    }
-}
-
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVM.java b/src/com/android/launcher3/compat/UserManagerCompatVM.java
deleted file mode 100644
index cf74b37..0000000
--- a/src/com/android/launcher3/compat/UserManagerCompatVM.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2016 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.compat;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.os.Build;
-import android.os.UserHandle;
-
-@TargetApi(Build.VERSION_CODES.M)
-public class UserManagerCompatVM extends UserManagerCompatVL {
-
-    UserManagerCompatVM(Context context) {
-        super(context);
-    }
-}
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVN.java b/src/com/android/launcher3/compat/UserManagerCompatVN.java
deleted file mode 100644
index 3733565..0000000
--- a/src/com/android/launcher3/compat/UserManagerCompatVN.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2016 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.compat;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.os.Build;
-import android.os.Process;
-import android.os.UserHandle;
-
-import java.util.List;
-
-@TargetApi(Build.VERSION_CODES.N)
-public class UserManagerCompatVN extends UserManagerCompatVM {
-
-    UserManagerCompatVN(Context context) {
-        super(context);
-    }
-
-    @Override
-    public boolean isQuietModeEnabled(UserHandle user) {
-        return mUserManager.isQuietModeEnabled(user);
-    }
-
-    @Override
-    public boolean isUserUnlocked(UserHandle user) {
-        return mUserManager.isUserUnlocked(user);
-    }
-
-    @Override
-    public boolean isAnyProfileQuietModeEnabled() {
-        List<UserHandle> userProfiles = getUserProfiles();
-        for (UserHandle userProfile : userProfiles) {
-            if (Process.myUserHandle().equals(userProfile)) {
-                continue;
-            }
-            if (isQuietModeEnabled(userProfile)) {
-                return true;
-            }
-        }
-        return false;
-    }
-}
-
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVNMr1.java b/src/com/android/launcher3/compat/UserManagerCompatVNMr1.java
index 3f64bc8..18d9053 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVNMr1.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVNMr1.java
@@ -19,16 +19,120 @@
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.ArrayMap;
+import android.util.LongSparseArray;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 @TargetApi(Build.VERSION_CODES.N_MR1)
-public class UserManagerCompatVNMr1 extends UserManagerCompatVN {
+public class UserManagerCompatVNMr1 extends UserManagerCompat {
+
+    protected final UserManager mUserManager;
+
+    protected LongSparseArray<UserHandle> mUsers;
+    // Create a separate reverse map as LongSparseArray.indexOfValue checks if objects are same
+    // and not {@link Object#equals}
+    protected ArrayMap<UserHandle, Long> mUserToSerialMap;
 
     UserManagerCompatVNMr1(Context context) {
-        super(context);
+        mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+    }
+
+    @Override
+    public boolean isQuietModeEnabled(UserHandle user) {
+        return mUserManager.isQuietModeEnabled(user);
+    }
+
+    @Override
+    public boolean isUserUnlocked(UserHandle user) {
+        return mUserManager.isUserUnlocked(user);
+    }
+
+    @Override
+    public boolean isAnyProfileQuietModeEnabled() {
+        List<UserHandle> userProfiles = getUserProfiles();
+        for (UserHandle userProfile : userProfiles) {
+            if (Process.myUserHandle().equals(userProfile)) {
+                continue;
+            }
+            if (isQuietModeEnabled(userProfile)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public long getSerialNumberForUser(UserHandle user) {
+        synchronized (this) {
+            if (mUserToSerialMap != null) {
+                Long serial = mUserToSerialMap.get(user);
+                return serial == null ? 0 : serial;
+            }
+        }
+        return mUserManager.getSerialNumberForUser(user);
+    }
+
+    @Override
+    public UserHandle getUserForSerialNumber(long serialNumber) {
+        synchronized (this) {
+            if (mUsers != null) {
+                return mUsers.get(serialNumber);
+            }
+        }
+        return mUserManager.getUserForSerialNumber(serialNumber);
     }
 
     @Override
     public boolean isDemoUser() {
         return mUserManager.isDemoUser();
     }
+
+    @Override
+    public boolean requestQuietModeEnabled(boolean enableQuietMode, UserHandle user) {
+        return false;
+    }
+
+    @Override
+    public void enableAndResetCache() {
+        synchronized (this) {
+            mUsers = new LongSparseArray<>();
+            mUserToSerialMap = new ArrayMap<>();
+            List<UserHandle> users = mUserManager.getUserProfiles();
+            if (users != null) {
+                for (UserHandle user : users) {
+                    long serial = mUserManager.getSerialNumberForUser(user);
+                    mUsers.put(serial, user);
+                    mUserToSerialMap.put(user, serial);
+                }
+            }
+        }
+    }
+
+    @Override
+    public List<UserHandle> getUserProfiles() {
+        synchronized (this) {
+            if (mUsers != null) {
+                return new ArrayList<>(mUserToSerialMap.keySet());
+            }
+        }
+
+        List<UserHandle> users = mUserManager.getUserProfiles();
+        return users == null ? Collections.<UserHandle>emptyList() : users;
+    }
+
+    @Override
+    public boolean hasWorkProfile() {
+        synchronized (this) {
+            if (mUsers != null) {
+                return mUsers.size() > 1;
+            }
+        }
+        return getUserProfiles().size() > 1;
+    }
 }
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index fa93081..fa4ebaf 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -17,13 +17,16 @@
 package com.android.launcher3.config;
 
 import static androidx.core.util.Preconditions.checkNotNull;
+
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.provider.Settings;
+
 import androidx.annotation.GuardedBy;
 import androidx.annotation.Keep;
 import androidx.annotation.VisibleForTesting;
+
 import com.android.launcher3.Utilities;
 
 import java.util.ArrayList;
@@ -107,7 +110,7 @@
 
     public static final ToggleableGlobalSettingsFlag SWIPE_HOME
             = new ToggleableGlobalSettingsFlag("SWIPE_HOME", false,
-            "[WIP] Swiping up on the nav bar goes home. Swipe and hold goes to recent apps.");
+            "Swiping up on the nav bar goes home. Swipe and hold goes to recent apps.");
 
     public static void initialize(Context context) {
         // Avoid the disk read for user builds
@@ -250,11 +253,17 @@
 
         @Override
         void updateStorage(Context context, boolean value) {
+            if (contentResolver == null) {
+                return;
+            }
             Settings.Global.putInt(contentResolver, getKey(), value ? 1 : 0);
         }
 
         @Override
         boolean getFromStorage(Context context, boolean defaultValue) {
+            if (contentResolver == null) {
+                return defaultValue;
+            }
             return Settings.Global.getInt(contentResolver, getKey(), defaultValue ? 1 : 0) == 1;
         }
 
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 79819cc..5ac9867 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -57,13 +57,14 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.InstantAppResolver;
 import com.android.launcher3.util.LooperExecutor;
-import com.android.launcher3.util.Provider;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
 import com.android.launcher3.widget.WidgetHostViewLoader;
 import com.android.launcher3.widget.WidgetImageView;
 
+import java.util.function.Supplier;
+
 @TargetApi(Build.VERSION_CODES.O)
 public class AddItemActivity extends BaseActivity implements OnLongClickListener, OnTouchListener {
 
@@ -220,7 +221,7 @@
         return true;
     }
 
-    private void applyWidgetItemAsync(final Provider<WidgetItem> itemProvider) {
+    private void applyWidgetItemAsync(final Supplier<WidgetItem> itemProvider) {
         new AsyncTask<Void, Void, WidgetItem>() {
             @Override
             protected WidgetItem doInBackground(Void... voids) {
diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java
index d8a3024..84fc94d 100644
--- a/src/com/android/launcher3/dragndrop/DragDriver.java
+++ b/src/com/android/launcher3/dragndrop/DragDriver.java
@@ -19,8 +19,8 @@
 import android.content.Context;
 import android.view.DragEvent;
 import android.view.MotionEvent;
+
 import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.Utilities;
 
 /**
  * Base class for driving a drag/drop operation.
@@ -83,7 +83,7 @@
 
     public static DragDriver create(Context context, DragController dragController,
             DragObject dragObject, DragOptions options) {
-        if (Utilities.ATLEAST_NOUGAT && options.systemDndStartPoint != null) {
+        if (options.systemDndStartPoint != null) {
             return new SystemDragDriver(dragController, context, dragObject);
         } else {
             return new InternalDragDriver(dragController);
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 51c2998..6fc81c9 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -34,11 +34,8 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Pair;
-import android.view.ActionMode;
 import android.view.FocusFinder;
 import android.view.KeyEvent;
-import android.view.Menu;
-import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewDebug;
@@ -65,7 +62,6 @@
 import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace.ItemOperator;
 import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
 import com.android.launcher3.config.FeatureFlags;
@@ -214,27 +210,6 @@
         mFolderName = findViewById(R.id.folder_name);
         mFolderName.setOnBackKeyListener(this);
         mFolderName.setOnFocusChangeListener(this);
-
-        if (!Utilities.ATLEAST_MARSHMALLOW) {
-            // We disable action mode in older OSes where floating selection menu is not yet
-            // available.
-            mFolderName.setCustomSelectionActionModeCallback(new ActionMode.Callback() {
-                public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
-                    return false;
-                }
-
-                public boolean onCreateActionMode(ActionMode mode, Menu menu) {
-                    return false;
-                }
-
-                public void onDestroyActionMode(ActionMode mode) {
-                }
-
-                public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
-                    return false;
-                }
-            });
-        }
         mFolderName.setOnEditorActionListener(this);
         mFolderName.setSelectAllOnFocus(true);
         mFolderName.setInputType(mFolderName.getInputType()
diff --git a/src/com/android/launcher3/folder/FolderShape.java b/src/com/android/launcher3/folder/FolderShape.java
index 4b06dda..61db6ff 100644
--- a/src/com/android/launcher3/folder/FolderShape.java
+++ b/src/com/android/launcher3/folder/FolderShape.java
@@ -15,8 +15,6 @@
  */
 package com.android.launcher3.folder;
 
-import static com.android.launcher3.Workspace.MAP_NO_RECURSE;
-
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.FloatArrayEvaluator;
@@ -43,9 +41,6 @@
 import android.util.Xml;
 import android.view.ViewOutlineProvider;
 
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
@@ -358,7 +353,7 @@
         if (!Utilities.ATLEAST_OREO) {
             return;
         }
-        new MainThreadExecutor().execute(() -> pickShapeInBackground(context));
+        pickBestShape(context);
     }
 
     private static FolderShape getShapeDefinition(String type, float radius) {
@@ -410,7 +405,7 @@
     }
 
     @TargetApi(Build.VERSION_CODES.O)
-    protected static void pickShapeInBackground(Context context) {
+    protected static void pickBestShape(Context context) {
         // Pick any large size
         int size = 200;
 
@@ -447,25 +442,7 @@
         }
 
         if (closestShape != null) {
-            FolderShape shape = closestShape;
-            new MainThreadExecutor().execute(() -> updateFolderShape(shape));
-        }
-    }
-
-    private static void updateFolderShape(FolderShape shape) {
-        sInstance = shape;
-        LauncherAppState app = LauncherAppState.getInstanceNoCreate();
-        if (app == null) {
-            return;
-        }
-        Launcher launcher = (Launcher) app.getModel().getCallback();
-        if (launcher != null) {
-            launcher.getWorkspace().mapOverItems(MAP_NO_RECURSE, (i, v) -> {
-                if (v instanceof FolderIcon) {
-                    v.invalidate();
-                }
-                return false;
-            });
+            sInstance = closestShape;
         }
     }
 }
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index e52fe66..837749d 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -21,6 +21,7 @@
 
 import android.annotation.TargetApi;
 import android.app.Fragment;
+import android.app.WallpaperManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.TypedArray;
@@ -29,6 +30,7 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.os.Build;
 import android.os.Handler;
@@ -281,7 +283,6 @@
             // Additional measure for views which use auto text size API
             measureView(mRootView, mDp.widthPx, mDp.heightPx);
 
-            canvas.drawColor(Color.GRAY);
             mRootView.draw(canvas);
             dispatchVisibilityAggregated(mRootView, false);
         }
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 4b54bc3..fb0a367 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -47,7 +47,8 @@
 import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.util.InstantAppResolver;
 import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.Provider;
+
+import java.util.function.Supplier;
 
 import androidx.annotation.NonNull;
 
@@ -156,7 +157,7 @@
      */
     public synchronized void updateTitleAndIcon(AppInfo application) {
         CacheEntry entry = cacheLocked(application.componentName,
-                application.user, Provider.of(null), mLauncherActivityInfoCachingLogic,
+                application.user, () -> null, mLauncherActivityInfoCachingLogic,
                 false, application.usingLowResIcon());
         if (entry.icon != null && !isDefaultIcon(entry.icon, application.user)) {
             applyCacheEntry(entry, application);
@@ -169,7 +170,7 @@
     public synchronized void getTitleAndIcon(ItemInfoWithIcon info,
             LauncherActivityInfo activityInfo, boolean useLowResIcon) {
         // If we already have activity info, no need to use package icon
-        getTitleAndIcon(info, Provider.of(activityInfo), false, useLowResIcon);
+        getTitleAndIcon(info, () -> activityInfo, false, useLowResIcon);
     }
 
     /**
@@ -191,7 +192,7 @@
     }
 
     public synchronized String getTitleNoCache(ComponentWithLabel info) {
-        CacheEntry entry = cacheLocked(info.getComponent(), info.getUser(), Provider.of(info),
+        CacheEntry entry = cacheLocked(info.getComponent(), info.getUser(), () -> info,
                 mComponentWithLabelCachingLogic, false /* usePackageIcon */,
                 true /* useLowResIcon */, false /* addToMemCache */);
         return Utilities.trim(entry.title);
@@ -202,7 +203,7 @@
      */
     private synchronized void getTitleAndIcon(
             @NonNull ItemInfoWithIcon infoInOut,
-            @NonNull Provider<LauncherActivityInfo> activityInfoProvider,
+            @NonNull Supplier<LauncherActivityInfo> activityInfoProvider,
             boolean usePkgIcon, boolean useLowResIcon) {
         CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), infoInOut.user,
                 activityInfoProvider, mLauncherActivityInfoCachingLogic, usePkgIcon, useLowResIcon);
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index f0a63ba..75f76d9 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -31,9 +31,10 @@
 import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
-import com.android.launcher3.util.Provider;
 import com.android.launcher3.util.Themes;
 
+import java.util.function.Supplier;
+
 import androidx.annotation.Nullable;
 
 /**
@@ -114,7 +115,7 @@
     }
 
     public BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo,
-            boolean badged, @Nullable Provider<ItemInfoWithIcon> fallbackIconProvider) {
+            boolean badged, @Nullable Supplier<ItemInfoWithIcon> fallbackIconProvider) {
         Drawable unbadgedDrawable = DeepShortcutManager.getInstance(mContext)
                 .getShortcutIconDrawable(shortcutInfo, mFillResIconDpi);
         IconCache cache = LauncherAppState.getInstance(mContext).getIconCache();
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index 23c6faf..210f744 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -251,11 +251,6 @@
 
     protected void bindWorkspaceItems(final ArrayList<ItemInfo> workspaceItems,
             final Executor executor) {
-
-        if (com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
-                && com.android.launcher3.Utilities.IS_DEBUG_DEVICE) {
-            Log.d("b/117332845", Log.getStackTraceString(new Throwable()));
-        }
         // Bind the workspace items
         int N = workspaceItems.size();
         for (int i = 0; i < N; i += ITEMS_CHUNK) {
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index 1c7bffa..243b286 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -47,8 +47,6 @@
  */
 public class GridSizeMigrationTask {
 
-    public static boolean ENABLED = Utilities.ATLEAST_NOUGAT;
-
     private static final String TAG = "GridSizeMigrationTask";
     private static final boolean DEBUG = true;
 
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index cfabc10..7275576 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -49,10 +49,8 @@
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.Workspace;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.PackageInstallerCompat;
@@ -75,7 +73,6 @@
 import com.android.launcher3.util.LooperIdleLock;
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageManagerHelper;
-import com.android.launcher3.util.Provider;
 import com.android.launcher3.util.TraceHelper;
 
 import java.util.ArrayList;
@@ -85,6 +82,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CancellationException;
+import java.util.function.Supplier;
 
 /**
  * Runnable for the thread that loads the contents of the launcher:
@@ -126,11 +124,6 @@
         mPackageInstaller = PackageInstallerCompat.getInstance(mApp.getContext());
         mAppWidgetManager = AppWidgetManagerCompat.getInstance(mApp.getContext());
         mIconCache = mApp.getIconCache();
-        if (com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
-                && com.android.launcher3.Utilities.IS_DEBUG_DEVICE) {
-            android.util.Log.d("b/117332845",
-                    android.util.Log.getStackTraceString(new Throwable()));
-        }
     }
 
     protected synchronized void waitForIdle() {
@@ -271,8 +264,7 @@
             clearDb = true;
         }
 
-        if (!clearDb && GridSizeMigrationTask.ENABLED &&
-                !GridSizeMigrationTask.migrateGridIfNeeded(context)) {
+        if (!clearDb && !GridSizeMigrationTask.migrateGridIfNeeded(context)) {
             // Migration failed. Clear workspace.
             clearDb = true;
         }
@@ -497,7 +489,7 @@
                                     LauncherIcons li = LauncherIcons.obtain(context);
                                     // If the pinned deep shortcut is no longer published,
                                     // use the last saved icon instead of the default.
-                                    Provider<ItemInfoWithIcon> fallbackIconProvider = () ->
+                                    Supplier<ItemInfoWithIcon> fallbackIconProvider = () ->
                                             c.loadIcon(finalInfo, li) ? finalInfo : null;
                                     info.applyFrom(li.createShortcutIcon(pinnedShortcut,
                                             true /* badged */, fallbackIconProvider));
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index d8d9930..ac5076c 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -109,11 +109,6 @@
     }
 
     private void checkItemInfoLocked(int itemId, ItemInfo item, StackTraceElement[] stackTrace) {
-        if (com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
-                && com.android.launcher3.Utilities.IS_DEBUG_DEVICE) {
-            android.util.Log.d("b/117332845",
-                    "Checking item: " + android.util.Log.getStackTraceString(new Throwable()));
-        }
         ItemInfo modelItem = mBgDataModel.itemsIdMap.get(itemId);
         if (modelItem != null && item != modelItem) {
             // check all the data is consistent
@@ -363,11 +358,6 @@
         private final int mItemId;
 
         UpdateItemRunnable(ItemInfo item, ContentWriter writer) {
-            if (com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
-                    && com.android.launcher3.Utilities.IS_DEBUG_DEVICE) {
-                android.util.Log.d("b/117332845",
-                        android.util.Log.getStackTraceString(new Throwable()));
-            }
             mItem = item;
             mWriter = writer;
             mItemId = item.id;
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 4e699f7..1644c89 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -29,7 +29,6 @@
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.MultiHashMap;
-import com.android.launcher3.util.Provider;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -97,7 +96,7 @@
                     // keep the current icon instead of reverting to the default icon.
                     LauncherIcons li = LauncherIcons.obtain(context);
                     shortcutInfo.applyFrom(li.createShortcutIcon(fullDetails, true,
-                            Provider.of(shortcutInfo)));
+                            () -> shortcutInfo));
                     li.recycle();
                     updatedShortcutInfos.add(shortcutInfo);
                 }
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index 59f4284..7c4e454 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -32,7 +32,6 @@
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.Provider;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -95,7 +94,7 @@
                     // If the shortcut is pinned but no longer has an icon in the system,
                     // keep the current icon instead of reverting to the default icon.
                     LauncherIcons li = LauncherIcons.obtain(context);
-                    si.applyFrom(li.createShortcutIcon(shortcut, true, Provider.of(si)));
+                    si.applyFrom(li.createShortcutIcon(shortcut, true, () -> si));
                     li.recycle();
                 } else {
                     si.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 0c098da..288d568 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -77,7 +77,6 @@
 /**
  * A container for shortcuts to deep links and notifications associated with an app.
  */
-@TargetApi(Build.VERSION_CODES.N)
 public class PopupContainerWithArrow extends ArrowPopup implements DragSource,
         DragController.DragListener, View.OnLongClickListener,
         View.OnTouchListener {
diff --git a/src/com/android/launcher3/shortcuts/ShortcutCache.java b/src/com/android/launcher3/shortcuts/ShortcutCache.java
deleted file mode 100644
index 5742d1d..0000000
--- a/src/com/android/launcher3/shortcuts/ShortcutCache.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2016 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.shortcuts;
-
-import android.annotation.TargetApi;
-import android.os.Build;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.LruCache;
-import java.util.List;
-
-/**
- * Loads {@link ShortcutInfoCompat}s on demand (e.g. when launcher
- * loads for pinned shortcuts and on long-press for dynamic shortcuts), and caches them
- * for handful of apps in an LruCache while launcher lives.
- */
-@TargetApi(Build.VERSION_CODES.N)
-public class ShortcutCache {
-    private static final int CACHE_SIZE = 30; // Max number shortcuts we cache.
-
-    private final LruCache<ShortcutKey, ShortcutInfoCompat> mCachedShortcuts;
-    // We always keep pinned shortcuts in the cache.
-    private final ArrayMap<ShortcutKey, ShortcutInfoCompat> mPinnedShortcuts;
-
-    public ShortcutCache() {
-        mCachedShortcuts = new LruCache<>(CACHE_SIZE);
-        mPinnedShortcuts = new ArrayMap<>();
-    }
-
-    /**
-     * Removes shortcuts from the cache when shortcuts change for a given package.
-     *
-     * Returns a map of ids to their evicted shortcuts.
-     *
-     * @see android.content.pm.LauncherApps.Callback#onShortcutsChanged(String, List, UserHandle).
-     */
-    public void removeShortcuts(List<ShortcutInfoCompat> shortcuts) {
-        for (ShortcutInfoCompat shortcut : shortcuts) {
-            ShortcutKey key = ShortcutKey.fromInfo(shortcut);
-            mCachedShortcuts.remove(key);
-            mPinnedShortcuts.remove(key);
-        }
-    }
-
-    public ShortcutInfoCompat get(ShortcutKey key) {
-        if (mPinnedShortcuts.containsKey(key)) {
-            return mPinnedShortcuts.get(key);
-        }
-        return mCachedShortcuts.get(key);
-    }
-
-    public void put(ShortcutKey key, ShortcutInfoCompat shortcut) {
-        if (shortcut.isPinned()) {
-            mPinnedShortcuts.put(key, shortcut);
-        } else {
-            mCachedShortcuts.put(key, shortcut);
-        }
-    }
-}
diff --git a/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java b/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java
index 325777d..e5bd002 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java
@@ -16,12 +16,10 @@
 
 package com.android.launcher3.shortcuts;
 
-import android.annotation.TargetApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ShortcutInfo;
-import android.os.Build;
 import android.os.UserHandle;
 
 import com.android.launcher3.R;
@@ -31,7 +29,6 @@
  *
  * Not to be confused with {@link com.android.launcher3.ShortcutInfo}.
  */
-@TargetApi(Build.VERSION_CODES.N)
 public class ShortcutInfoCompat {
     private static final String INTENT_CATEGORY = "com.android.launcher3.DEEP_SHORTCUT";
     private static final String EXTRA_BADGEPKG = "badge_package";
@@ -42,7 +39,6 @@
         mShortcutInfo = shortcutInfo;
     }
 
-    @TargetApi(Build.VERSION_CODES.N)
     public Intent makeIntent() {
         return new Intent(Intent.ACTION_MAIN)
                 .addCategory(INTENT_CATEGORY)
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 65103f6..fb41ea1 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -20,8 +20,6 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
 
-import static com.android.launcher3.Utilities.ATLEAST_NOUGAT;
-
 import android.app.Activity;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
@@ -39,15 +37,12 @@
     public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation";
 
     public static boolean getAllowRotationDefaultValue() {
-        if (ATLEAST_NOUGAT) {
-            // If the device was scaled, used the original dimensions to determine if rotation
-            // is allowed of not.
-            Resources res = Resources.getSystem();
-            int originalSmallestWidth = res.getConfiguration().smallestScreenWidthDp
-                    * res.getDisplayMetrics().densityDpi / DENSITY_DEVICE_STABLE;
-            return originalSmallestWidth >= 600;
-        }
-        return false;
+        // If the device was scaled, used the original dimensions to determine if rotation
+        // is allowed of not.
+        Resources res = Resources.getSystem();
+        int originalSmallestWidth = res.getConfiguration().smallestScreenWidthDp
+                * res.getDisplayMetrics().densityDpi / DENSITY_DEVICE_STABLE;
+        return originalSmallestWidth >= 600;
     }
 
     public static final int REQUEST_NONE = 0;
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index bb14328..0e2ed6c 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -46,7 +46,6 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -298,7 +297,7 @@
      * When going between normal and overview states, see if we passed the overview threshold and
      * play the appropriate atomic animation if so.
      */
-    private void maybeUpdateAtomicAnim(LauncherState fromState, LauncherState toState,
+    protected void maybeUpdateAtomicAnim(LauncherState fromState, LauncherState toState,
             float progress) {
         if (!goingBetweenNormalAndOverview(fromState, toState)) {
             return;
@@ -435,7 +434,11 @@
             mLauncher.getAppsView().addSpringFromFlingUpdateListener(anim, velocity);
         }
         anim.start();
-        mAtomicAnimAutoPlayInfo = new AutoPlayAtomicAnimationInfo(endProgress, anim.getDuration());
+        settleAtomicAnimation(endProgress, anim.getDuration());
+    }
+
+    protected void settleAtomicAnimation(float endProgress, long duration) {
+        mAtomicAnimAutoPlayInfo = new AutoPlayAtomicAnimationInfo(endProgress, duration);
         maybeAutoPlayAtomicComponentsAnim();
     }
 
diff --git a/src/com/android/launcher3/touch/TouchEventTranslator.java b/src/com/android/launcher3/touch/TouchEventTranslator.java
index bf0c84c..3fcda90 100644
--- a/src/com/android/launcher3/touch/TouchEventTranslator.java
+++ b/src/com/android/launcher3/touch/TouchEventTranslator.java
@@ -23,7 +23,7 @@
 import android.view.MotionEvent.PointerCoords;
 import android.view.MotionEvent.PointerProperties;
 
-import com.android.launcher3.Utilities.Consumer;
+import java.util.function.Consumer;
 
 /**
  * To minimize the size of the MotionEvent, historic events are not copied and passed via the
diff --git a/src/com/android/launcher3/util/ConfigMonitor.java b/src/com/android/launcher3/util/ConfigMonitor.java
index 607afab..12280f8 100644
--- a/src/com/android/launcher3/util/ConfigMonitor.java
+++ b/src/com/android/launcher3/util/ConfigMonitor.java
@@ -29,9 +29,9 @@
 import android.view.Display;
 import android.view.WindowManager;
 
-import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.MainThreadExecutor;
-import com.android.launcher3.Utilities.Consumer;
+
+import java.util.function.Consumer;
 
 /**
  * {@link BroadcastReceiver} which watches configuration changes and
diff --git a/src/com/android/launcher3/util/LooperIdleLock.java b/src/com/android/launcher3/util/LooperIdleLock.java
index 35cac14..2896535 100644
--- a/src/com/android/launcher3/util/LooperIdleLock.java
+++ b/src/com/android/launcher3/util/LooperIdleLock.java
@@ -19,8 +19,6 @@
 import android.os.Looper;
 import android.os.MessageQueue;
 
-import com.android.launcher3.Utilities;
-
 /**
  * Utility class to block execution until the UI looper is idle.
  */
@@ -33,13 +31,7 @@
     public LooperIdleLock(Object lock, Looper looper) {
         mLock = lock;
         mIsLocked = true;
-        if (Utilities.ATLEAST_MARSHMALLOW) {
-            looper.getQueue().addIdleHandler(this);
-        } else {
-            // Looper.myQueue() only gives the current queue. Move the execution to the UI thread
-            // so that the IdleHandler is attached to the correct message queue.
-            new LooperExecutor(looper).execute(this);
-        }
+        looper.getQueue().addIdleHandler(this);
     }
 
     @Override
diff --git a/src/com/android/launcher3/util/OverScroller.java b/src/com/android/launcher3/util/OverScroller.java
index d697ece..fc8a138 100644
--- a/src/com/android/launcher3/util/OverScroller.java
+++ b/src/com/android/launcher3/util/OverScroller.java
@@ -26,6 +26,11 @@
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
 /**
  * Based on {@link android.widget.OverScroller} supporting only 1-d scrolling and with more
  * customization options.
@@ -196,6 +201,9 @@
 
         switch (mMode) {
             case SCROLL_MODE:
+                if (isSpringing()) {
+                    return true;
+                }
                 long time = AnimationUtils.currentAnimationTimeMillis();
                 // Any scroller can be used for time, since they were started
                 // together in scroll mode. We use X here.
@@ -254,6 +262,22 @@
     }
 
     /**
+     * Start scrolling using a spring by providing a starting point and the distance to travel.
+     *
+     * @param start Starting scroll offset in pixels. Positive
+     *        numbers will scroll the content to the left.
+     * @param delta Distance to travel. Positive numbers will scroll the
+     *        content to the left.
+     * @param duration Duration of the scroll in milliseconds.
+     * @param velocity The starting velocity for the spring in px per ms.
+     */
+    public void startScrollSpring(int start, int delta, int duration, float velocity) {
+        mMode = SCROLL_MODE;
+        mScroller.mState = mScroller.SPRING;
+        mScroller.startScroll(start, delta, duration, velocity);
+    }
+
+    /**
      * Call this when you want to 'spring back' into a valid coordinate range.
      *
      * @param start Starting X coordinate
@@ -354,6 +378,10 @@
         return (int) (time - mScroller.mStartTime);
     }
 
+    public boolean isSpringing() {
+        return mScroller.mState == SplineOverScroller.SPRING && !isFinished();
+    }
+
     static class SplineOverScroller {
         // Initial position
         private int mStart;
@@ -397,6 +425,8 @@
         // Current state of the animation.
         private int mState = SPLINE;
 
+        private SpringAnimation mSpring;
+
         // Constant gravity value, used in the deceleration phase.
         private static final float GRAVITY = 2000.0f;
 
@@ -417,6 +447,20 @@
         private static final int SPLINE = 0;
         private static final int CUBIC = 1;
         private static final int BALLISTIC = 2;
+        private static final int SPRING = 3;
+
+        private static final FloatPropertyCompat<SplineOverScroller> SPRING_PROPERTY =
+                new FloatPropertyCompat<SplineOverScroller>("splineOverScrollerSpring") {
+                    @Override
+                    public float getValue(SplineOverScroller scroller) {
+                        return scroller.mCurrentPosition;
+                    }
+
+                    @Override
+                    public void setValue(SplineOverScroller scroller, float value) {
+                        scroller.mCurrentPosition = (int) value;
+                    }
+                };
 
         static {
             float x_min = 0.0f;
@@ -465,6 +509,9 @@
         }
 
         void updateScroll(float q) {
+            if (mState == SPRING) {
+                return;
+            }
             mCurrentPosition = mStart + Math.round(q * (mFinal - mStart));
         }
 
@@ -495,6 +542,10 @@
         }
 
         void startScroll(int start, int distance, int duration) {
+            startScroll(start, distance, duration, 0);
+        }
+
+        void startScroll(int start, int distance, int duration, float velocity) {
             mFinished = false;
 
             mCurrentPosition = mStart = start;
@@ -503,12 +554,31 @@
             mStartTime = AnimationUtils.currentAnimationTimeMillis();
             mDuration = duration;
 
+            if (mState == SPRING) {
+                if (mSpring != null) {
+                    mSpring.cancel();
+                }
+                mSpring = new SpringAnimation(this, SPRING_PROPERTY);
+
+                mSpring.setSpring(new SpringForce(mFinal)
+                        .setStiffness(SpringForce.STIFFNESS_LOW)
+                        .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
+                mSpring.setStartVelocity(velocity);
+                mSpring.animateToFinalPosition(mFinal);
+                mSpring.addEndListener((animation, canceled, value, velocity1) -> {
+                    finish();
+                    mState = SPLINE;
+                    mSpring = null;
+                });
+            }
             // Unused
             mDeceleration = 0.0f;
             mVelocity = 0;
         }
 
         void finish() {
+            if (mSpring != null && mSpring.isRunning()) mSpring.cancel();
+
             mCurrentPosition = mFinal;
             // Not reset since WebView relies on this value for fast fling.
             // TODO: restore when WebView uses the fast fling implemented in this class.
@@ -518,6 +588,9 @@
 
         void setFinalPosition(int position) {
             mFinal = position;
+            if (mState == SPRING && mSpring != null) {
+                mSpring.animateToFinalPosition(mFinal);
+            }
             mSplineDistance = mFinal - mStart;
             mFinished = false;
         }
@@ -722,6 +795,10 @@
          * reached.
          */
         boolean update() {
+            if (mState == SPRING) {
+                return mFinished;
+            }
+
             final long time = AnimationUtils.currentAnimationTimeMillis();
             final long currentTime = time - mStartTime;
 
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 0b3b632..d71bd15 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -37,13 +37,11 @@
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.PromiseAppInfo;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.LauncherAppsCompat;
 
 import java.net.URISyntaxException;
@@ -100,14 +98,7 @@
      * {@link android.app.admin.DevicePolicyManager#isPackageSuspended}.
      */
     public static boolean isAppSuspended(ApplicationInfo info) {
-        // The value of FLAG_SUSPENDED was reused by a hidden constant
-        // ApplicationInfo.FLAG_PRIVILEGED prior to N, so only check for suspended flag on N
-        // or later.
-        if (Utilities.ATLEAST_NOUGAT) {
-            return (info.flags & ApplicationInfo.FLAG_SUSPENDED) != 0;
-        } else {
-            return false;
-        }
+        return (info.flags & ApplicationInfo.FLAG_SUSPENDED) != 0;
     }
 
     /**
@@ -136,11 +127,6 @@
             return false;
         }
 
-        if (!Utilities.ATLEAST_MARSHMALLOW) {
-            // These checks are sufficient for below M devices.
-            return true;
-        }
-
         // On M and above also check AppOpsManager for compatibility mode permissions.
         if (TextUtils.isEmpty(AppOpsManager.permissionToOp(target.activityInfo.permission))) {
             // There is no app-op for this permission, which could have been disabled.
diff --git a/src/com/android/launcher3/util/RaceConditionTracker.java b/src/com/android/launcher3/util/RaceConditionTracker.java
index 8b06787..6954d0e 100644
--- a/src/com/android/launcher3/util/RaceConditionTracker.java
+++ b/src/com/android/launcher3/util/RaceConditionTracker.java
@@ -24,6 +24,8 @@
 public class RaceConditionTracker {
     public final static boolean ENTER = true;
     public final static boolean EXIT = false;
+    static final String ENTER_POSTFIX = "enter";
+    static final String EXIT_POSTFIX = "exit";
 
     public interface EventProcessor {
         void onEvent(String eventName);
@@ -46,7 +48,7 @@
     }
 
     public static String enterExitEvt(String eventName, boolean isEnter) {
-        return eventName + ":" + (isEnter ? "enter" : "exit");
+        return eventName + ":" + (isEnter ? ENTER_POSTFIX : EXIT_POSTFIX);
     }
 
     public static String enterEvt(String eventName) {
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index deb0965..a264f9b 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -315,7 +315,7 @@
 
         if (enabled) {
             stateManager.addStateListener(this);
-            onStateSetImmediately(mLauncher.getStateManager().getState());
+            handleStateChangedComplete(mLauncher.getStateManager().getState());
         } else {
             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
         }
@@ -361,12 +361,11 @@
 
     @Override
     public void onStateTransitionComplete(LauncherState finalState) {
-        onStateSetImmediately(finalState);
+        handleStateChangedComplete(finalState);
     }
 
-    @Override
-    public void onStateSetImmediately(LauncherState state) {
-        setImportantForAccessibility(state == ALL_APPS
+    private void handleStateChangedComplete(LauncherState finalState) {
+        setImportantForAccessibility(finalState == ALL_APPS
                 ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
                 : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
     }
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 673b3cc..508695b 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -161,7 +161,7 @@
 
     private static View createColorScrim(Context context) {
         View view = new View(context);
-        if (Utilities.ATLEAST_NOUGAT) view.forceHasOverlappingRendering(false);
+        view.forceHasOverlappingRendering(false);
 
         WallpaperColorInfo colors = WallpaperColorInfo.getInstance(context);
         int alpha = context.getResources().getInteger(R.integer.extracted_color_gradient_alpha);
diff --git a/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java
index e70aac6..6c0c429 100644
--- a/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java
+++ b/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3.shortcuts;
 
-import android.annotation.TargetApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.LauncherApps;
@@ -30,7 +29,6 @@
 
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.Utilities;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -94,7 +92,6 @@
      * Gets all the manifest and dynamic shortcuts associated with the given package and user,
      * to be displayed in the shortcuts container on long press.
      */
-    @TargetApi(25)
     public List<ShortcutInfoCompat> queryForShortcutsContainer(ComponentName activity,
             UserHandle user) {
         return query(ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_DYNAMIC,
@@ -105,21 +102,18 @@
      * Removes the given shortcut from the current list of pinned shortcuts.
      * (Runs on background thread)
      */
-    @TargetApi(25)
     public void unpinShortcut(final ShortcutKey key) {
-        if (Utilities.ATLEAST_NOUGAT_MR1) {
-            String packageName = key.componentName.getPackageName();
-            String id = key.getId();
-            UserHandle user = key.user;
-            List<String> pinnedIds = extractIds(queryForPinnedShortcuts(packageName, user));
-            pinnedIds.remove(id);
-            try {
-                mLauncherApps.pinShortcuts(packageName, pinnedIds, user);
-                mWasLastCallSuccess = true;
-            } catch (SecurityException|IllegalStateException e) {
-                Log.w(TAG, "Failed to unpin shortcut", e);
-                mWasLastCallSuccess = false;
-            }
+        String packageName = key.componentName.getPackageName();
+        String id = key.getId();
+        UserHandle user = key.user;
+        List<String> pinnedIds = extractIds(queryForPinnedShortcuts(packageName, user));
+        pinnedIds.remove(id);
+        try {
+            mLauncherApps.pinShortcuts(packageName, pinnedIds, user);
+            mWasLastCallSuccess = true;
+        } catch (SecurityException|IllegalStateException e) {
+            Log.w(TAG, "Failed to unpin shortcut", e);
+            mWasLastCallSuccess = false;
         }
     }
 
@@ -127,51 +121,42 @@
      * Adds the given shortcut to the current list of pinned shortcuts.
      * (Runs on background thread)
      */
-    @TargetApi(25)
     public void pinShortcut(final ShortcutKey key) {
-        if (Utilities.ATLEAST_NOUGAT_MR1) {
-            String packageName = key.componentName.getPackageName();
-            String id = key.getId();
-            UserHandle user = key.user;
-            List<String> pinnedIds = extractIds(queryForPinnedShortcuts(packageName, user));
-            pinnedIds.add(id);
-            try {
-                mLauncherApps.pinShortcuts(packageName, pinnedIds, user);
-                mWasLastCallSuccess = true;
-            } catch (SecurityException|IllegalStateException e) {
-                Log.w(TAG, "Failed to pin shortcut", e);
-                mWasLastCallSuccess = false;
-            }
+        String packageName = key.componentName.getPackageName();
+        String id = key.getId();
+        UserHandle user = key.user;
+        List<String> pinnedIds = extractIds(queryForPinnedShortcuts(packageName, user));
+        pinnedIds.add(id);
+        try {
+            mLauncherApps.pinShortcuts(packageName, pinnedIds, user);
+            mWasLastCallSuccess = true;
+        } catch (SecurityException|IllegalStateException e) {
+            Log.w(TAG, "Failed to pin shortcut", e);
+            mWasLastCallSuccess = false;
         }
     }
 
-    @TargetApi(25)
     public void startShortcut(String packageName, String id, Rect sourceBounds,
           Bundle startActivityOptions, UserHandle user) {
-        if (Utilities.ATLEAST_NOUGAT_MR1) {
-            try {
-                mLauncherApps.startShortcut(packageName, id, sourceBounds,
-                        startActivityOptions, user);
-                mWasLastCallSuccess = true;
-            } catch (SecurityException|IllegalStateException e) {
-                Log.e(TAG, "Failed to start shortcut", e);
-                mWasLastCallSuccess = false;
-            }
+        try {
+            mLauncherApps.startShortcut(packageName, id, sourceBounds,
+                    startActivityOptions, user);
+            mWasLastCallSuccess = true;
+        } catch (SecurityException|IllegalStateException e) {
+            Log.e(TAG, "Failed to start shortcut", e);
+            mWasLastCallSuccess = false;
         }
     }
 
-    @TargetApi(25)
     public Drawable getShortcutIconDrawable(ShortcutInfoCompat shortcutInfo, int density) {
-        if (Utilities.ATLEAST_NOUGAT_MR1) {
-            try {
-                Drawable icon = mLauncherApps.getShortcutIconDrawable(
-                        shortcutInfo.getShortcutInfo(), density);
-                mWasLastCallSuccess = true;
-                return icon;
-            } catch (SecurityException|IllegalStateException e) {
-                Log.e(TAG, "Failed to get shortcut icon", e);
-                mWasLastCallSuccess = false;
-            }
+        try {
+            Drawable icon = mLauncherApps.getShortcutIconDrawable(
+                    shortcutInfo.getShortcutInfo(), density);
+            mWasLastCallSuccess = true;
+            return icon;
+        } catch (SecurityException|IllegalStateException e) {
+            Log.e(TAG, "Failed to get shortcut icon", e);
+            mWasLastCallSuccess = false;
         }
         return null;
     }
@@ -208,46 +193,38 @@
      *
      * TODO: Use the cache to optimize this so we don't make an RPC every time.
      */
-    @TargetApi(25)
     private List<ShortcutInfoCompat> query(int flags, String packageName,
             ComponentName activity, List<String> shortcutIds, UserHandle user) {
-        if (Utilities.ATLEAST_NOUGAT_MR1) {
-            ShortcutQuery q = new ShortcutQuery();
-            q.setQueryFlags(flags);
-            if (packageName != null) {
-                q.setPackage(packageName);
-                q.setActivity(activity);
-                q.setShortcutIds(shortcutIds);
-            }
-            List<ShortcutInfo> shortcutInfos = null;
-            try {
-                shortcutInfos = mLauncherApps.getShortcuts(q, user);
-                mWasLastCallSuccess = true;
-            } catch (SecurityException|IllegalStateException e) {
-                Log.e(TAG, "Failed to query for shortcuts", e);
-                mWasLastCallSuccess = false;
-            }
-            if (shortcutInfos == null) {
-                return Collections.EMPTY_LIST;
-            }
-            List<ShortcutInfoCompat> shortcutInfoCompats = new ArrayList<>(shortcutInfos.size());
-            for (ShortcutInfo shortcutInfo : shortcutInfos) {
-                shortcutInfoCompats.add(new ShortcutInfoCompat(shortcutInfo));
-            }
-            return shortcutInfoCompats;
-        } else {
+        ShortcutQuery q = new ShortcutQuery();
+        q.setQueryFlags(flags);
+        if (packageName != null) {
+            q.setPackage(packageName);
+            q.setActivity(activity);
+            q.setShortcutIds(shortcutIds);
+        }
+        List<ShortcutInfo> shortcutInfos = null;
+        try {
+            shortcutInfos = mLauncherApps.getShortcuts(q, user);
+            mWasLastCallSuccess = true;
+        } catch (SecurityException|IllegalStateException e) {
+            Log.e(TAG, "Failed to query for shortcuts", e);
+            mWasLastCallSuccess = false;
+        }
+        if (shortcutInfos == null) {
             return Collections.EMPTY_LIST;
         }
+        List<ShortcutInfoCompat> shortcutInfoCompats = new ArrayList<>(shortcutInfos.size());
+        for (ShortcutInfo shortcutInfo : shortcutInfos) {
+            shortcutInfoCompats.add(new ShortcutInfoCompat(shortcutInfo));
+        }
+        return shortcutInfoCompats;
     }
 
-    @TargetApi(25)
     public boolean hasHostPermission() {
-        if (Utilities.ATLEAST_NOUGAT_MR1) {
-            try {
-                return mLauncherApps.hasShortcutHostPermission();
-            } catch (SecurityException|IllegalStateException e) {
-                Log.e(TAG, "Failed to make shortcut manager call", e);
-            }
+        try {
+            return mLauncherApps.hasShortcutHostPermission();
+        } catch (SecurityException|IllegalStateException e) {
+            Log.e(TAG, "Failed to make shortcut manager call", e);
         }
         return false;
     }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompatVL.java b/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompatVL.java
index 6808859..500fdc3 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompatVL.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompatVL.java
@@ -44,7 +44,6 @@
 import android.util.Log;
 import android.util.Pair;
 
-import com.android.launcher3.Utilities;
 import com.android.launcher3.icons.ColorExtractor;
 
 import java.io.IOException;
@@ -61,6 +60,8 @@
     private static final String ACTION_EXTRACTION_COMPLETE =
             "com.android.launcher3.uioverrides.dynamicui.WallpaperManagerCompatVL.EXTRACTION_COMPLETE";
 
+    public static final int WALLPAPER_COMPAT_JOB_ID = 1;
+
     private final ArrayList<OnColorsChangedListenerCompat> mListeners = new ArrayList<>();
 
     private final Context mContext;
@@ -122,7 +123,7 @@
     }
 
     private void reloadColors() {
-        JobInfo job = new JobInfo.Builder(Utilities.WALLPAPER_COMPAT_JOB_ID,
+        JobInfo job = new JobInfo.Builder(WALLPAPER_COMPAT_JOB_ID,
                 new ComponentName(mContext, ColorExtractionService.class))
                 .setMinimumLatency(0).build();
         ((JobScheduler) mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE)).schedule(job);
@@ -137,9 +138,6 @@
     }
 
     private static final int getWallpaperId(Context context) {
-        if (!Utilities.ATLEAST_NOUGAT) {
-            return -1;
-        }
         return context.getSystemService(WallpaperManager.class).getWallpaperId(FLAG_SYSTEM);
     }
 
@@ -215,27 +213,25 @@
                 // For live wallpaper, extract colors from thumbnail
                 drawable = info.loadThumbnail(getPackageManager());
             } else {
-                if (Utilities.ATLEAST_NOUGAT) {
-                    try (ParcelFileDescriptor fd = wm.getWallpaperFile(FLAG_SYSTEM)) {
-                        BitmapRegionDecoder decoder = BitmapRegionDecoder
-                                .newInstance(fd.getFileDescriptor(), false);
+                try (ParcelFileDescriptor fd = wm.getWallpaperFile(FLAG_SYSTEM)) {
+                    BitmapRegionDecoder decoder = BitmapRegionDecoder
+                            .newInstance(fd.getFileDescriptor(), false);
 
-                        int requestedArea = decoder.getWidth() * decoder.getHeight();
-                        BitmapFactory.Options options = new BitmapFactory.Options();
+                    int requestedArea = decoder.getWidth() * decoder.getHeight();
+                    BitmapFactory.Options options = new BitmapFactory.Options();
 
-                        if (requestedArea > MAX_WALLPAPER_EXTRACTION_AREA) {
-                            double areaRatio =
-                                    (double) requestedArea / MAX_WALLPAPER_EXTRACTION_AREA;
-                            double nearestPowOf2 =
-                                    Math.floor(Math.log(areaRatio) / (2 * Math.log(2)));
-                            options.inSampleSize = (int) Math.pow(2, nearestPowOf2);
-                        }
-                        Rect region = new Rect(0, 0, decoder.getWidth(), decoder.getHeight());
-                        bitmap = decoder.decodeRegion(region, options);
-                        decoder.recycle();
-                    } catch (IOException | NullPointerException e) {
-                        Log.e(TAG, "Fetching partial bitmap failed, trying old method", e);
+                    if (requestedArea > MAX_WALLPAPER_EXTRACTION_AREA) {
+                        double areaRatio =
+                                (double) requestedArea / MAX_WALLPAPER_EXTRACTION_AREA;
+                        double nearestPowOf2 =
+                                Math.floor(Math.log(areaRatio) / (2 * Math.log(2)));
+                        options.inSampleSize = (int) Math.pow(2, nearestPowOf2);
                     }
+                    Rect region = new Rect(0, 0, decoder.getWidth(), decoder.getHeight());
+                    bitmap = decoder.decodeRegion(region, options);
+                    decoder.recycle();
+                } catch (IOException | NullPointerException e) {
+                    Log.e(TAG, "Fetching partial bitmap failed, trying old method", e);
                 }
                 if (bitmap == null) {
                     drawable = wm.getDrawable();
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index ebab122..24b5b02 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -18,7 +18,7 @@
     xmlns:tools="http://schemas.android.com/tools"
     package="com.android.launcher3.tests">
 
-    <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="21"
+    <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="25"
               tools:overrideLibrary="android.support.test.uiautomator.v18"/>
 
     <application android:debuggable="true">
diff --git a/tests/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithmTest.java b/tests/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithmTest.java
index afbedba..293b04a 100644
--- a/tests/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithmTest.java
+++ b/tests/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithmTest.java
@@ -20,15 +20,14 @@
 
 import android.content.ComponentName;
 
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
 import com.android.launcher3.AppInfo;
-import com.android.launcher3.Utilities;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
 /**
  * Unit tests for {@link DefaultAppSearchAlgorithm}
  */
@@ -78,9 +77,6 @@
 
     @Test
     public void testMatchesVN() {
-        if (!Utilities.ATLEAST_NOUGAT) {
-            return;
-        }
         assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("다운로드"), "다", MATCHER));
         assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("드라이브"), "드", MATCHER));
         assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("다운로드 드라이브"), "ㄷ", MATCHER));
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 8c03e4b..02f5502 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -123,7 +123,7 @@
                 public void evaluate() throws Throwable {
                     try {
                         // Create launcher activity if necessary and bring it to the front.
-                        mDevice.pressHome();
+                        mLauncher.pressHome();
                         waitForLauncherCondition("Launcher activity wasn't created",
                                 launcher -> launcher != null);
 
@@ -216,11 +216,6 @@
     }
 
     protected void resetLoaderState() {
-        if (com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
-                && com.android.launcher3.Utilities.IS_DEBUG_DEVICE) {
-            android.util.Log.d("b/117332845",
-                    "START " + android.util.Log.getStackTraceString(new Throwable()));
-        }
         try {
             mMainThreadExecutor.execute(new Runnable() {
                 @Override
@@ -232,11 +227,6 @@
             throw new IllegalArgumentException(t);
         }
         waitForModelLoaded();
-        if (com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
-                && com.android.launcher3.Utilities.IS_DEBUG_DEVICE) {
-            android.util.Log.d("b/117332845",
-                    "FINISH " + android.util.Log.getStackTraceString(new Throwable()));
-        }
     }
 
     protected void waitForModelLoaded() {
@@ -358,4 +348,17 @@
         waitForLauncherCondition(
                 "Launcher still active", launcher -> launcher == null, DEFAULT_UI_TIMEOUT);
     }
+
+    protected boolean isInBackground(Launcher launcher) {
+        return !launcher.hasBeenResumed();
+    }
+
+    protected boolean isInState(LauncherState state) {
+        if (!TestHelpers.isInLauncherProcess()) return true;
+        return getFromLauncher(launcher -> launcher.getStateManager().getState() == state);
+    }
+
+    protected int getAllAppsScroll(Launcher launcher) {
+        return launcher.getAppsView().getActiveRecyclerView().getCurrentScrollY();
+    }
 }
diff --git a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
index 9354862..fdf87be 100644
--- a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
+++ b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
@@ -21,14 +21,12 @@
         LauncherActivityInfo settingsApp = getSettingsApp();
 
         clearHomescreen();
-        mDevice.pressHome();
-        mDevice.waitForIdle();
 
         final String appName = settingsApp.getLabel().toString();
         // 1. Open all apps and wait for load complete.
         // 2. Drag icon to homescreen.
         // 3. Verify that the icon works on homescreen.
-        mLauncher.getWorkspace().
+        mLauncher.pressHome().
                 switchToAllApps().
                 getAppIcon(appName).
                 dragToWorkspace().
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
new file mode 100644
index 0000000..ab5761d
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.ui;
+
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Intent;
+import android.util.Log;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.UiDevice;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.tapl.AllApps;
+import com.android.launcher3.tapl.AppIcon;
+import com.android.launcher3.tapl.Widgets;
+import com.android.launcher3.tapl.Workspace;
+import com.android.launcher3.views.OptionsPopupView;
+import com.android.launcher3.widget.WidgetsFullSheet;
+import com.android.launcher3.widget.WidgetsRecyclerView;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class TaplTestsLauncher3 extends AbstractLauncherUiTest {
+    private static final String TAG = "TaplTestsAosp";
+
+    private static int sScreenshotCount = 0;
+
+    public static class FailureWatcher extends TestWatcher {
+        private UiDevice mDevice;
+
+        public FailureWatcher(UiDevice device) {
+            this.mDevice = device;
+        }
+
+        private void dumpViewHierarchy() {
+            final ByteArrayOutputStream stream = new ByteArrayOutputStream();
+            try {
+                mDevice.dumpWindowHierarchy(stream);
+                stream.flush();
+                stream.close();
+                for (String line : stream.toString().split("\\r?\\n")) {
+                    Log.e(TAG, line.trim());
+                }
+            } catch (IOException e) {
+                Log.e(TAG, "error dumping XML to logcat", e);
+            }
+        }
+
+        @Override
+        protected void failed(Throwable e, Description description) {
+            if (mDevice == null) return;
+            final String pathname = getInstrumentation().getTargetContext().
+                    getFilesDir().getPath() + "/TaplTestScreenshot" + sScreenshotCount++ + ".png";
+            Log.e(TAG, "Failed test " + description.getMethodName() +
+                    ", screenshot will be saved to " + pathname +
+                    ", track trace is below, UI object dump is further below:\n" +
+                    Log.getStackTraceString(e));
+            dumpViewHierarchy();
+            mDevice.takeScreenshot(new File(pathname));
+        }
+    }
+
+    @Rule
+    public TestWatcher mFailureWatcher = new FailureWatcher(mDevice);
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        clearLauncherData();
+
+        mLauncher.pressHome();
+        waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL);
+        waitForResumed("Launcher internal state is still Background");
+    }
+
+    // Please don't add negative test cases for methods that fail only after a long wait.
+    public static void expectFail(String message, Runnable action) {
+        boolean failed = false;
+        try {
+            action.run();
+        } catch (AssertionError e) {
+            failed = true;
+        }
+        assertTrue(message, failed);
+    }
+
+    private boolean isWorkspaceScrollable(Launcher launcher) {
+        return launcher.getWorkspace().getPageCount() > 1;
+    }
+
+    private int getCurrentWorkspacePage(Launcher launcher) {
+        return launcher.getWorkspace().getCurrentPage();
+    }
+
+    private WidgetsRecyclerView getWidgetsView(Launcher launcher) {
+        return WidgetsFullSheet.getWidgetsView(launcher);
+    }
+
+    @Test
+    public void testDevicePressMenu() throws Exception {
+        mDevice.pressMenu();
+        mDevice.waitForIdle();
+        executeOnLauncher(
+                launcher -> assertTrue("Launcher internal state didn't switch to Showing Menu",
+                        OptionsPopupView.getOptionsPopup(launcher) != null));
+    }
+
+    public static void runAllAppsTest(AbstractLauncherUiTest test, AllApps allApps) {
+        assertNotNull("allApps parameter is null", allApps);
+
+        assertTrue(
+                "Launcher internal state is not All Apps", test.isInState(LauncherState.ALL_APPS));
+
+        // Test flinging forward and backward.
+        test.executeOnLauncher(launcher -> assertEquals(
+                "All Apps started in already scrolled state", 0, test.getAllAppsScroll(launcher)));
+
+        allApps.flingForward();
+        assertTrue("Launcher internal state is not All Apps",
+                test.isInState(LauncherState.ALL_APPS));
+        final Integer flingForwardY = test.getFromLauncher(
+                launcher -> test.getAllAppsScroll(launcher));
+        test.executeOnLauncher(
+                launcher -> assertTrue("flingForward() didn't scroll App Apps", flingForwardY > 0));
+
+        allApps.flingBackward();
+        assertTrue(
+                "Launcher internal state is not All Apps", test.isInState(LauncherState.ALL_APPS));
+        final Integer flingBackwardY = test.getFromLauncher(
+                launcher -> test.getAllAppsScroll(launcher));
+        test.executeOnLauncher(launcher -> assertTrue("flingBackward() didn't scroll App Apps",
+                flingBackwardY < flingForwardY));
+
+        // Test scrolling down to YouTube.
+        assertNotNull("All apps: can't fine YouTube", allApps.getAppIcon("YouTube"));
+        // Test scrolling up to Camera.
+        assertNotNull("All apps: can't fine Camera", allApps.getAppIcon("Camera"));
+        // Test failing to find a non-existing app.
+        final AllApps allAppsFinal = allApps;
+        expectFail("All apps: could find a non-existing app",
+                () -> allAppsFinal.getAppIcon("NO APP"));
+
+        assertTrue(
+                "Launcher internal state is not All Apps", test.isInState(LauncherState.ALL_APPS));
+    }
+
+    @Test
+    @PortraitLandscape
+    public void testWorkspaceSwitchToAllApps() {
+        assertNotNull("switchToAllApps() returned null",
+                mLauncher.getWorkspace().switchToAllApps());
+        assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS));
+    }
+
+    @Test
+    public void testWorkspace() throws Exception {
+        final Workspace workspace = mLauncher.getWorkspace();
+
+        // Test that ensureWorkspaceIsScrollable adds a page by dragging an icon there.
+        executeOnLauncher(launcher -> assertFalse("Initial workspace state is scrollable",
+                isWorkspaceScrollable(launcher)));
+        assertNull("Messages app was found on empty workspace",
+                workspace.tryGetWorkspaceAppIcon("Messages"));
+
+        workspace.ensureWorkspaceIsScrollable();
+
+        executeOnLauncher(
+                launcher -> assertEquals("Ensuring workspace scrollable didn't switch to page #1",
+                        1, getCurrentWorkspacePage(launcher)));
+        executeOnLauncher(
+                launcher -> assertTrue("ensureScrollable didn't make workspace scrollable",
+                        isWorkspaceScrollable(launcher)));
+        assertNotNull("ensureScrollable didn't add Messages app",
+                workspace.tryGetWorkspaceAppIcon("Messages"));
+
+        // Test flinging workspace.
+        workspace.flingBackward();
+        assertTrue("Launcher internal state is not Home", isInState(LauncherState.NORMAL));
+        executeOnLauncher(
+                launcher -> assertEquals("Flinging back didn't switch workspace to page #0",
+                        0, getCurrentWorkspacePage(launcher)));
+
+        workspace.flingForward();
+        executeOnLauncher(
+                launcher -> assertEquals("Flinging forward didn't switch workspace to page #1",
+                        1, getCurrentWorkspacePage(launcher)));
+        assertTrue("Launcher internal state is not Home", isInState(LauncherState.NORMAL));
+
+        // Test starting a workspace app.
+        final AppIcon app = workspace.tryGetWorkspaceAppIcon("Messages");
+        assertNotNull("No Messages app in workspace", app);
+        assertNotNull("AppIcon.launch returned null",
+                app.launch(resolveSystemApp(Intent.CATEGORY_APP_MESSAGING)));
+        executeOnLauncher(launcher -> assertTrue(
+                "Launcher activity is the top activity; expecting another activity to be the top "
+                        + "one",
+                isInBackground(launcher)));
+    }
+
+    public static void runIconLaunchFromAllAppsTest(AbstractLauncherUiTest test, AllApps allApps) {
+        final AppIcon app = allApps.getAppIcon("Calculator");
+        assertNotNull("AppIcon.launch returned null", app.launch(
+                test.resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)));
+        test.executeOnLauncher(launcher -> assertTrue(
+                "Launcher activity is the top activity; expecting another activity to be the top "
+                        + "one",
+                test.isInBackground(launcher)));
+    }
+
+    @Test
+    @PortraitLandscape
+    public void testAppIconLaunchFromAllAppsFromHome() throws Exception {
+        final AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
+        assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS));
+
+        runIconLaunchFromAllAppsTest(this, allApps);
+    }
+
+    @Test
+    @PortraitLandscape
+    public void testWidgets() throws Exception {
+        // Test opening widgets.
+        executeOnLauncher(launcher ->
+                assertTrue("Widgets is initially opened", getWidgetsView(launcher) == null));
+        Widgets widgets = mLauncher.getWorkspace().openAllWidgets();
+        assertNotNull("openAllWidgets() returned null", widgets);
+        widgets = mLauncher.getAllWidgets();
+        assertNotNull("getAllWidgets() returned null", widgets);
+        executeOnLauncher(launcher ->
+                assertTrue("Widgets is not shown", getWidgetsView(launcher).isShown()));
+        executeOnLauncher(launcher -> assertEquals("Widgets is scrolled upon opening",
+                0, getWidgetsScroll(launcher)));
+
+        // Test flinging widgets.
+        widgets.flingForward();
+        Integer flingForwardY = getFromLauncher(launcher -> getWidgetsScroll(launcher));
+        executeOnLauncher(launcher -> assertTrue("Flinging forward didn't scroll widgets",
+                flingForwardY > 0));
+
+        widgets.flingBackward();
+        executeOnLauncher(launcher -> assertTrue("Flinging backward didn't scroll widgets",
+                getWidgetsScroll(launcher) < flingForwardY));
+
+        mLauncher.pressHome();
+        waitForLauncherCondition("Widgets were not closed",
+                launcher -> getWidgetsView(launcher) == null);
+    }
+
+    private int getWidgetsScroll(Launcher launcher) {
+        return getWidgetsView(launcher).getCurrentScrollY();
+    }
+}
diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index 8b8e436..fbb4f51 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -84,11 +84,6 @@
     @Override
     @Before
     public void setUp() throws Exception {
-        if (com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
-                && com.android.launcher3.Utilities.IS_DEBUG_DEVICE) {
-            android.util.Log.d("b/117332845",
-                    android.util.Log.getStackTraceString(new Throwable()));
-        }
         super.setUp();
 
         mResolver = mTargetContext.getContentResolver();
@@ -110,11 +105,6 @@
         }
 
         super.tearDown();
-        if (com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
-                && com.android.launcher3.Utilities.IS_DEBUG_DEVICE) {
-            android.util.Log.d("b/117332845",
-                    android.util.Log.getStackTraceString(new Throwable()));
-        }
     }
 
     @Test
@@ -157,10 +147,6 @@
 
     @Test
     public void testPendingWidget_autoRestored() {
-        if (com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS && com.android.launcher3.Utilities.IS_DEBUG_DEVICE) {
-            android.util.Log.d("b/117332845",
-                    "Test Started @ " + android.util.Log.getStackTraceString(new Throwable()));
-        }
         // A non-restored widget with no config screen gets restored automatically.
         LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
 
@@ -170,21 +156,10 @@
 
         setupContents(item);
         verifyWidgetPresent(info);
-
-        if (com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
-                && com.android.launcher3.Utilities.IS_DEBUG_DEVICE) {
-            android.util.Log.d("b/117332845",
-                    "Test Ended @ " + android.util.Log.getStackTraceString(new Throwable()));
-        }
     }
 
     @Test
     public void testPendingWidget_withConfigScreen() {
-        if (com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
-                && com.android.launcher3.Utilities.IS_DEBUG_DEVICE) {
-            android.util.Log.d("b/117332845",
-                    "Test Started @ " + android.util.Log.getStackTraceString(new Throwable()));
-        }
         // A non-restored widget with config screen get bound and shows a 'Click to setup' UI.
         LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, true);
 
@@ -206,11 +181,6 @@
         assertNotNull(AppWidgetManager.getInstance(mTargetContext)
                 .getAppWidgetInfo(mCursor.getInt(mCursor.getColumnIndex(
                         LauncherSettings.Favorites.APPWIDGET_ID))));
-        if (com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
-                && com.android.launcher3.Utilities.IS_DEBUG_DEVICE) {
-            android.util.Log.d("b/117332845",
-                    "Test Ended @ " + android.util.Log.getStackTraceString(new Throwable()));
-        }
     }
 
     @Test @Ignore
diff --git a/tests/src/com/android/launcher3/util/RaceConditionReproducer.java b/tests/src/com/android/launcher3/util/RaceConditionReproducer.java
index 316e40d..0235f95 100644
--- a/tests/src/com/android/launcher3/util/RaceConditionReproducer.java
+++ b/tests/src/com/android/launcher3/util/RaceConditionReproducer.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3.util;
 
+import static com.android.launcher3.util.RaceConditionTracker.ENTER_POSTFIX;
+import static com.android.launcher3.util.RaceConditionTracker.EXIT_POSTFIX;
+
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -46,7 +49,7 @@
  * If an event A occurs before event B in the sequence, this is how execution order looks like:
  * Events: ... A ... B ...
  * Events and instructions, guaranteed order:
- *   (instructions executed prior to A) A ... B (instructions executed after B)
+ * (instructions executed prior to A) A ... B (instructions executed after B)
  *
  * Each iteration has 3 parts (phases).
  * Phase 1. Picking a previously seen event subsequence that we believe can have previously unseen
@@ -58,6 +61,8 @@
  * Phase 3. Releasing all threads and letting the test iteration run till its end.
  *
  * The iterations end when all seen paths have been declared “uncontinuable”.
+ *
+ * When we register event XXX:enter, we hold all other events until we register XXX:exit.
  */
 public class RaceConditionReproducer implements RaceConditionTracker.EventProcessor {
     private static final String TAG = "RaceConditionReproducer";
@@ -81,7 +86,7 @@
         private final Map<String, EventNode> mNextEvents = new HashMap<>();
         // Whether we believe that further iterations will not be able to add more events to
         // mNextEvents.
-        private boolean mStoppedAddingChildren = false;
+        private boolean mStoppedAddingChildren = true;
 
         private void debugDump(StringBuilder sb, int indent, String name) {
             for (int i = 0; i < indent; ++i) sb.append('.');
@@ -134,6 +139,8 @@
                 }
             }
             if (!mStoppedAddingChildren) {
+                // Mark that we have finished adding children. It will remain true if no new
+                // children are added, or will be set to false upon adding a new child.
                 mStoppedAddingChildren = true;
                 return true;
             }
@@ -216,6 +223,7 @@
         RaceConditionTracker.setEventProcessor(null);
         runResumeAllEventsCallbackLocked();
         assertTrue("Non-empty postponed events", mPostponedEvents.isEmpty());
+        assertTrue("Last registered event is :enter", lastEventAsEnter() == null);
 
         // No events came after mLastRegisteredEvent. It doesn't make sense to come to it again
         // because we won't see new continuations.
@@ -246,12 +254,36 @@
     }
 
     /**
+     * Returns whether the last event was not an XXX:enter, or this event is a matching XXX:exit.
+     */
+    private boolean canRegisterEventNowLocked(String event) {
+        final String lastEventAsEnter = lastEventAsEnter();
+        final String thisEventAsExit = eventAsExit(event);
+
+        if (lastEventAsEnter != null) {
+            if (!lastEventAsEnter.equals(thisEventAsExit)) {
+                assertTrue("YYY:exit after XXX:enter", thisEventAsExit == null);
+                // Last event was :enter, but this event is not :exit.
+                return false;
+            }
+        } else {
+            // Previous event was not :enter.
+            assertTrue(":exit after a non-enter event", thisEventAsExit == null);
+        }
+        return true;
+    }
+
+    /**
      * Registers an event issued by the app and returns null or decides that the event must be
      * postponed, and returns an object to wait on.
      */
     private synchronized Semaphore tryRegisterEvent(String event) {
         Log.d(TAG, "Event issued by the app: " + event);
 
+        if (!canRegisterEventNowLocked(event)) {
+            return createWaitObjectForPostponedEventLocked(event);
+        }
+
         if (mRegisteredEventCount < mSequenceToFollow.size()) {
             // We are in the first part of the iteration. We only register events that follow the
             // mSequenceToFollow and postponing all other events.
@@ -288,9 +320,14 @@
                 return createWaitObjectForPostponedEventLocked(event);
             }
         } else {
-            // The second phase of the iteration. We are past the growth point and register
+            // The third phase of the iteration. We are past the growth point and register
             // everything that comes.
             registerEventLocked(event);
+            // Register events that may have been postponed while waiting for an :exit event
+            // during the third phase. We don't do this if just registered event is :enter.
+            if (eventAsEnter(event) == null && mRegisteredEventCount > mSequenceToFollow.size()) {
+                registerPostponedEventsLocked(new HashSet<>(mPostponedEvents.keySet()));
+            }
         }
         return null;
     }
@@ -347,6 +384,11 @@
     private void registerPostponedEventsLocked(Collection<String> events) {
         for (String event : events) {
             registerPostponedEventLocked(event);
+            if (eventAsEnter(event) != null) {
+                // Once :enter is registered, switch to waiting for :exit to come. Won't register
+                // other postponed events.
+                break;
+            }
         }
     }
 
@@ -355,14 +397,51 @@
         registerEventLocked(event);
     }
 
+    /**
+     * If the last registered event was XXX:enter, returns XXX, otherwise, null.
+     */
+    private String lastEventAsEnter() {
+        return eventAsEnter(mCurrentSequence.substring(mCurrentSequence.lastIndexOf("|") + 1));
+    }
+
+    /**
+     * If the event is XXX:postfix, returns XXX, otherwise, null.
+     */
+    private static String prefixFromPostfixedEvent(String event, String postfix) {
+        final int columnPos = event.indexOf(':');
+        if (columnPos != -1 && postfix.equals(event.substring(columnPos + 1))) {
+            return event.substring(0, columnPos);
+        }
+        return null;
+    }
+
+    /**
+     * If the event is XXX:enter, returns XXX, otherwise, null.
+     */
+    private static String eventAsEnter(String event) {
+        return prefixFromPostfixedEvent(event, ENTER_POSTFIX);
+    }
+
+    /**
+     * If the event is XXX:exit, returns XXX, otherwise, null.
+     */
+    private static String eventAsExit(String event) {
+        return prefixFromPostfixedEvent(event, EXIT_POSTFIX);
+    }
+
     private void registerEventLocked(String event) {
+        assertTrue(canRegisterEventNowLocked(event));
+
         Log.d(TAG, "Actually registering event: " + event);
         EventNode next = mLastRegisteredEvent.mNextEvents.get(event);
         if (next == null) {
             // This event wasn't seen after mLastRegisteredEvent.
             next = new EventNode();
             mLastRegisteredEvent.mNextEvents.put(event, next);
-            mLastRegisteredEvent.mStoppedAddingChildren = false;
+            // The fact that we've added a new event after the previous one means that the
+            // previous event is still a growth point, unless this event is :exit, which means
+            // that the previous event is :enter.
+            mLastRegisteredEvent.mStoppedAddingChildren = eventAsExit(event) != null;
         }
 
         mLastRegisteredEvent = next;
@@ -371,12 +450,6 @@
         if (mCurrentSequence.length() > 0) mCurrentSequence.append("|");
         mCurrentSequence.append(event);
         Log.d(TAG, "Repro sequence: " + mCurrentSequence);
-
-        if (mRegisteredEventCount == mSequenceToFollow.size() + 1) {
-            // We just entered the third phase of the iteration, i.e. registered an event after
-            // the growth point. Now we can let go of all postponed events.
-            runResumeAllEventsCallbackLocked();
-        }
     }
 
     private void runResumeAllEventsCallbackLocked() {
diff --git a/tests/src/com/android/launcher3/util/RaceConditionReproducerTest.java b/tests/src/com/android/launcher3/util/RaceConditionReproducerTest.java
index 7dde5cc..3fc268e 100644
--- a/tests/src/com/android/launcher3/util/RaceConditionReproducerTest.java
+++ b/tests/src/com/android/launcher3/util/RaceConditionReproducerTest.java
@@ -77,6 +77,46 @@
     }
 
     @Test
+    @Ignore // The test is too long for continuous testing.
+    // 2 threads, 3 events, including enter-exit pairs each.
+    public void test3_3_enter_exit() throws Exception {
+        final RaceConditionReproducer eventProcessor = new RaceConditionReproducer();
+        boolean sawTheValidSequence = false;
+
+        for (; ; ) {
+            eventProcessor.startIteration();
+            Thread tb = new Thread(() -> {
+                RaceConditionTracker.onEvent("B1:enter");
+                RaceConditionTracker.onEvent("B1:exit");
+                RaceConditionTracker.onEvent("B2");
+                RaceConditionTracker.onEvent("B3:enter");
+                RaceConditionTracker.onEvent("B3:exit");
+            });
+            tb.start();
+
+            RaceConditionTracker.onEvent("A1");
+            RaceConditionTracker.onEvent("A2:enter");
+            RaceConditionTracker.onEvent("A2:exit");
+            RaceConditionTracker.onEvent("A3:enter");
+            RaceConditionTracker.onEvent("A3:exit");
+
+            tb.join();
+            final boolean needMoreIterations = eventProcessor.finishIteration();
+
+            sawTheValidSequence = sawTheValidSequence ||
+                    "B1:enter|B1:exit|A1|A2:enter|A2:exit|B2|A3:enter|A3:exit|B3:enter|B3:exit".
+                            equals(eventProcessor.getCurrentSequenceString());
+
+            if (!needMoreIterations) break;
+        }
+
+        assertEquals("Wrong number of leaf nodes",
+                factorial(3 + 3) / (factorial(3) * factorial(3)),
+                eventProcessor.numberOfLeafNodes());
+        assertTrue(sawTheValidSequence);
+    }
+
+    @Test
     // 2 threads, 3 events each; reproducing a particular event sequence.
     public void test3_3_ReproMode() throws Exception {
         final RaceConditionReproducer eventProcessor = new RaceConditionReproducer(
@@ -122,4 +162,42 @@
                 factorial(2 + 2 + 1) / (factorial(2) * factorial(2) * factorial(1)),
                 eventProcessor.numberOfLeafNodes());
     }
+
+    @Test
+    @Ignore // The test is too long for continuous testing.
+    // 2 threads with 2 events; 1 thread with 1 event. Includes enter-exit pairs.
+    public void test2_1_2_enter_exit() throws Exception {
+        final RaceConditionReproducer eventProcessor = new RaceConditionReproducer();
+
+        for (; ; ) {
+            eventProcessor.startIteration();
+            Thread tb = new Thread(() -> {
+                RaceConditionTracker.onEvent("B1:enter");
+                RaceConditionTracker.onEvent("B1:exit");
+                RaceConditionTracker.onEvent("B2:enter");
+                RaceConditionTracker.onEvent("B2:exit");
+            });
+            tb.start();
+
+            Thread tc = new Thread(() -> {
+                RaceConditionTracker.onEvent("C1:enter");
+                RaceConditionTracker.onEvent("C1:exit");
+            });
+            tc.start();
+
+            RaceConditionTracker.onEvent("A1:enter");
+            RaceConditionTracker.onEvent("A1:exit");
+            RaceConditionTracker.onEvent("A2:enter");
+            RaceConditionTracker.onEvent("A2:exit");
+
+            tb.join();
+            tc.join();
+
+            if (!eventProcessor.finishIteration()) break;
+        }
+
+        assertEquals("Wrong number of leaf nodes",
+                factorial(2 + 2 + 1) / (factorial(2) * factorial(2) * factorial(1)),
+                eventProcessor.numberOfLeafNodes());
+    }
 }
diff --git a/tests/src/com/android/launcher3/util/Wait.java b/tests/src/com/android/launcher3/util/Wait.java
index 0e41c02..593cce8 100644
--- a/tests/src/com/android/launcher3/util/Wait.java
+++ b/tests/src/com/android/launcher3/util/Wait.java
@@ -23,7 +23,7 @@
                     return;
                 }
             } catch (Throwable t) {
-                // Ignore
+                throw new RuntimeException(t);
             }
             SystemClock.sleep(sleepMillis);
         }
@@ -34,7 +34,7 @@
                 return;
             }
         } catch (Throwable t) {
-            // Ignore
+            throw new RuntimeException(t);
         }
         Assert.fail(message);
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index 3ffd30c..d39a38e 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.tapl;
 
 import android.graphics.Point;
+import android.view.MotionEvent;
 import android.widget.TextView;
 
 import androidx.test.uiautomator.By;
@@ -40,9 +41,10 @@
      */
     public AppIconMenu openMenu() {
         final Point iconCenter = mObject.getVisibleCenter();
-        mLauncher.longTap(iconCenter.x, iconCenter.y);
+        mLauncher.sendPointer(MotionEvent.ACTION_DOWN, iconCenter);
         final UiObject2 deepShortcutsContainer = mLauncher.waitForLauncherObject(
                 "deep_shortcuts_container");
+        mLauncher.sendPointer(MotionEvent.ACTION_UP, iconCenter);
         return new AppIconMenu(mLauncher, deepShortcutsContainer);
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index 5f60113..0ff3070 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.tapl;
 
 import androidx.annotation.NonNull;
+import androidx.test.uiautomator.BySelector;
 import androidx.test.uiautomator.Direction;
 import androidx.test.uiautomator.UiObject2;
 
@@ -28,6 +29,7 @@
  */
 public class BaseOverview extends LauncherInstrumentation.VisibleContainer {
     private static final int DEFAULT_FLING_SPEED = 15000;
+    private static final int FLINGS_FOR_DISMISS_LIMIT = 5;
 
     BaseOverview(LauncherInstrumentation launcher) {
         super(launcher);
@@ -50,6 +52,22 @@
     }
 
     /**
+     * Dismissed all tasks by scrolling to Clear-all button and pressing it.
+     */
+    public Workspace dismissAllTasks() {
+        final BySelector clearAllSelector = mLauncher.getLauncherObjectSelector("clear_all");
+        for (int i = 0;
+                i < FLINGS_FOR_DISMISS_LIMIT
+                        && verifyActiveContainer().findObject(clearAllSelector) == null;
+                ++i) {
+            flingForward();
+        }
+
+        mLauncher.getObjectInContainer(verifyActiveContainer(), clearAllSelector).click();
+        return new Workspace(mLauncher);
+    }
+
+    /**
      * Flings backward (right) and waits the fling's end.
      */
     public void flingBackward() {
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 49bd73a..444f3bd 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -21,10 +21,13 @@
 import android.app.ActivityManager;
 import android.app.Instrumentation;
 import android.app.UiAutomation;
+import android.graphics.Point;
 import android.os.Bundle;
 import android.os.Parcelable;
+import android.os.SystemClock;
 import android.provider.Settings;
 import android.util.Log;
+import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.accessibility.AccessibilityEvent;
 
@@ -403,11 +406,6 @@
         return mDevice;
     }
 
-    void longTap(int x, int y) {
-        mDevice.drag(x, y, x, y, 0);
-    }
-
-
     void swipe(int startX, int startY, int endX, int endY) {
         executeAndWaitForEvent(
                 () -> mDevice.swipe(startX, startY, endX, endY, 60),
@@ -419,4 +417,11 @@
     void waitForIdle() {
         mDevice.waitForIdle();
     }
+
+    void sendPointer(int action, Point point) {
+        final MotionEvent event = MotionEvent.obtain(SystemClock.uptimeMillis(),
+                SystemClock.uptimeMillis(), action, point.x, point.y, 0);
+        mInstrumentation.sendPointerSync(event);
+        event.recycle();
+    }
 }
\ No newline at end of file