Merge "Import translations. DO NOT MERGE" into ub-launcher3-qt-dev
diff --git a/Android.bp b/Android.bp
index 5acec37..b80282e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -24,6 +24,7 @@
     srcs: [
         "tests/tapl/**/*.java",
         "src/com/android/launcher3/util/SecureSettingsObserver.java",
+        "src/com/android/launcher3/ResourceUtils.java",
         "src/com/android/launcher3/TestProtocol.java",
     ],
     manifest: "tests/tapl/AndroidManifest.xml",
diff --git a/go/quickstep/res/values-sw480dp/dimens.xml b/go/quickstep/res/values-sw480dp/dimens.xml
index b48dafb..571b8a1 100644
--- a/go/quickstep/res/values-sw480dp/dimens.xml
+++ b/go/quickstep/res/values-sw480dp/dimens.xml
@@ -18,13 +18,13 @@
     <dimen name="recents_list_width">480dp</dimen>
 
     <dimen name="task_item_height">90dp</dimen>
-    <dimen name="task_item_top_margin">16dp</dimen>
-    <dimen name="task_thumbnail_icon_horiz_margin">20dp</dimen>
+    <dimen name="task_item_top_margin">24dp</dimen>
+    <dimen name="task_thumbnail_icon_horiz_margin">24dp</dimen>
 
     <dimen name="task_thumbnail_corner_radius">4dp</dimen>
 
-    <dimen name="clear_all_item_view_height">48dp</dimen>
+    <dimen name="clear_all_item_view_height">52dp</dimen>
     <dimen name="clear_all_item_view_top_margin">28dp</dimen>
     <dimen name="clear_all_item_view_bottom_margin">28dp</dimen>
-    <dimen name="clear_all_button_width">140dp</dimen>
+    <dimen name="clear_all_button_width">160dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java b/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java
index d189c50..bcb1f5c 100644
--- a/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java
+++ b/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java
@@ -1,16 +1,20 @@
 package com.android.launcher3;
 
+import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
 import static com.android.quickstep.views.IconRecentsView.CONTENT_ALPHA;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
 
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
-import android.app.ActivityOptions;
 import android.content.Context;
+import android.os.Handler;
 import android.view.View;
 
 import com.android.quickstep.views.IconRecentsView;
+import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 /**
@@ -29,6 +33,12 @@
     }
 
     @Override
+    RemoteAnimationRunnerCompat getWallpaperOpenRunner(boolean fromUnlock) {
+        return new GoWallpaperOpenLauncherAnimationRunner(mHandler,
+                false /* startAtFrontOfQueue */, fromUnlock);
+    }
+
+    @Override
     protected void composeRecentsLaunchAnimator(AnimatorSet anim, View v,
             RemoteAnimationTargetCompat[] targets, boolean launcherClosing) {
         // Stubbed. Recents launch animation will come from the recents view itself and will not
@@ -51,4 +61,34 @@
 
         return mLauncher.getStateManager()::reapplyState;
     }
+
+    /**
+     * Remote animation runner for animation from app to Launcher. For Go, when going to recents,
+     * we need to ensure that the recents view is ready for remote animation before starting.
+     */
+    private final class GoWallpaperOpenLauncherAnimationRunner extends
+            WallpaperOpenLauncherAnimationRunner {
+        public GoWallpaperOpenLauncherAnimationRunner(Handler handler, boolean startAtFrontOfQueue,
+                boolean fromUnlock) {
+            super(handler, startAtFrontOfQueue, fromUnlock);
+        }
+
+        @Override
+        public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
+                AnimationResult result) {
+            boolean isGoingToRecents =
+                    taskIsATargetWithMode(targetCompats, mLauncher.getTaskId(), MODE_OPENING)
+                    && (mLauncher.getStateManager().getState() == LauncherState.OVERVIEW);
+            if (isGoingToRecents) {
+                IconRecentsView recentsView = mLauncher.getOverviewPanel();
+                if (!recentsView.isReadyForRemoteAnim()) {
+                    recentsView.setOnReadyForRemoteAnimCallback(() ->
+                        postAsyncCallback(mHandler, () -> onCreateAnimation(targetCompats, result))
+                    );
+                    return;
+                }
+            }
+            super.onCreateAnimation(targetCompats, result);
+        }
+    }
 }
diff --git a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index c228bb9..62e40d1 100644
--- a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -15,30 +15,20 @@
  */
 package com.android.quickstep;
 
-import static com.android.launcher3.anim.Interpolators.ACCEL_2;
-import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.quickstep.util.RemoteAnimationProvider.getLayer;
+import static com.android.quickstep.views.IconRecentsView.REMOTE_APP_TO_OVERVIEW_DURATION;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
 
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
-import android.graphics.Matrix;
-import android.graphics.Rect;
 import android.util.Log;
-import android.view.View;
-
-import androidx.annotation.NonNull;
 
 import com.android.launcher3.BaseDraggingActivity;
-import com.android.quickstep.util.MultiValueUpdateListener;
 import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
 import com.android.quickstep.views.IconRecentsView;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
 
 /**
  * Provider for the atomic remote window animation from the app to the overview.
@@ -47,9 +37,6 @@
  */
 final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> implements
         RemoteAnimationProvider {
-
-    private static final long APP_TO_THUMBNAIL_FADE_DURATION = 50;
-    private static final long APP_SCALE_DOWN_DURATION = 400;
     private static final String TAG = "AppToOverviewAnimationProvider";
 
     private final ActivityControlHelper<T> mHelper;
@@ -131,106 +118,17 @@
             return anim;
         }
 
-        View thumbnailView = mRecentsView.getBottomThumbnailView();
-        if (thumbnailView == null) {
-            // This can be null if there were previously 0 tasks and the recycler view has not had
-            // enough time to take in the data change, bind a new view, and lay out the new view.
-            // TODO: Have a fallback to animate to
-            if (Log.isLoggable(TAG, Log.WARN)) {
-                Log.w(TAG, "No thumbnail view for running task. Using stub animation.");
-            }
-            anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration()));
-            return anim;
-        }
-
-        playAppScaleDownAnim(anim, closingAppTarget, recentsTarget, thumbnailView);
+        mRecentsView.playRemoteAppToRecentsAnimation(anim, closingAppTarget, recentsTarget);
 
         return anim;
     }
 
     /**
-     * Animate a closing app to scale down to the location of the thumbnail view in recents.
-     *
-     * @param anim animator set
-     * @param appTarget the app surface thats closing
-     * @param recentsTarget the surface containing recents
-     * @param thumbnailView the thumbnail view to animate to
-     */
-    private void playAppScaleDownAnim(@NonNull AnimatorSet anim,
-            @NonNull RemoteAnimationTargetCompat appTarget,
-            @NonNull RemoteAnimationTargetCompat recentsTarget, @NonNull View thumbnailView) {
-
-        // Identify where the entering remote app should animate to.
-        Rect endRect = new Rect();
-        thumbnailView.getGlobalVisibleRect(endRect);
-
-        Rect appBounds = appTarget.sourceContainerBounds;
-
-        ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 1);
-        valueAnimator.setDuration(APP_SCALE_DOWN_DURATION);
-
-        SyncRtSurfaceTransactionApplierCompat surfaceApplier =
-                new SyncRtSurfaceTransactionApplierCompat(thumbnailView);
-
-        // Keep recents visible throughout the animation.
-        SurfaceParams[] params = new SurfaceParams[2];
-        // Closing app should stay on top.
-        int boostedMode = MODE_CLOSING;
-        params[0] = new SurfaceParams(recentsTarget.leash, 1f, null /* matrix */,
-                null /* windowCrop */, getLayer(recentsTarget, boostedMode), 0 /* cornerRadius */);
-
-        valueAnimator.addUpdateListener(new MultiValueUpdateListener() {
-            private final FloatProp mScaleX;
-            private final FloatProp mScaleY;
-            private final FloatProp mTranslationX;
-            private final FloatProp mTranslationY;
-            private final FloatProp mAlpha;
-
-            {
-                // Scale down and move to view location.
-                float endScaleX = ((float) endRect.width()) / appBounds.width();
-                mScaleX = new FloatProp(1f, endScaleX, 0, APP_SCALE_DOWN_DURATION,
-                        ACCEL_DEACCEL);
-                float endScaleY = ((float) endRect.height()) / appBounds.height();
-                mScaleY = new FloatProp(1f, endScaleY, 0, APP_SCALE_DOWN_DURATION,
-                        ACCEL_DEACCEL);
-                float endTranslationX = endRect.left -
-                        (appBounds.width() - thumbnailView.getWidth()) / 2.0f;
-                mTranslationX = new FloatProp(0, endTranslationX, 0, APP_SCALE_DOWN_DURATION,
-                        ACCEL_DEACCEL);
-                float endTranslationY = endRect.top -
-                        (appBounds.height() - thumbnailView.getHeight()) / 2.0f;
-                mTranslationY = new FloatProp(0, endTranslationY, 0, APP_SCALE_DOWN_DURATION,
-                        ACCEL_DEACCEL);
-
-                // Fade out quietly near the end to be replaced by the real view.
-                mAlpha = new FloatProp(1.0f, 0,
-                        APP_SCALE_DOWN_DURATION - APP_TO_THUMBNAIL_FADE_DURATION,
-                        APP_TO_THUMBNAIL_FADE_DURATION, ACCEL_2);
-            }
-
-            @Override
-            public void onUpdate(float percent) {
-                Matrix m = new Matrix();
-                m.setScale(mScaleX.value, mScaleY.value,
-                        appBounds.width() / 2.0f, appBounds.height() / 2.0f);
-                m.postTranslate(mTranslationX.value, mTranslationY.value);
-
-                params[1] = new SurfaceParams(appTarget.leash, mAlpha.value, m,
-                        null /* windowCrop */, getLayer(appTarget, boostedMode),
-                        0 /* cornerRadius */);
-                surfaceApplier.scheduleApply(params);
-            }
-        });
-        anim.play(valueAnimator);
-    }
-
-    /**
      * Get duration of animation from app to overview.
      *
      * @return duration of animation
      */
     long getRecentsLaunchDuration() {
-        return APP_SCALE_DOWN_DURATION;
+        return REMOTE_APP_TO_OVERVIEW_DURATION;
     }
 }
