Fix a memory leak

Currently we pass a callback from AbsSwipeUpHandler to TaskAnimationManager to render task view launch animation, exposing the launcher activity to the static TouchInteractionService, which causes the leak. To not only fix the leak, but consider a better place for this functionality semantically, RecentsView is a better home.

Fixes: 183458638
Test: Run presubmit

Change-Id: Id35d0ad22005003d2516c34515a5cb338605be0b
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index f4ef1f7..c91fa9f 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -22,7 +22,6 @@
 
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
-import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
@@ -97,8 +96,8 @@
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.InputConsumerProxy;
+import com.android.quickstep.util.InputProxyHandlerFactory;
 import com.android.quickstep.util.MotionPauseDetector;
-import com.android.quickstep.util.MultiValueUpdateListener;
 import com.android.quickstep.util.ProtoTracer;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.util.RectFSpringAnim;
@@ -113,12 +112,10 @@
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.systemui.shared.system.LatencyTrackerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
 import com.android.systemui.shared.system.TaskInfoCompat;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.function.Consumer;
 
 /**
@@ -255,7 +252,10 @@
         mActivityInterface = gestureState.getActivityInterface();
         mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
         mInputConsumerProxy =
-                new InputConsumerProxy(inputConsumer, this::createNewInputProxyHandler);
+                new InputConsumerProxy(inputConsumer, () -> {
+                    endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
+                    endLauncherTransitionController();
+                }, new InputProxyHandlerFactory(mActivityInterface, mGestureState));
         mTaskAnimationManager = taskAnimationManager;
         mTouchTimeMs = touchTimeMs;
         mContinuingLastGesture = continuingLastGesture;
@@ -783,19 +783,6 @@
         handleNormalGestureEnd(endVelocity, isFling, velocity, false /* isCancel */);
     }
 