diff --git a/go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java b/go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java
index 9282345..808cd72 100644
--- a/go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java
+++ b/go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java
@@ -180,6 +180,7 @@
 
                     @Override
                     public void onAnimationEnd(Animator animation) {
+                        CONTENT_TRANSITION_PROGRESS.set(itemView, 1.0f);
                         dispatchChangeFinished(viewHolder, true /* oldItem */);
                         mRunningAnims.remove(anim);
                         dispatchFinishedWhenDone();
@@ -215,46 +216,43 @@
     @Override
     public void endAnimation(@NonNull ViewHolder item) {
         for (int i = mPendingAnims.size() - 1; i >= 0; i--) {
-            PendingAnimation pendAnim = mPendingAnims.get(i);
-            if (pendAnim.viewHolder == item) {
-                mPendingAnims.remove(i);
-                switch (pendAnim.animType) {
-                    case ANIM_TYPE_REMOVE:
-                        dispatchRemoveFinished(item);
-                        break;
-                    case ANIM_TYPE_CHANGE:
-                        dispatchChangeFinished(item, true /* oldItem */);
-                        break;
-                    default:
-                        break;
-                }
-            }
+            endPendingAnimation(mPendingAnims.get(i));
+            mPendingAnims.remove(i);
         }
         dispatchFinishedWhenDone();
     }
 
     @Override
     public void endAnimations() {
+        if (!isRunning()) {
+            return;
+        }
         for (int i = mPendingAnims.size() - 1; i >= 0; i--) {
-            PendingAnimation pendAnim = mPendingAnims.get(i);
-            ViewHolder item = pendAnim.viewHolder;
-            switch (pendAnim.animType) {
-                case ANIM_TYPE_REMOVE:
-                    dispatchRemoveFinished(item);
-                    break;
-                case ANIM_TYPE_CHANGE:
-                    dispatchChangeFinished(item, true /* oldItem */);
-                    break;
-                default:
-                    break;
-            }
+            endPendingAnimation(mPendingAnims.get(i));
             mPendingAnims.remove(i);
         }
-        for (int i = 0; i < mRunningAnims.size(); i++) {
+        for (int i = mRunningAnims.size() - 1; i >= 0; i--) {
             ObjectAnimator anim = mRunningAnims.get(i);
-            anim.end();
+            // This calls the on end animation callback which will set values to their end target.
+            anim.cancel();
         }
-        dispatchAnimationsFinished();
+        dispatchFinishedWhenDone();
+    }
+
+    private void endPendingAnimation(PendingAnimation pendAnim) {
+        ViewHolder item = pendAnim.viewHolder;
+        switch (pendAnim.animType) {
+            case ANIM_TYPE_REMOVE:
+                item.itemView.setAlpha(1.0f);
+                dispatchRemoveFinished(item);
+                break;
+            case ANIM_TYPE_CHANGE:
+                CONTENT_TRANSITION_PROGRESS.set(item.itemView, 1.0f);
+                dispatchChangeFinished(item, true /* oldItem */);
+                break;
+            default:
+                break;
+        }
     }
 
     @Override
diff --git a/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
index bba08a4..d39a66c 100644
--- a/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
+++ b/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
@@ -45,6 +45,7 @@
     @Override
     public AnimationFactory prepareRecentsUI(RecentsActivity activity, boolean activityVisible,
             boolean animateActivity, Consumer<AnimatorPlaybackController> callback) {
+        // TODO: Logic for setting remote animation
         if (activityVisible) {
             return (transitionLength) -> { };
         }
diff --git a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
index 40db7dd..d595007 100644
--- a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -40,6 +40,7 @@
             boolean activityVisible, boolean animateActivity,
             Consumer<AnimatorPlaybackController> callback) {
         LauncherState fromState = activity.getStateManager().getState();
+        activity.<IconRecentsView>getOverviewPanel().setUsingRemoteAnimation(true);
         //TODO: Implement this based off where the recents view needs to be for app => recents anim.
         return new AnimationFactory() {
             @Override
@@ -87,6 +88,7 @@
         if (launcher == null) {
             return false;
         }
+        launcher.<IconRecentsView>getOverviewPanel().setUsingRemoteAnimation(false);
         launcher.getUserEventDispatcher().logActionCommand(
                 LauncherLogProto.Action.Command.RECENTS_BUTTON,
                 getContainerType(),
diff --git a/go/quickstep/src/com/android/quickstep/RecentsActivity.java b/go/quickstep/src/com/android/quickstep/RecentsActivity.java
index 9fb8067..7f813ce 100644
--- a/go/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/go/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -42,7 +42,7 @@
 
     @Override
     protected void reapplyUi() {
-        //TODO: Implement this depending on how insets will affect the view.
+        // No-op. Insets are automatically re-applied in the root view.
     }
 
     @Override
@@ -67,8 +67,8 @@
     }
 
     @Override
-    protected void onStart() {
+    protected void onResume() {
         mIconRecentsView.onBeginTransitionToOverview();
-        super.onStart();
+        super.onResume();
     }
 }
diff --git a/go/quickstep/src/com/android/quickstep/fallback/GoRecentsActivityRootView.java b/go/quickstep/src/com/android/quickstep/fallback/GoRecentsActivityRootView.java
index c0ebcb5..b550011 100644
--- a/go/quickstep/src/com/android/quickstep/fallback/GoRecentsActivityRootView.java
+++ b/go/quickstep/src/com/android/quickstep/fallback/GoRecentsActivityRootView.java
@@ -16,7 +16,10 @@
 package com.android.quickstep.fallback;
 
 import android.content.Context;
+import android.graphics.Insets;
+import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.view.WindowInsets;
 
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.BaseDragLayer;
@@ -30,5 +33,23 @@
         super(context, attrs, 1 /* alphaChannelCount */);
         // Go leaves touch control to the view itself.
         mControllers = new TouchController[0];
+        setSystemUiVisibility(SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                | SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                | SYSTEM_UI_FLAG_LAYOUT_STABLE);
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+        if (insets.equals(mInsets)) {
+            return;
+        }
+        super.setInsets(insets);
+    }
+
+    @Override
+    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+        Insets sysInsets = insets.getSystemWindowInsets();
+        setInsets(new Rect(sysInsets.left, sysInsets.top, sysInsets.right, sysInsets.bottom));
+        return insets.consumeSystemWindowInsets();
     }
 }
diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
index 7225e57..8276078 100644
--- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
+++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
@@ -19,10 +19,14 @@
 
 import static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL;
 
+import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.quickstep.TaskAdapter.CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT;
 import static com.android.quickstep.TaskAdapter.ITEM_TYPE_CLEAR_ALL;
 import static com.android.quickstep.TaskAdapter.ITEM_TYPE_TASK;
+import static com.android.quickstep.TaskAdapter.MAX_TASKS_TO_DISPLAY;
 import static com.android.quickstep.TaskAdapter.TASKS_START_POSITION;
+import static com.android.quickstep.util.RemoteAnimationProvider.getLayer;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -32,6 +36,7 @@
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.ArraySet;
@@ -40,10 +45,12 @@
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewTreeObserver;
+import android.view.animation.PathInterpolator;
 import android.widget.FrameLayout;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.interpolator.view.animation.LinearOutSlowInInterpolator;
 import androidx.recyclerview.widget.DefaultItemAnimator;
 import androidx.recyclerview.widget.ItemTouchHelper;
 import androidx.recyclerview.widget.LinearLayoutManager;
@@ -64,7 +71,11 @@
 import com.android.quickstep.TaskHolder;
 import com.android.quickstep.TaskListLoader;
 import com.android.quickstep.TaskSwipeCallback;
+import com.android.quickstep.util.MultiValueUpdateListener;
 import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -102,6 +113,22 @@
     private static final float ITEM_ANIMATE_OUT_TRANSLATION_X_RATIO = .25f;
     private static final long CLEAR_ALL_FADE_DELAY = 120;
 
+    private static final long REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION = 300;
+    private static final long REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION = 400;
+    private static final long REMOTE_TO_RECENTS_ITEM_FADE_START_DELAY = 200;
+    private static final long REMOTE_TO_RECENTS_ITEM_FADE_DURATION = 217;
+    private static final long REMOTE_TO_RECENTS_ITEM_FADE_BETWEEN_DELAY = 33;
+
+    private static final PathInterpolator FAST_OUT_SLOW_IN_1 =
+            new PathInterpolator(.4f, 0f, 0f, 1f);
+    private static final PathInterpolator FAST_OUT_SLOW_IN_2 =
+            new PathInterpolator(.5f, 0f, 0f, 1f);
+    private static final LinearOutSlowInInterpolator OUT_SLOW_IN =
+            new LinearOutSlowInInterpolator();
+
+    public static final long REMOTE_APP_TO_OVERVIEW_DURATION =
+            REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION;
+
     /**
      * A ratio representing the view's relative placement within its padded space. For example, 0
      * is top aligned and 0.5 is centered vertically.
@@ -125,6 +152,7 @@
     private View mEmptyView;
     private View mContentView;
     private boolean mTransitionedFromApp;
+    private boolean mUsingRemoteAnimation;
     private AnimatorSet mLayoutAnimation;
     private final ArraySet<View> mLayingOutViews = new ArraySet<>();
     private Rect mInsets;
@@ -276,7 +304,9 @@
             // not be scrollable.
             mTaskLayoutManager.scrollToPositionWithOffset(TASKS_START_POSITION, 0 /* offset */);
         }
-        scheduleFadeInLayoutAnimation();
+        if (!mUsingRemoteAnimation) {
+            scheduleFadeInLayoutAnimation();
+        }
         // Load any task changes
         if (!mTaskLoader.needsToLoad()) {
             return;
@@ -315,6 +345,17 @@
     }
 
     /**
+     * Set whether we're using a custom remote animation. If so, we will not do the default layout
+     * animation when entering recents and instead wait for the remote app surface to be ready to
+     * use.
+     *
+     * @param usingRemoteAnimation true if doing a remote animation, false o/w
+     */
+    public void setUsingRemoteAnimation(boolean usingRemoteAnimation) {
+        mUsingRemoteAnimation = usingRemoteAnimation;
+    }
+
+    /**
      * Handles input from the overview button. Launch the most recent task unless we just came from
      * the app. In that case, we launch the next most recent.
      */
@@ -365,17 +406,49 @@
     }
 
     /**
-     * Get the bottom most thumbnail view to animate to.
+     * Get the bottom most task view to animate to.
      *
-     * @return the thumbnail view if laid out
+     * @return the task view
      */
-    public @Nullable View getBottomThumbnailView() {
-        ArrayList<TaskItemView> taskViews = getTaskViews();
-        if (taskViews.isEmpty()) {
-            return null;
+    private @Nullable TaskItemView getBottomTaskView() {
+        int childCount = mTaskRecyclerView.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View view = mTaskRecyclerView.getChildAt(i);
+            if (mTaskRecyclerView.getChildViewHolder(view).getItemViewType() == ITEM_TYPE_TASK) {
+                return (TaskItemView) view;
+            }
         }
-        TaskItemView view = taskViews.get(0);
-        return view.getThumbnailView();
+        return null;
+    }
+
+    /**
+     * Whether this view has processed all data changes and is ready to animate from the app to
+     * the overview.
+     *
+     * @return true if ready to animate app to overview, false otherwise
+     */
+    public boolean isReadyForRemoteAnim() {
+        return !mTaskRecyclerView.hasPendingAdapterUpdates();
+    }
+
+    /**
+     * Set a callback for whenever this view is ready to do a remote animation from the app to
+     * overview. See {@link #isReadyForRemoteAnim()}.
+     *
+     * @param callback callback to run when view is ready to animate
+     */
+    public void setOnReadyForRemoteAnimCallback(onReadyForRemoteAnimCallback callback) {
+        mTaskRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(
+                new ViewTreeObserver.OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        if (isReadyForRemoteAnim()) {
+                            callback.onReadyForRemoteAnim();
+                            mTaskRecyclerView.getViewTreeObserver().
+                                    removeOnGlobalLayoutListener(this);
+                        }
+                    }
+                });
     }
 
     /**
@@ -551,10 +624,217 @@
         mLayoutAnimation.start();
     }
 
+    /**
+     * Play remote animation from app to recents. This should scale the currently closing app down
+     * to the recents thumbnail.
+     *
+     * @param anim animator set
+     * @param appTarget the app surface thats closing
+     * @param recentsTarget the surface containing recents
+     */
+    public void playRemoteAppToRecentsAnimation(@NonNull AnimatorSet anim,
+            @NonNull RemoteAnimationTargetCompat appTarget,
+            @NonNull RemoteAnimationTargetCompat recentsTarget) {
+        TaskItemView bottomView = getBottomTaskView();
+        if (bottomView == null) {
+            // This can be null if there were previously 0 tasks and the recycler view has not had
+            // enough time to take in the data change, bind a new view, and lay out the new view.
+            // TODO: Have a fallback to animate to
+            anim.play(ValueAnimator.ofInt(0, 1).setDuration(REMOTE_APP_TO_OVERVIEW_DURATION));
+        }
+        final Matrix appMatrix = new Matrix();
+        playRemoteTransYAnim(anim, appMatrix);
+        playRemoteAppScaleDownAnim(anim, appMatrix, appTarget, recentsTarget,
+                bottomView.getThumbnailView());
+        playRemoteTaskListFadeIn(anim, bottomView);
+    }
+
+    /**
+     * Play translation Y animation for the remote app to recents animation. Animates over all task
+     * views as well as the closing app, easing them into their final vertical positions.
+     *
+     * @param anim animator set to play on
+     * @param appMatrix transformation matrix for the closing app surface
+     */
+    private void playRemoteTransYAnim(@NonNull AnimatorSet anim, @NonNull Matrix appMatrix) {
+        final ArrayList<TaskItemView> views = getTaskViews();
+
+        // Start Y translation from about halfway through the tasks list to the bottom thumbnail.
+        float taskHeight = getResources().getDimension(R.dimen.task_item_height);
+        float totalTransY = -(MAX_TASKS_TO_DISPLAY / 2.0f - 1) * taskHeight;
+        for (int i = 0, size = views.size(); i < size; i++) {
+            views.get(i).setTranslationY(totalTransY);
+        }
+
+        ValueAnimator transYAnim = ValueAnimator.ofFloat(totalTransY, 0);
+        transYAnim.setDuration(REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION);
+        transYAnim.setInterpolator(FAST_OUT_SLOW_IN_2);
+        transYAnim.addUpdateListener(valueAnimator -> {
+            float transY = (float) valueAnimator.getAnimatedValue();
+            for (int i = 0, size = views.size(); i < size; i++) {
+                views.get(i).setTranslationY(transY);
+            }
+            appMatrix.postTranslate(0, transY - totalTransY);
+        });
+        transYAnim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                for (int i = 0, size = views.size(); i < size; i++) {
+                    views.get(i).setTranslationY(0);
+                }
+            }
+        });
+        anim.play(transYAnim);
+    }
+
+    /**
+     * Play the scale down animation for the remote app to recents animation where the app surface
+     * scales down to where the thumbnail is.
+     *
+     * @param anim animator set to play on
+     * @param appMatrix transformation matrix for the app surface
+     * @param appTarget closing app target
+     * @param recentsTarget opening recents target
+     * @param thumbnailView thumbnail view to animate to
+     */
+    private void playRemoteAppScaleDownAnim(@NonNull AnimatorSet anim, @NonNull Matrix appMatrix,
+            @NonNull RemoteAnimationTargetCompat appTarget,
+            @NonNull RemoteAnimationTargetCompat recentsTarget,
+            @NonNull View thumbnailView) {
+        // Identify where the entering remote app should animate to.
+        Rect endRect = new Rect();
+        thumbnailView.getGlobalVisibleRect(endRect);
+        Rect appBounds = appTarget.sourceContainerBounds;
+
+        SyncRtSurfaceTransactionApplierCompat surfaceApplier =
+                new SyncRtSurfaceTransactionApplierCompat(this);
+
+        // Keep recents visible throughout the animation.
+        SurfaceParams[] params = new SurfaceParams[2];
+        // Closing app should stay on top.
+        int boostedMode = MODE_CLOSING;
+        params[0] = new SurfaceParams(recentsTarget.leash, 1f, null /* matrix */,
+                null /* windowCrop */, getLayer(recentsTarget, boostedMode), 0 /* cornerRadius */);
+
+        ValueAnimator remoteAppAnim = ValueAnimator.ofInt(0, 1);
+        remoteAppAnim.setDuration(REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION);
+        remoteAppAnim.addUpdateListener(new MultiValueUpdateListener() {
+            private final FloatProp mScaleX;
+            private final FloatProp mScaleY;
+            private final FloatProp mTranslationX;
+            private final FloatProp mTranslationY;
+            private final FloatProp mAlpha;
+
+            {
+                // Scale down and move to view location.
+                float endScaleX = ((float) endRect.width()) / appBounds.width();
+                mScaleX = new FloatProp(1f, endScaleX, 0, REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION,
+                        FAST_OUT_SLOW_IN_1);
+                float endScaleY = ((float) endRect.height()) / appBounds.height();
+                mScaleY = new FloatProp(1f, endScaleY, 0, REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION,
+                        FAST_OUT_SLOW_IN_1);
+                float endTranslationX = endRect.left -
+                        (appBounds.width() - thumbnailView.getWidth()) / 2.0f;
+                mTranslationX = new FloatProp(0, endTranslationX, 0,
+                        REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION, FAST_OUT_SLOW_IN_1);
+                float endTranslationY = endRect.top -
+                        (appBounds.height() - thumbnailView.getHeight()) / 2.0f;
+                mTranslationY = new FloatProp(0, endTranslationY, 0,
+                        REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION, FAST_OUT_SLOW_IN_2);
+                mAlpha = new FloatProp(1.0f, 0, 0, REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION,
+                        ACCEL_2);
+            }
+
+            @Override
+            public void onUpdate(float percent) {
+                appMatrix.preScale(mScaleX.value, mScaleY.value,
+                        appBounds.width() / 2.0f, appBounds.height() / 2.0f);
+                appMatrix.postTranslate(mTranslationX.value, mTranslationY.value);
+
+                params[1] = new SurfaceParams(appTarget.leash, mAlpha.value, appMatrix,
+                        null /* windowCrop */, getLayer(appTarget, boostedMode),
+                        0 /* cornerRadius */);
+                surfaceApplier.scheduleApply(params);
+                appMatrix.reset();
+            }
+        });
+        anim.play(remoteAppAnim);
+    }
+
+    /**
+     * Play task list fade in animation as part of remote app to recents animation. This animation
+     * ensures that the task views in the recents list fade in from bottom to top.
+     *
+     * @param anim animator set to play on
+     * @param appTaskView the task view associated with the remote app closing
+     */
+    private void playRemoteTaskListFadeIn(@NonNull AnimatorSet anim,
+            @NonNull TaskItemView appTaskView) {
+        long delay = REMOTE_TO_RECENTS_ITEM_FADE_START_DELAY;
+        int childCount = mTaskRecyclerView.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            ValueAnimator fadeAnim = ValueAnimator.ofFloat(0, 1.0f);
+            fadeAnim.setDuration(REMOTE_TO_RECENTS_ITEM_FADE_DURATION).setInterpolator(OUT_SLOW_IN);
+            fadeAnim.setStartDelay(delay);
+            View view = mTaskRecyclerView.getChildAt(i);
+            if (Objects.equals(view, appTaskView)) {
+                // Only animate icon and text for the view with snapshot animating in
+                final View icon = appTaskView.getIconView();
+                final View label = appTaskView.getLabelView();
+
+                icon.setAlpha(0.0f);
+                label.setAlpha(0.0f);
+
+                fadeAnim.addUpdateListener(alphaVal -> {
+                    float val = alphaVal.getAnimatedFraction();
+
+                    icon.setAlpha(val);
+                    label.setAlpha(val);
+                });
+                fadeAnim.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        icon.setAlpha(1.0f);
+                        label.setAlpha(1.0f);
+                    }
+                });
+            } else {
+                // Otherwise, fade in the entire view.
+                view.setAlpha(0.0f);
+                fadeAnim.addUpdateListener(alphaVal -> {
+                    float val = alphaVal.getAnimatedFraction();
+                    view.setAlpha(val);
+                });
+                fadeAnim.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        view.setAlpha(1.0f);
+                    }
+                });
+            }
+            anim.play(fadeAnim);
+
+            int itemType = mTaskRecyclerView.getChildViewHolder(view).getItemViewType();
+            if (itemType == ITEM_TYPE_CLEAR_ALL) {
+                // Don't add delay. Clear all should animate at same time as next view.
+                continue;
+            }
+            delay += REMOTE_TO_RECENTS_ITEM_FADE_BETWEEN_DELAY;
+        }
+    }
+
     @Override
     public void setInsets(Rect insets) {
         mInsets = insets;
         mTaskRecyclerView.setPadding(insets.left, insets.top, insets.right, insets.bottom);
         mTaskRecyclerView.invalidateItemDecorations();
     }