-    /**
-     * Called to create a input proxy for the running task
-     */
-    @UiThread
-    protected InputConsumer createNewInputProxyHandler() {
-        endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
-        endLauncherTransitionController();
-
-        StatefulActivity activity = mActivityInterface.getCreatedActivity();
-        return activity == null ? InputConsumer.NO_OP
-                : new OverviewInputConsumer(mGestureState, activity, null, true);
-    }
-
     private void endRunningWindowAnim(boolean cancel) {
         if (mRunningWindowAnim != null) {
             if (cancel) {
@@ -1338,6 +1325,7 @@
             mInputConsumerProxy.destroy();
             mTaskAnimationManager.setLiveTileCleanUpHandler(null);
         }
+        mInputConsumerProxy.unregisterCallback();
         endRunningWindowAnim(false /* cancel */);
 
         if (mGestureEndCallback != null) {
@@ -1502,36 +1490,13 @@
 
     protected abstract void finishRecentsControllerToHome(Runnable callback);
 
-    private final TaskStackChangeListener mLiveTileRestartListener = new TaskStackChangeListener() {
-        @Override
-        public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
-                boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
-            if (mRecentsView.getRunningTaskIndex() != -1
-                    && mRecentsView.getRunningTaskId() == task.taskId
-                    && mRecentsAnimationTargets.hasTask(task.taskId)) {
-                launchOtherTaskInLiveTileMode(task.taskId, mRecentsAnimationTargets.apps);
-            }
-            ActivityManagerWrapper.getInstance().unregisterTaskStackListener(
-                    mLiveTileRestartListener);
-        }
-    };
-
     private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
         endLauncherTransitionController();
         mActivityInterface.onSwipeUpToRecentsComplete();
         mRecentsView.onSwipeUpAnimationSuccess();
         if (LIVE_TILE.get()) {
-            mTaskAnimationManager.setLaunchOtherTaskInLiveTileModeHandler(
-                    appearedTaskTarget -> {
-                        RemoteAnimationTargetCompat[] apps = Arrays.copyOf(
-                                mRecentsAnimationTargets.apps,
-                                mRecentsAnimationTargets.apps.length + 1);
-                        apps[apps.length - 1] = appearedTaskTarget;
-                        launchOtherTaskInLiveTileMode(appearedTaskTarget.taskId, apps);
-                    });
             mTaskAnimationManager.setLiveTileCleanUpHandler(mInputConsumerProxy::destroy);
-            ActivityManagerWrapper.getInstance().registerTaskStackListener(
-                    mLiveTileRestartListener);
+            mTaskAnimationManager.enableLiveTileRestartListener();
         }
 
         SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(false, TAG);
@@ -1539,65 +1504,6 @@
         reset();
     }
 
-    private void launchOtherTaskInLiveTileMode(int taskId, RemoteAnimationTargetCompat[] apps) {
-        AnimatorSet anim = new AnimatorSet();
-        TaskView taskView = mRecentsView.getTaskView(taskId);
-        if (taskView == null || !mRecentsView.isTaskViewVisible(taskView)) {
-            // TODO: Refine this animation.
-            SurfaceTransactionApplier surfaceApplier =
-                    new SurfaceTransactionApplier(mActivity.getDragLayer());
-            ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
-            appAnimator.setDuration(RECENTS_LAUNCH_DURATION);
-            appAnimator.setInterpolator(ACCEL_DEACCEL);
-            appAnimator.addUpdateListener(new MultiValueUpdateListener() {
-                @Override
-                public void onUpdate(float percent) {
-                    SurfaceParams.Builder builder = new SurfaceParams.Builder(
-                            apps[apps.length - 1].leash);
-                    Matrix matrix = new Matrix();
-                    matrix.postScale(percent, percent);
-                    matrix.postTranslate(mDp.widthPx * (1 - percent) / 2,
-                            mDp.heightPx * (1 - percent) / 2);
-                    builder.withAlpha(percent).withMatrix(matrix);
-                    surfaceApplier.scheduleApply(builder.build());
-                }
-            });
-            anim.play(appAnimator);
-        } else {
-            TaskViewUtils.composeRecentsLaunchAnimator(
-                    anim, taskView, apps,
-                    mRecentsAnimationTargets.wallpapers, true /* launcherClosing */,
-                    mActivity.getStateManager(), mRecentsView,
-                    mActivityInterface.getDepthController());
-        }
-        anim.addListener(new AnimatorListenerAdapter(){
-
-            @Override
-            public void onAnimationEnd(Animator animator) {
-                cleanUp(false);
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animator) {
-                cleanUp(true);
-            }
-
-            private void cleanUp(boolean canceled) {
-                if (mRecentsAnimationController != null) {
-                    mRecentsAnimationController.finish(false /* toRecents */,
-                            null /* onFinishComplete */);
-                    if (canceled) {
-                        mRecentsAnimationController = null;
-                    } else {
-                        mActivityInterface.onLaunchTaskSuccess();
-                    }
-                    ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
-                }
-            }
-        });
-        anim.start();
-    }
-
     private static boolean isNotInRecents(RemoteAnimationTargetCompat app) {
         return app.isNotInRecents
                 || app.activityType == ACTIVITY_TYPE_HOME;
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
index da0a664..718c5ba 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
@@ -41,13 +41,4 @@
     public boolean hasTargets() {
         return unfilteredApps.length != 0;
     }
-
-    public boolean hasTask(int taskId) {
-        for (RemoteAnimationTargetCompat target : unfilteredApps) {
-            if (target.taskId == taskId) {
-                return true;
-            }
-        }
-        return false;
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 9a454f2..b6dad2d 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -17,10 +17,11 @@
 
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
 import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_INITIALIZED;
 import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_STARTED;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
 
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
@@ -31,13 +32,13 @@
 
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.RemoteTransitionCompat;
-
-import java.util.function.Consumer;
+import com.android.systemui.shared.system.TaskStackChangeListener;
 
 public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener {
     public static final boolean ENABLE_SHELL_TRANSITIONS =
@@ -49,10 +50,24 @@
     // Temporary until we can hook into gesture state events
     private GestureState mLastGestureState;
     private RemoteAnimationTargetCompat mLastAppearedTaskTarget;
-    private Consumer<RemoteAnimationTargetCompat> mLaunchOtherTaskHandler;
     private Runnable mLiveTileCleanUpHandler;
     private Context mCtx;
 
+    private final TaskStackChangeListener mLiveTileRestartListener = new TaskStackChangeListener() {
+        @Override
+        public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
+                boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
+            BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface();
+            if (LIVE_TILE.get() && activityInterface.isInLiveTileMode()
+                    && activityInterface.getCreatedActivity() != null) {
+                RecentsView recentsView = activityInterface.getCreatedActivity().getOverviewPanel();
+                recentsView.launchSideTaskInLiveTileModeForRestartedApp(task.taskId);
+                ActivityManagerWrapper.getInstance().unregisterTaskStackListener(
+                        mLiveTileRestartListener);
+            }
+        }
+    };
+
     TaskAnimationManager(Context ctx) {
         mCtx = ctx;
     }
@@ -114,9 +129,14 @@
 
             @Override
             public void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
-                if (mLaunchOtherTaskHandler != null
-                        && mLastGestureState.getEndTarget() == RECENTS) {
-                    mLaunchOtherTaskHandler.accept(appearedTaskTarget);
+                BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface();
+                if (LIVE_TILE.get() && activityInterface.isInLiveTileMode()
+                        && activityInterface.getCreatedActivity() != null) {
+                    RecentsView recentsView =
+                            activityInterface.getCreatedActivity().getOverviewPanel();
+                    RemoteAnimationTargetCompat[] apps = new RemoteAnimationTargetCompat[1];
+                    apps[0] = appearedTaskTarget;
+                    recentsView.launchSideTaskInLiveTileMode(appearedTaskTarget.taskId, apps);
                     return;
                 }
                 if (mController != null) {
@@ -161,17 +181,12 @@
         return mCallbacks;
     }
 
-    /**
-     * The passed-in handler is used to render side task launch animation in recents in live tile
-     * mode.
-     */
-    public void setLaunchOtherTaskInLiveTileModeHandler(
-            Consumer<RemoteAnimationTargetCompat> handler) {
-        mLaunchOtherTaskHandler = handler;
+    public void setLiveTileCleanUpHandler(Runnable cleanUpHandler) {
+        mLiveTileCleanUpHandler = cleanUpHandler;
     }
 
-    public void setLiveTileCleanUpHandler(Runnable runnable) {
-        mLiveTileCleanUpHandler = runnable;
+    public void enableLiveTileRestartListener() {
+        ActivityManagerWrapper.getInstance().registerTaskStackListener(mLiveTileRestartListener);
     }
 
     /**
@@ -215,6 +230,7 @@
             mLiveTileCleanUpHandler.run();
             mLiveTileCleanUpHandler = null;
         }
+        ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mLiveTileRestartListener);
 
         // Release all the target leashes
         if (mTargets != null) {
@@ -231,7 +247,6 @@
         mTargets = null;
         mLastGestureState = null;
         mLastAppearedTaskTarget = null;
-        mLaunchOtherTaskHandler = null;
     }
 
     public void dump() {
diff --git a/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java b/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
index 3e87f48..2e5b33a 100644
--- a/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
+++ b/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
@@ -38,7 +38,8 @@
     private static final String TAG = "InputConsumerProxy";
 
     private final InputConsumerController mInputConsumerController;
-    private final Supplier<InputConsumer> mConsumerSupplier;
+    private Runnable mCallback;
+    private Supplier<InputConsumer> mConsumerSupplier;
 
     // The consumer is created lazily on demand.
     private InputConsumer mInputConsumer;
@@ -48,8 +49,9 @@
     private boolean mDestroyPending = false;
 
     public InputConsumerProxy(InputConsumerController inputConsumerController,
-            Supplier<InputConsumer> consumerSupplier) {
+            Runnable callback, Supplier<InputConsumer> consumerSupplier) {
         mInputConsumerController = inputConsumerController;
+        mCallback = callback;
         mConsumerSupplier = consumerSupplier;
     }
 
@@ -64,9 +66,7 @@
         if (ev instanceof MotionEvent) {
             onInputConsumerMotionEvent((MotionEvent) ev);
         } else if (ev instanceof KeyEvent) {
-            if (mInputConsumer == null) {
-                mInputConsumer = mConsumerSupplier.get();
-            }
+            initInputConsumerIfNeeded();
             mInputConsumer.onKeyEvent((KeyEvent) ev);
             return true;
         }
@@ -89,9 +89,7 @@
 
         if (action == ACTION_DOWN) {
             mTouchInProgress = true;
-            if (mInputConsumer == null) {
-                mInputConsumer = mConsumerSupplier.get();
-            }
+            initInputConsumerIfNeeded();
         } else if (action == ACTION_CANCEL || action == ACTION_UP) {
             // Finish any pending actions
             mTouchInProgress = false;
@@ -115,4 +113,18 @@
         mDestroyed = true;
         mInputConsumerController.setInputListener(null);
     }
+
+    public void unregisterCallback() {
+        mCallback = null;
+    }
+
+    private void initInputConsumerIfNeeded() {
+        if (mInputConsumer == null) {
+            if (mCallback != null) {
+                mCallback.run();
+            }
+            mInputConsumer = mConsumerSupplier.get();
+            mConsumerSupplier = null;
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/InputProxyHandlerFactory.java b/quickstep/src/com/android/quickstep/util/InputProxyHandlerFactory.java
new file mode 100644
index 0000000..8209c09
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/InputProxyHandlerFactory.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.quickstep.BaseActivityInterface;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.InputConsumer;
+import com.android.quickstep.inputconsumers.OverviewInputConsumer;
+
+import java.util.function.Supplier;
+
+/**
+ * A factory that creates a input consumer for
+ *  {@link com.android.quickstep.util.InputConsumerProxy}.
+ */
+public class InputProxyHandlerFactory implements Supplier<InputConsumer> {
+
+    private final BaseActivityInterface mActivityInterface;
+    private final GestureState mGestureState;
+
+    @UiThread
+    public InputProxyHandlerFactory(BaseActivityInterface activityInterface,
+            GestureState gestureState) {
+        mActivityInterface = activityInterface;
+        mGestureState = gestureState;
+    }
+
+    /**
+     * Called to create a input proxy for the running task
+     */
+    @Override
+    public InputConsumer get() {
+        StatefulActivity activity = mActivityInterface.getCreatedActivity();
+        return activity == null ? InputConsumer.NO_OP
+                : new OverviewInputConsumer(mGestureState, activity, null, true);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index d357ebe..7693440 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -27,6 +27,7 @@
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
 import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
+import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
 import static com.android.launcher3.Utilities.mapToRange;
 import static com.android.launcher3.Utilities.squaredHypot;
@@ -34,6 +35,7 @@
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
 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.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_CLEAR_ALL;
@@ -50,6 +52,7 @@
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.LayoutTransition;
 import android.animation.LayoutTransition.TransitionListener;
@@ -61,6 +64,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Canvas;
+import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
@@ -129,8 +133,11 @@
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskThumbnailCache;
+import com.android.quickstep.TaskViewUtils;
 import com.android.quickstep.ViewUtils;
+import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.MultiValueUpdateListener;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.util.SplitScreenBounds;
 import com.android.quickstep.util.SurfaceTransactionApplier;
@@ -142,6 +149,8 @@
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.PackageManagerWrapper;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.wm.shell.pip.IPipAnimationListener;
 
@@ -715,6 +724,72 @@
         super.draw(canvas);
     }
 
+    public void launchSideTaskInLiveTileModeForRestartedApp(int taskId) {
+        if (mRunningTaskId != -1 && mRunningTaskId == taskId &&
+                getLiveTileParams().getTargetSet().findTask(taskId) != null) {
+            launchSideTaskInLiveTileMode(taskId, getLiveTileParams().getTargetSet().apps);
+        }
+    }
+
+    public void launchSideTaskInLiveTileMode(int taskId, RemoteAnimationTargetCompat[] apps) {
+        AnimatorSet anim = new AnimatorSet();
+        TaskView taskView = getTaskView(taskId);
+        if (taskView == null || !isTaskViewVisible(taskView)) {
+            // TODO: Refine this animation.
+            SurfaceTransactionApplier surfaceApplier =
+                    new SurfaceTransactionApplier(mActivity.getDragLayer());
+            ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
+            appAnimator.setDuration(RECENTS_LAUNCH_DURATION);
+            appAnimator.setInterpolator(ACCEL_DEACCEL);
+            appAnimator.addUpdateListener(new MultiValueUpdateListener() {
+                @Override
+                public void onUpdate(float percent) {
+                    SurfaceParams.Builder builder = new SurfaceParams.Builder(
+                            apps[apps.length - 1].leash);
+                    Matrix matrix = new Matrix();
+                    matrix.postScale(percent, percent);
+                    matrix.postTranslate(mActivity.getDeviceProfile().widthPx * (1 - percent) / 2,
+                            mActivity.getDeviceProfile().heightPx * (1 - percent) / 2);
+                    builder.withAlpha(percent).withMatrix(matrix);
+                    surfaceApplier.scheduleApply(builder.build());
+                }
+            });
+            anim.play(appAnimator);
+        } else {
+            TaskViewUtils.composeRecentsLaunchAnimator(
+                    anim, taskView, apps,
+                    mLiveTileParams.getTargetSet().wallpapers, true /* launcherClosing */,
+                    mActivity.getStateManager(), this,
+                    getDepthController());
+        }
+        anim.addListener(new AnimatorListenerAdapter(){
+
+            @Override
+            public void onAnimationEnd(Animator animator) {
+                cleanUp(false);
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animator) {
+                cleanUp(true);
+            }
+
+            private void cleanUp(boolean canceled) {
+                if (mRecentsAnimationController != null) {
+                    mRecentsAnimationController.finish(false /* toRecents */,
+                            null /* onFinishComplete */);
+                    if (canceled) {
+                        mRecentsAnimationController = null;
+                    } else {
+                        mSizeStrategy.onLaunchTaskSuccess();
+                    }
+                    ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
+                }
+            }
+        });
+        anim.start();
+    }
+
     private void updateTaskStartIndex(View affectingView) {
         if (!(affectingView instanceof TaskView) && !(affectingView instanceof ClearAllButton)) {
             int childCount = getChildCount();