+
+    /**
+     * Callback for when this view is ready for a remote animation from app to overview.
+     */
+    public interface onReadyForRemoteAnimCallback {
+
+        void onReadyForRemoteAnim();
+    }
 }
diff --git a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java
index 6db8013..f184ad0 100644
--- a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java
+++ b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java
@@ -137,6 +137,14 @@
         return mThumbnailView;
     }
 
+    public View getIconView() {
+        return mIconView;
+    }
+
+    public View getLabelView() {
+        return mLabelView;
+    }
+
     /**
      * Start a new animation from the current task content to the specified new content. The caller
      * is responsible for the actual animation control via the property
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
index 96d2dca..5eecf17 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
@@ -24,6 +24,7 @@
 import android.view.MotionEvent;
 
 import com.android.launcher3.util.Preconditions;
+import com.android.quickstep.inputconsumers.InputConsumer;
 import com.android.quickstep.util.SwipeAnimationTargetSet;
 import com.android.systemui.shared.system.InputConsumerController;
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
index e20ef52..2c919b3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
@@ -296,10 +296,6 @@
             if (sysUiProxy == null) {
                 return null;
             }
-            if (SysUINavigationMode.getMode(activity) == SysUINavigationMode.Mode.NO_BUTTON) {
-                // TODO(b/130225926): Temporarily disable pinning while gesture nav is enabled
-                return null;
-            }
             if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) {
                 return null;
             }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index a343a36..128fd45 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -35,7 +35,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.RectF;
 import android.graphics.Region;
@@ -58,6 +57,7 @@
 
 import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.R;
+import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.logging.EventLogArray;
@@ -66,6 +66,13 @@
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
+import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
+import com.android.quickstep.inputconsumers.AssistantTouchConsumer;
+import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer;
+import com.android.quickstep.inputconsumers.InputConsumer;
+import com.android.quickstep.inputconsumers.OtherActivityInputConsumer;
+import com.android.quickstep.inputconsumers.OverviewInputConsumer;
+import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -90,9 +97,6 @@
     public static final LooperExecutor BACKGROUND_EXECUTOR =
             new LooperExecutor(UiThreadHelper.getBackgroundLooper());
 
-    private static final String NAVBAR_VERTICAL_SIZE = "navigation_bar_frame_height";
-    private static final String NAVBAR_HORIZONTAL_SIZE = "navigation_bar_width";
-
     public static final EventLogArray TOUCH_INTERACTION_LOG =
             new EventLogArray("touch_interaction_log", 40);
 
@@ -291,15 +295,7 @@
     }
 
     private int getNavbarSize(String resName) {
-        int frameSize;
-        Resources res = getResources();
-        int frameSizeResID = res.getIdentifier(resName, "dimen", "android");
-        if (frameSizeResID != 0) {
-            frameSize = res.getDimensionPixelSize(frameSizeResID);
-        } else {
-            frameSize = Utilities.pxFromDp(48, res.getDisplayMetrics());
-        }
-        return frameSize;
+        return ResourceUtils.getNavbarSize(resName, getResources());
     }
 
     private void initTouchBounds() {
@@ -312,20 +308,21 @@
         defaultDisplay.getRealSize(realSize);
         mSwipeTouchRegion.set(0, 0, realSize.x, realSize.y);
         if (mMode == Mode.NO_BUTTON) {
-            mSwipeTouchRegion.top = mSwipeTouchRegion.bottom - getNavbarSize(NAVBAR_VERTICAL_SIZE);
+            mSwipeTouchRegion.top = mSwipeTouchRegion.bottom - getNavbarSize(
+                    ResourceUtils.NAVBAR_VERTICAL_SIZE);
         } else {
             switch (defaultDisplay.getRotation()) {
                 case Surface.ROTATION_90:
                     mSwipeTouchRegion.left = mSwipeTouchRegion.right
-                            - getNavbarSize(NAVBAR_HORIZONTAL_SIZE);
+                            - getNavbarSize(ResourceUtils.NAVBAR_HORIZONTAL_SIZE);
                     break;
                 case Surface.ROTATION_270:
                     mSwipeTouchRegion.right = mSwipeTouchRegion.left
-                            + getNavbarSize(NAVBAR_HORIZONTAL_SIZE);
+                            + getNavbarSize(ResourceUtils.NAVBAR_HORIZONTAL_SIZE);
                     break;
                 default:
                     mSwipeTouchRegion.top = mSwipeTouchRegion.bottom
-                            - getNavbarSize(NAVBAR_VERTICAL_SIZE);
+                            - getNavbarSize(ResourceUtils.NAVBAR_VERTICAL_SIZE);
             }
         }
     }
@@ -470,6 +467,12 @@
                         mInputMonitorCompat);
             }
 
+            if (ActivityManagerWrapper.getInstance().isScreenPinningActive()) {
+                // Note: we only allow accessibility to wrap this, and it replaces the previous
+                // base input consumer (which should be NO_OP anyway since topTaskLocked == true).
+                base = new ScreenPinnedInputConsumer(this, mISystemUiProxy, activityControl);
+            }
+
             if ((mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0) {
                 base = new AccessibilityInputConsumer(this, mISystemUiProxy,
                         (mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0, base,
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 58ae5eb..49c95f1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -67,6 +67,7 @@
 import android.view.animation.Interpolator;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.AbstractFloatingView;
@@ -91,6 +92,8 @@
 import com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState;
 import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
 import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.inputconsumers.InputConsumer;
+import com.android.quickstep.inputconsumers.OverviewInputConsumer;
 import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
@@ -166,7 +169,7 @@
     private static final int LAUNCHER_UI_STATES =
             STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
 
-    enum GestureEndTarget {
+    public enum GestureEndTarget {
         HOME(1, STATE_SCALED_CONTROLLER_HOME, true, false, ContainerType.WORKSPACE, false),
 
         RECENTS(1, STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
@@ -220,8 +223,8 @@
     private final ClipAnimationHelper mClipAnimationHelper;
     private final ClipAnimationHelper.TransformParams mTransformParams;
 
-    protected Runnable mGestureEndCallback;
-    protected GestureEndTarget mGestureEndTarget;
+    private Runnable mGestureEndCallback;
+    private GestureEndTarget mGestureEndTarget;
     // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
     private RunningWindowAnim mRunningWindowAnim;
     private boolean mIsShelfPeeking;
@@ -273,7 +276,7 @@
     private final long mTouchTimeMs;
     private long mLauncherFrameDrawnTime;
 
-    WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context,
+    public WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context,
             long touchTimeMs, ActivityControlHelper<T> controller, boolean continuingLastGesture,
             InputConsumerController inputConsumer) {
         mContext = context;
@@ -1125,6 +1128,13 @@
         return anim;
     }
 
+    /**
+     * @return The GestureEndTarget if the gesture has ended, else null.
+     */
+    public @Nullable GestureEndTarget getGestureEndTarget() {
+        return mGestureEndTarget;
+    }
+
     @UiThread
     private void resumeLastTask() {
         mRecentsAnimationWrapper.finish(false /* toRecents */, null);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AccessibilityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
similarity index 98%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/AccessibilityInputConsumer.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
index 8f8cd18..f8475ca 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AccessibilityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.quickstep;
+package com.android.quickstep.inputconsumers;
 
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_DOWN;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java
similarity index 98%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java
index 335e8b1..0448fd1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.quickstep;
+package com.android.quickstep.inputconsumers;
 
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_DOWN;
@@ -43,6 +43,7 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.touch.SwipeDetector;
+import com.android.quickstep.ActivityControlHelper;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.system.InputMonitorCompat;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -236,7 +237,7 @@
         }
     }
 
-    static boolean withinTouchRegion(Context context, MotionEvent ev) {
+    public static boolean withinTouchRegion(Context context, MotionEvent ev) {
         final Resources res = context.getResources();
         final int width = res.getDisplayMetrics().widthPixels;
         final int height = res.getDisplayMetrics().heightPixels;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/DelegateInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
similarity index 96%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/DelegateInputConsumer.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
index d36162f..311ddd2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/DelegateInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
@@ -1,4 +1,4 @@
-package com.android.quickstep;
+package com.android.quickstep.inputconsumers;
 
 import android.view.MotionEvent;
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
similarity index 97%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index 7fd09f7..b1d175d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.quickstep;
+package com.android.quickstep.inputconsumers;
 
 import android.content.Context;
 import android.content.Intent;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/InputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
similarity index 95%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/InputConsumer.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
index 37b7288..2e8880d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.quickstep;
+package com.android.quickstep.inputconsumers;
 
 import android.annotation.TargetApi;
 import android.os.Build;
@@ -30,6 +30,7 @@
     int TYPE_ASSISTANT = 1 << 3;
     int TYPE_DEVICE_LOCKED = 1 << 4;
     int TYPE_ACCESSIBILITY = 1 << 5;
+    int TYPE_SCREEN_PINNED = 1 << 6;
 
     InputConsumer NO_OP = () -> TYPE_NO_OP;
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
similarity index 97%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index db377b0..e862fa6 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.quickstep;
+package com.android.quickstep.inputconsumers;
 
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_DOWN;
@@ -50,7 +50,13 @@
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.RaceConditionTracker;
 import com.android.launcher3.util.TraceHelper;
+import com.android.quickstep.ActivityControlHelper;
+import com.android.quickstep.OverviewCallbacks;
+import com.android.quickstep.RecentsModel;
+import com.android.quickstep.SwipeSharedState;
+import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.WindowTransformSwipeHandler;
 import com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget;
 import com.android.quickstep.util.CachedEventDispatcher;
 import com.android.quickstep.util.MotionPauseDetector;
@@ -376,7 +382,7 @@
             // The consumer is being switched while we are active. Set up the shared state to be
             // used by the next animation
             removeListener();
-            GestureEndTarget endTarget = mInteractionHandler.mGestureEndTarget;
+            GestureEndTarget endTarget = mInteractionHandler.getGestureEndTarget();
             mSwipeSharedState.canGestureBeContinued = endTarget != null && endTarget.canBeContinued;
             mSwipeSharedState.goingToLauncher = endTarget != null && endTarget.isLauncher;
             if (mSwipeSharedState.canGestureBeContinued) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
similarity index 96%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index bafc367..bab3c71 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.quickstep;
+package com.android.quickstep.inputconsumers;
 
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
@@ -27,6 +27,8 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.views.BaseDragLayer;
+import com.android.quickstep.ActivityControlHelper;
+import com.android.quickstep.OverviewCallbacks;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
new file mode 100644
index 0000000..a0e20f2
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
@@ -0,0 +1,88 @@
+/*
+ * 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.inputconsumers;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.HapticFeedbackConstants;
+import android.view.MotionEvent;
+
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.R;
+import com.android.quickstep.ActivityControlHelper;
+import com.android.quickstep.util.MotionPauseDetector;
+import com.android.systemui.shared.recents.ISystemUiProxy;
+
+/**
+ * An input consumer that detects swipe up and hold to exit screen pinning mode.
+ */
+public class ScreenPinnedInputConsumer implements InputConsumer {
+
+    private static final String TAG = "ScreenPinnedConsumer";
+
+    private final float mMotionPauseMinDisplacement;
+    private final MotionPauseDetector mMotionPauseDetector;
+
+    private float mTouchDownY;
+
+    public ScreenPinnedInputConsumer(Context context, ISystemUiProxy sysuiProxy,
+            ActivityControlHelper activityControl) {
+        mMotionPauseMinDisplacement = context.getResources().getDimension(
+                R.dimen.motion_pause_detector_min_displacement_from_app);
+        mMotionPauseDetector = new MotionPauseDetector(context, true /* makePauseHarderToTrigger*/);
+        mMotionPauseDetector.setOnMotionPauseListener(isPaused -> {
+            if (isPaused) {
+                try {
+                    sysuiProxy.stopScreenPinning();
+                    BaseDraggingActivity launcherActivity = activityControl.getCreatedActivity();
+                    if (launcherActivity != null) {
+                        launcherActivity.getRootView().performHapticFeedback(
+                                HapticFeedbackConstants.LONG_PRESS,
+                                HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+                    }
+                    mMotionPauseDetector.clear();
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Unable to stop screen pinning ", e);
+                }
+            }
+        });
+    }
+
+    @Override
+    public int getType() {
+        return TYPE_SCREEN_PINNED;
+    }
+
+    @Override
+    public void onMotionEvent(MotionEvent ev) {
+        float y = ev.getY();
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                mTouchDownY = y;
+                break;
+            case MotionEvent.ACTION_MOVE:
+                float displacement = mTouchDownY - y;
+                mMotionPauseDetector.setDisallowPause(displacement < mMotionPauseMinDisplacement);
+                mMotionPauseDetector.addPosition(y, ev.getEventTime());
+                break;
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                mMotionPauseDetector.clear();
+                break;
+        }
+    }
+}
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 32f312f..82d1aa6 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -36,6 +36,7 @@
 
     <!-- These speeds are in dp / ms -->
     <dimen name="motion_pause_detector_speed_very_slow">0.0285dp</dimen>
+    <dimen name="motion_pause_detector_speed_slow">0.15dp</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_from_app">36dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index e1a115a..3b75304 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -132,7 +132,7 @@
     private final DragLayer mDragLayer;
     private final AlphaProperty mDragLayerAlpha;
 
-    private final Handler mHandler;
+    final Handler mHandler;
     private final boolean mIsRtl;
 
     private final float mContentTransY;
@@ -573,70 +573,9 @@
      * @return Runner that plays when user goes to Launcher
      *         ie. pressing home, swiping up from nav bar.
      */
-    private RemoteAnimationRunnerCompat getWallpaperOpenRunner(boolean fromUnlock) {
-        return new LauncherAnimationRunner(mHandler, false /* startAtFrontOfQueue */) {
-            @Override
-            public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
-                    AnimationResult result) {
-                if (!mLauncher.hasBeenResumed()) {
-                    // If launcher is not resumed, wait until new async-frame after resume
-                    mLauncher.setOnResumeCallback(() ->
-                            postAsyncCallback(mHandler, () ->
-                                    onCreateAnimation(targetCompats, result)));
-                    return;
-                }
-
-                if (mLauncher.hasSomeInvisibleFlag(PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION)) {
-                    mLauncher.addForceInvisibleFlag(INVISIBLE_BY_PENDING_FLAGS);
-                    mLauncher.getStateManager().moveToRestState();
-                }
-
-                AnimatorSet anim = null;
-                RemoteAnimationProvider provider = mRemoteAnimationProvider;
-                if (provider != null) {
-                    anim = provider.createWindowAnimation(targetCompats);
-                }
-
-                if (anim == null) {
-                    anim = new AnimatorSet();
-                    anim.play(fromUnlock
-                            ? getUnlockWindowAnimator(targetCompats)
-                            : getClosingWindowAnimators(targetCompats));
-
-                    // Normally, we run the launcher content animation when we are transitioning
-                    // home, but if home is already visible, then we don't want to animate the
-                    // contents of launcher unless we know that we are animating home as a result
-                    // of the home button press with quickstep, which will result in launcher being
-                    // started on touch down, prior to the animation home (and won't be in the
-                    // targets list because it is already visible). In that case, we force
-                    // invisibility on touch down, and only reset it after the animation to home
-                    // is initialized.
-                    if (launcherIsATargetWithMode(targetCompats, MODE_OPENING)
-                            || mLauncher.isForceInvisible()) {
-                        // Only register the content animation for cancellation when state changes
-                        mLauncher.getStateManager().setCurrentAnimation(anim);
-                        if (fromUnlock) {
-                            Pair<AnimatorSet, Runnable> contentAnimator =
-                                    getLauncherContentAnimator(false /* isAppOpening */,
-                                            new float[] {mContentTransY, 0});
-                            contentAnimator.first.setStartDelay(0);
-                            anim.play(contentAnimator.first);
-                            anim.addListener(new AnimatorListenerAdapter() {
-                                @Override
-                                public void onAnimationEnd(Animator animation) {
-                                    contentAnimator.second.run();
-                                }
-                            });
-                        } else {
-                            createLauncherResumeAnimation(anim);
-                        }
-                    }
-                }
-
-                mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
-                result.setAnimation(anim);
-            }
-        };
+    RemoteAnimationRunnerCompat getWallpaperOpenRunner(boolean fromUnlock) {
+        return new WallpaperOpenLauncherAnimationRunner(mHandler, false /* startAtFrontOfQueue */,
+                fromUnlock);
     }
 
     /**
@@ -773,4 +712,79 @@
         return mLauncher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION)
                 == PackageManager.PERMISSION_GRANTED;
     }
+
+    /**
+     * Remote animation runner for animation from the app to Launcher, including recents.
+     */
+    class WallpaperOpenLauncherAnimationRunner extends LauncherAnimationRunner {
+        private final boolean mFromUnlock;
+
+        public WallpaperOpenLauncherAnimationRunner(Handler handler, boolean startAtFrontOfQueue,
+                boolean fromUnlock) {
+            super(handler, startAtFrontOfQueue);
+            mFromUnlock = fromUnlock;
+        }
+
+        @Override
+        public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
+                LauncherAnimationRunner.AnimationResult result) {
+            if (!mLauncher.hasBeenResumed()) {
+                // If launcher is not resumed, wait until new async-frame after resume
+                mLauncher.setOnResumeCallback(() ->
+                        postAsyncCallback(mHandler, () ->
+                                onCreateAnimation(targetCompats, result)));
+                return;
+            }
+
+            if (mLauncher.hasSomeInvisibleFlag(PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION)) {
+                mLauncher.addForceInvisibleFlag(INVISIBLE_BY_PENDING_FLAGS);
+                mLauncher.getStateManager().moveToRestState();
+            }
+
+            AnimatorSet anim = null;
+            RemoteAnimationProvider provider = mRemoteAnimationProvider;
+            if (provider != null) {
+                anim = provider.createWindowAnimation(targetCompats);
+            }
+
+            if (anim == null) {
+                anim = new AnimatorSet();
+                anim.play(mFromUnlock
+                        ? getUnlockWindowAnimator(targetCompats)
+                        : getClosingWindowAnimators(targetCompats));
+
+                // Normally, we run the launcher content animation when we are transitioning
+                // home, but if home is already visible, then we don't want to animate the
+                // contents of launcher unless we know that we are animating home as a result
+                // of the home button press with quickstep, which will result in launcher being
+                // started on touch down, prior to the animation home (and won't be in the
+                // targets list because it is already visible). In that case, we force
+                // invisibility on touch down, and only reset it after the animation to home
+                // is initialized.
+                if (launcherIsATargetWithMode(targetCompats, MODE_OPENING)
+                        || mLauncher.isForceInvisible()) {
+                    // Only register the content animation for cancellation when state changes
+                    mLauncher.getStateManager().setCurrentAnimation(anim);
+                    if (mFromUnlock) {
+                        Pair<AnimatorSet, Runnable> contentAnimator =
+                                getLauncherContentAnimator(false /* isAppOpening */,
+                                        new float[] {mContentTransY, 0});
+                        contentAnimator.first.setStartDelay(0);
+                        anim.play(contentAnimator.first);
+                        anim.addListener(new AnimatorListenerAdapter() {
+                            @Override
+                            public void onAnimationEnd(Animator animation) {
+                                contentAnimator.second.run();
+                            }
+                        });
+                    } else {
+                        createLauncherResumeAnimation(anim);
+                    }
+                }
+            }
+
+            mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
+            result.setAnimation(anim);
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index f58f0d4..893c053 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -35,10 +35,18 @@
     /** If no motion is added for this amount of time, assume the motion has paused. */
     private static final long FORCE_PAUSE_TIMEOUT = 300;
 
+    /**
+     * After {@link #makePauseHarderToTrigger()}, must
+     * move slowly for this long to trigger a pause.
+     */
+    private static final long HARDER_TRIGGER_TIMEOUT = 400;
+
     private final float mSpeedVerySlow;
+    private final float mSpeedSlow;
     private final float mSpeedSomewhatFast;
     private final float mSpeedFast;
     private final Alarm mForcePauseTimeout;
+    private final boolean mMakePauseHarderToTrigger;
 
     private Long mPreviousTime = null;
     private Float mPreviousPosition = null;
@@ -52,19 +60,29 @@
     private boolean mHasEverBeenPaused;
     /** @see #setDisallowPause(boolean) */
     private boolean mDisallowPause;
+    // Time at which speed became < mSpeedSlow (only used if mMakePauseHarderToTrigger == true).
+    private long mSlowStartTime;
 
     public MotionPauseDetector(Context context) {
+        this(context, false);
+    }
+
+    /**
+     * @param makePauseHarderToTrigger Used for gestures that require a more explicit pause.
+     */
+    public MotionPauseDetector(Context context, boolean makePauseHarderToTrigger) {
         Resources res = context.getResources();
         mSpeedVerySlow = res.getDimension(R.dimen.motion_pause_detector_speed_very_slow);
+        mSpeedSlow = res.getDimension(R.dimen.motion_pause_detector_speed_slow);
         mSpeedSomewhatFast = res.getDimension(R.dimen.motion_pause_detector_speed_somewhat_fast);
         mSpeedFast = res.getDimension(R.dimen.motion_pause_detector_speed_fast);
         mForcePauseTimeout = new Alarm();
         mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */));
+        mMakePauseHarderToTrigger = makePauseHarderToTrigger;
     }
 
     /**
-     * Get callbacks for when motion pauses and resumes, including an
-     * immediate callback with the current pause state.
+     * Get callbacks for when motion pauses and resumes.
      */
     public void setOnMotionPauseListener(OnMotionPauseListener listener) {
         mOnMotionPauseListener = listener;
@@ -88,13 +106,15 @@
         if (mFirstPosition == null) {
             mFirstPosition = position;
         }
-        mForcePauseTimeout.setAlarm(FORCE_PAUSE_TIMEOUT);
+        mForcePauseTimeout.setAlarm(mMakePauseHarderToTrigger
+                ? HARDER_TRIGGER_TIMEOUT
+                : FORCE_PAUSE_TIMEOUT);
         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);
+                checkMotionPaused(velocity, mPreviousVelocity, time);
             }
             mPreviousVelocity = velocity;
         }
@@ -102,7 +122,7 @@
         mPreviousPosition = position;
     }
 
-    private void checkMotionPaused(float velocity, float prevVelocity) {
+    private void checkMotionPaused(float velocity, float prevVelocity, long time) {
         float speed = Math.abs(velocity);
         float previousSpeed = Math.abs(prevVelocity);
         boolean isPaused;
@@ -122,6 +142,17 @@
                     boolean isRapidDeceleration = speed < previousSpeed * RAPID_DECELERATION_FACTOR;
                     isPaused = isRapidDeceleration && speed < mSpeedSomewhatFast;
                 }
+                if (mMakePauseHarderToTrigger) {
+                    if (speed < mSpeedSlow) {
+                        if (mSlowStartTime == 0) {
+                            mSlowStartTime = time;
+                        }
+                        isPaused = time - mSlowStartTime >= HARDER_TRIGGER_TIMEOUT;
+                    } else {
+                        mSlowStartTime = 0;
+                        isPaused = false;
+                    }
+                }
             }
         }
         updatePaused(isPaused);
@@ -149,6 +180,7 @@
         mFirstPosition = null;
         setOnMotionPauseListener(null);
         mIsPaused = mHasEverBeenPaused = false;
+        mSlowStartTime = 0;
         mForcePauseTimeout.cancelAlarm();
     }
 
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index 8bdb90d..2111e2c 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -28,6 +28,7 @@
 import com.android.launcher3.util.RaceConditionReproducer;
 import com.android.quickstep.NavigationModeSwitchRule.Mode;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
+import com.android.quickstep.inputconsumers.OtherActivityInputConsumer;
 
 import org.junit.Before;
 import org.junit.Ignore;
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 3d2d7cf..d098b8c 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -213,7 +213,7 @@
         // Add a bit of space between nav bar and hotseat in multi-window vertical bar layout.
         hotseatBarSidePaddingStartPx = isMultiWindowMode && isVerticalBarLayout()
                 ? edgeMarginPx : 0;
-        hotseatBarSizePx = Utilities.pxFromDp(inv.iconSize, dm) + (isVerticalBarLayout()
+        hotseatBarSizePx = ResourceUtils.pxFromDp(inv.iconSize, dm) + (isVerticalBarLayout()
                 ? (hotseatBarSidePaddingStartPx + hotseatBarSidePaddingEndPx)
                 : (res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size)
                         + hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx));
@@ -319,7 +319,7 @@
         // Workspace
         final boolean isVerticalLayout = isVerticalBarLayout();
         float invIconSizePx = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize;
-        iconSizePx = Math.max(1, (int) (Utilities.pxFromDp(invIconSizePx, dm) * scale));
+        iconSizePx = Math.max(1, (int) (ResourceUtils.pxFromDp(invIconSizePx, dm) * scale));
         iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, dm) * scale);
         iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale);
 
@@ -399,7 +399,7 @@
     }
 
     private void updateFolderCellSize(float scale, DisplayMetrics dm, Resources res) {
-        folderChildIconSizePx = (int) (Utilities.pxFromDp(inv.iconSize, dm) * scale);
+        folderChildIconSizePx = (int) (ResourceUtils.pxFromDp(inv.iconSize, dm) * scale);
         folderChildTextSizePx =
                 (int) (res.getDimensionPixelSize(R.dimen.folder_child_text_size) * scale);
 
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 819a551..8a8a2fb 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -210,7 +210,7 @@
         iconSize = interpolatedDisplayOption.iconSize;
         iconShapePath = getIconShapePath(context);
         landscapeIconSize = interpolatedDisplayOption.landscapeIconSize;
-        iconBitmapSize = Utilities.pxFromDp(iconSize, dm);
+        iconBitmapSize = ResourceUtils.pxFromDp(iconSize, dm);
         iconTextSize = interpolatedDisplayOption.iconTextSize;
         fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
 
diff --git a/src/com/android/launcher3/ResourceUtils.java b/src/com/android/launcher3/ResourceUtils.java
new file mode 100644
index 0000000..8df3290
--- /dev/null
+++ b/src/com/android/launcher3/ResourceUtils.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+import android.content.res.Resources;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+
+public class ResourceUtils {
+    public static final String NAVBAR_VERTICAL_SIZE = "navigation_bar_frame_height";
+    public static final String NAVBAR_HORIZONTAL_SIZE = "navigation_bar_width";
+
+    public static int getNavbarSize(String resName, Resources res) {
+        return getDimenByName(resName, res, 48);
+    }
+
+    private static int getDimenByName(String resName, Resources res, int defaultValue) {
+        final int frameSize;
+        final int frameSizeResID = res.getIdentifier(resName, "dimen", "android");
+        if (frameSizeResID != 0) {
+            frameSize = res.getDimensionPixelSize(frameSizeResID);
+        } else {
+            frameSize = pxFromDp(defaultValue, res.getDisplayMetrics());
+        }
+        return frameSize;
+    }
+
+    public static int pxFromDp(float size, DisplayMetrics metrics) {
+        return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, metrics));
+    }
+}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 35b967f..5cfd95c 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -477,10 +477,7 @@
         float densityRatio = (float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
         return (size / densityRatio);
     }
-    public static int pxFromDp(float size, DisplayMetrics metrics) {
-        return (int) Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
-                size, metrics));
-    }
+
     public static int pxFromSp(float size, DisplayMetrics metrics) {
         return (int) Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                 size, metrics));
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index e2a5160..41252aa 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -604,4 +604,18 @@
 
         return super.performAccessibilityAction(action, arguments);
     }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        switch (ev.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                mAllAppsStore.setDeferUpdates(true);
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                mAllAppsStore.setDeferUpdates(false);
+                break;
+        }
+        return super.dispatchTouchEvent(ev);
+    }
 }
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 2450039..0e2ddd4 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -39,6 +39,7 @@
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
+import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PropertyResetListener;
@@ -165,7 +166,7 @@
                 Math.round((totalOffsetX + initialSize) / initialScale),
                 Math.round((paddingOffsetY + initialSize) / initialScale));
         Rect endRect = new Rect(0, 0, lp.width, lp.height);
-        float finalRadius = Utilities.pxFromDp(2, mContext.getResources().getDisplayMetrics());
+        float finalRadius = ResourceUtils.pxFromDp(2, mContext.getResources().getDisplayMetrics());
 
         // Create the animators.
         AnimatorSet a = new AnimatorSet();
diff --git a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
index 66f9dbf..c0aa75f 100644
--- a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
+++ b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
@@ -43,7 +43,7 @@
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
+import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
 import com.android.launcher3.util.Themes;
@@ -148,7 +148,7 @@
         mLauncher = Launcher.getLauncher(view.getContext());
         mWallpaperColorInfo = WallpaperColorInfo.getInstance(mLauncher);
 
-        mMaskHeight = Utilities.pxFromDp(ALPHA_MASK_BITMAP_DP,
+        mMaskHeight = ResourceUtils.pxFromDp(ALPHA_MASK_BITMAP_DP,
                 view.getResources().getDisplayMetrics());
         mTopScrim = Themes.getAttrDrawable(view.getContext(), R.attr.workspaceStatusBarScrim);
         mBottomMask = mTopScrim == null ? null : createDitheredAlphaMask();
@@ -297,8 +297,8 @@
 
     public Bitmap createDitheredAlphaMask() {
         DisplayMetrics dm = mLauncher.getResources().getDisplayMetrics();
-        int width = Utilities.pxFromDp(ALPHA_MASK_WIDTH_DP, dm);
-        int gradientHeight = Utilities.pxFromDp(ALPHA_MASK_HEIGHT_DP, dm);
+        int width = ResourceUtils.pxFromDp(ALPHA_MASK_WIDTH_DP, dm);
+        int gradientHeight = ResourceUtils.pxFromDp(ALPHA_MASK_HEIGHT_DP, dm);
         Bitmap dst = Bitmap.createBitmap(width, mMaskHeight, Bitmap.Config.ALPHA_8);
         Canvas c = new Canvas(dst);
         Paint paint = new Paint(Paint.DITHER_FLAG);
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index e6e20e1..053c493 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -201,7 +201,7 @@
      */
     private UserHandle getUserForAncestralSerialNumber(BackupManager backupManager,
             long ancestralSerialNumber) {
-        if (Build.VERSION.SDK_INT < 29) {
+        if (!Utilities.ATLEAST_Q) {
             return null;
         }
         return backupManager.getUserForAncestralSerialNumber(ancestralSerialNumber);
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index 3e2c0ae..a7078a2 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -36,7 +36,7 @@
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
+import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.util.PackageUserKey;
@@ -126,7 +126,7 @@
             // Otherwise, add an empty view to the start as padding (but still scroll edge to edge).
             View leftPaddingView = LayoutInflater.from(getContext()).inflate(
                     R.layout.widget_list_divider, widgetRow, false);
-            leftPaddingView.getLayoutParams().width = Utilities.pxFromDp(
+            leftPaddingView.getLayoutParams().width = ResourceUtils.pxFromDp(
                     16, getResources().getDisplayMetrics());
             widgetCells.addView(leftPaddingView, 0);
         }
diff --git a/tests/Android.mk b/tests/Android.mk
index 080c98b..0991a04 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -30,6 +30,7 @@
     LOCAL_STATIC_JAVA_LIBRARIES += libSharedSystemUI
 
     LOCAL_SRC_FILES := $(call all-java-files-under, tapl) \
+        ../src/com/android/launcher3/ResourceUtils.java \
         ../src/com/android/launcher3/util/SecureSettingsObserver.java \
         ../src/com/android/launcher3/TestProtocol.java
 endif
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 75db2f1..b6878bb 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -51,6 +51,7 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.tapl.LauncherInstrumentation;
@@ -208,7 +209,9 @@
      * @return the matching object.
      */
     protected UiObject2 scrollAndFind(UiObject2 container, BySelector condition) {
-        container.setGestureMargins(0, 0, 0, 200);
+        final int margin = ResourceUtils.getNavbarSize(
+                ResourceUtils.NAVBAR_VERTICAL_SIZE, mLauncher.getResources()) + 1;
+        container.setGestureMargins(0, 0, 0, margin);
 
         int i = 0;
         for (; ; ) {
@@ -216,7 +219,8 @@
             mDevice.wait(Until.findObject(condition), SHORT_UI_TIMEOUT);
             UiObject2 widget = container.findObject(condition);
             if (widget != null && widget.getVisibleBounds().intersects(
-                    0, 0, mDevice.getDisplayWidth(), mDevice.getDisplayHeight() - 200)) {
+                    0, 0, mDevice.getDisplayWidth(),
+                    mDevice.getDisplayHeight() - margin)) {
                 return widget;
             }
             if (++i > 40) fail("Too many attempts");
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 581e886..7578dff 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -24,7 +24,6 @@
 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;
@@ -241,9 +240,8 @@
     }
 
     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)));
+        final AppIcon app = allApps.getAppIcon("TestActivity7");
+        assertNotNull("AppIcon.launch returned null", app.launch(getAppPackageName()));
         test.executeOnLauncher(launcher -> assertTrue(
                 "Launcher activity is the top activity; expecting another activity to be the top "
                         + "one",
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 1ad0037..68bdfe3 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -23,6 +23,7 @@
 import androidx.test.uiautomator.Direction;
 import androidx.test.uiautomator.UiObject2;
 
+import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.TestProtocol;
 
 /**
@@ -66,12 +67,9 @@
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                 "want to get app icon on all apps")) {
             final UiObject2 allAppsContainer = verifyActiveContainer();
-            if (mLauncher.getNavigationModel() != ZERO_BUTTON) {
-                final UiObject2 navBar = mLauncher.waitForSystemUiObject("navigation_bar_frame");
-                allAppsContainer.setGestureMargins(0, 0, 0, navBar.getVisibleBounds().height() + 1);
-            } else {
-                allAppsContainer.setGestureMargins(0, 0, 0, 200);
-            }
+            allAppsContainer.setGestureMargins(0, 0, 0,
+                    ResourceUtils.getNavbarSize(ResourceUtils.NAVBAR_VERTICAL_SIZE,
+                            mLauncher.getResources()) + 1);
             final BySelector appIconSelector = AppIcon.getAppIconSelector(appName, mLauncher);
             if (!hasClickableIcon(allAppsContainer, appIconSelector)) {
                 scrollBackToBeginning();
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index fd2eabb..4d8ff1b 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -586,6 +586,10 @@
         return ViewConfiguration.get(getContext()).getScaledTouchSlop();
     }
 
+    public Resources getResources() {
+        return getContext().getResources();
+    }
+
     private static MotionEvent getMotionEvent(long downTime, long eventTime, int action,
             float x, float y) {
         MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties();
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 100a11c..f7e0b6c 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -19,6 +19,8 @@
 import androidx.test.uiautomator.Direction;
 import androidx.test.uiautomator.UiObject2;
 
+import com.android.launcher3.ResourceUtils;
+
 /**
  * All widgets container.
  */
@@ -38,7 +40,9 @@
                 "want to fling forward in widgets")) {
             LauncherInstrumentation.log("Widgets.flingForward enter");
             final UiObject2 widgetsContainer = verifyActiveContainer();
-            widgetsContainer.setGestureMargins(0, 0, 0, 200);
+            widgetsContainer.setGestureMargins(0, 0, 0,
+                    ResourceUtils.getNavbarSize(ResourceUtils.NAVBAR_VERTICAL_SIZE,
+                            mLauncher.getResources()) + 1);
             widgetsContainer.fling(Direction.DOWN,
                     (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung forward")) {