Merge "Adding task open animation in fallback activity" into ub-launcher3-master
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index ad0b734..8fbb512 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -19,6 +19,13 @@
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
+import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
+import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
+import static com.android.launcher3.anim.Interpolators.APP_CLOSE_ALPHA;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.quickstep.TaskUtils.findTaskViewToLaunch;
+import static com.android.quickstep.TaskUtils.getRecentsWindowAnimator;
+import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
 import static com.android.systemui.shared.recents.utilities.Utilities.getNextFrameNumber;
 import static com.android.systemui.shared.recents.utilities.Utilities.getSurface;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
@@ -31,7 +38,6 @@
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.app.ActivityOptions;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
@@ -45,7 +51,6 @@
 import android.view.Surface;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.animation.Interpolator;
 
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.InsettableFrameLayout.LayoutParams;
@@ -55,12 +60,10 @@
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.shortcuts.DeepShortcutView;
-import com.android.quickstep.RecentsAnimationInterpolator;
-import com.android.quickstep.RecentsAnimationInterpolator.TaskWindowBounds;
+import com.android.quickstep.util.MultiValueUpdateListener;
 import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityCompat;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
@@ -79,7 +82,7 @@
         implements OnDeviceProfileChangeListener {
 
     private static final String TAG = "LauncherTransition";
-    private static final int STATUS_BAR_TRANSITION_DURATION = 120;
+    public static final int STATUS_BAR_TRANSITION_DURATION = 120;
 
     private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
             "android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
@@ -87,7 +90,7 @@
     private static final int APP_LAUNCH_DURATION = 500;
     // Use a shorter duration for x or y translation to create a curve effect
     private static final int APP_LAUNCH_CURVED_DURATION = 233;
-    private static final int RECENTS_LAUNCH_DURATION = 336;
+    public static final int RECENTS_LAUNCH_DURATION = 336;
     private static final int LAUNCHER_RESUME_START_DELAY = 100;
     private static final int CLOSING_TRANSITION_DURATION_MS = 350;
 
@@ -185,64 +188,6 @@
     }
 
     /**
-     * Try to find a TaskView that corresponds with the component of the launched view.
-     *
-     * If this method returns a non-null TaskView, it will be used in composeRecentsLaunchAnimation.
-     * Otherwise, we will assume we are using a normal app transition, but it's possible that the
-     * opening remote target (which we don't get until onAnimationStart) will resolve to a TaskView.
-     */
-    private TaskView findTaskViewToLaunch(
-            BaseDraggingActivity activity, View v, RemoteAnimationTargetCompat[] targets) {
-        if (v instanceof TaskView) {
-            return (TaskView) v;
-        }
-        RecentsView recentsView = activity.getOverviewPanel();
-
-        // It's possible that the launched view can still be resolved to a visible task view, check
-        // the task id of the opening task and see if we can find a match.
-        if (v.getTag() instanceof ItemInfo) {
-            ItemInfo itemInfo = (ItemInfo) v.getTag();
-            ComponentName componentName = itemInfo.getTargetComponent();
-            if (componentName != null) {
-                for (int i = 0; i < recentsView.getChildCount(); i++) {
-                    TaskView taskView = (TaskView) recentsView.getPageAt(i);
-                    if (recentsView.isTaskViewVisible(taskView)) {
-                        Task task = taskView.getTask();
-                        if (componentName.equals(task.key.getComponent())) {
-                            return taskView;
-                        }
-                    }
-                }
-            }
-        }
-
-        if (targets == null) {
-            return null;
-        }
-        // Resolve the opening task id
-        int openingTaskId = -1;
-        for (RemoteAnimationTargetCompat target : targets) {
-            if (target.mode == MODE_OPENING) {
-                openingTaskId = target.taskId;
-                break;
-            }
-        }
-
-        // If there is no opening task id, fall back to the normal app icon launch animation
-        if (openingTaskId == -1) {
-            return null;
-        }
-
-        // If the opening task id is not currently visible in overview, then fall back to normal app
-        // icon launch animation
-        TaskView taskView = recentsView.getTaskView(openingTaskId);
-        if (taskView == null || !recentsView.isTaskViewVisible(taskView)) {
-            return null;
-        }
-        return taskView;
-    }
-
-    /**
      * Composes the animations for a launch from the recents list if possible.
      */
     private boolean composeRecentsLaunchAnimator(View v,
@@ -285,7 +230,8 @@
             };
         }
 
-        target.play(getRecentsWindowAnimator(taskView, skipLauncherChanges, targets));
+        target.play(getRecentsWindowAnimator(taskView, skipLauncherChanges, targets)
+                .setDuration(RECENTS_LAUNCH_DURATION));
         target.play(launcherAnim);
 
         // Set the current animation first, before adding windowAnimEndListener. Setting current
@@ -297,83 +243,6 @@
     }
 
     /**
-     * @return Animator that controls the window of the opening targets for the recents launch
-     * animation.
-     */
-    private ValueAnimator getRecentsWindowAnimator(TaskView v, boolean skipLauncherChanges,
-            RemoteAnimationTargetCompat[] targets) {
-        final RecentsAnimationInterpolator recentsInterpolator = v.getRecentsInterpolator();
-
-        Rect crop = new Rect();
-        Matrix matrix = new Matrix();
-
-        ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
-        appAnimator.setDuration(RECENTS_LAUNCH_DURATION);
-        appAnimator.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
-        appAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            boolean isFirstFrame = true;
-
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                final Surface surface = getSurface(v);
-                final long frameNumber = surface != null ? getNextFrameNumber(surface) : -1;
-                if (frameNumber == -1) {
-                    // Booo, not cool! Our surface got destroyed, so no reason to animate anything.
-                    Log.w(TAG, "Failed to animate, surface got destroyed.");
-                    return;
-                }
-                final float percent = animation.getAnimatedFraction();
-                TaskWindowBounds tw = recentsInterpolator.interpolate(percent);
-
-                float alphaDuration = 75;
-                if (!skipLauncherChanges) {
-                    v.setScaleX(tw.taskScale);
-                    v.setScaleY(tw.taskScale);
-                    v.setTranslationX(tw.taskX);
-                    v.setTranslationY(tw.taskY);
-                    // Defer fading out the view until after the app window gets faded in
-                    v.setAlpha(getValue(1f, 0f, alphaDuration, alphaDuration,
-                            appAnimator.getDuration() * percent, Interpolators.LINEAR));
-                }
-
-                matrix.setScale(tw.winScale, tw.winScale);
-                matrix.postTranslate(tw.winX, tw.winY);
-                crop.set(tw.winCrop);
-
-                // Fade in the app window.
-                float alpha = getValue(0f, 1f, 0, alphaDuration,
-                        appAnimator.getDuration() * percent, Interpolators.LINEAR);
-
-                TransactionCompat t = new TransactionCompat();
-                for (RemoteAnimationTargetCompat target : targets) {
-                    if (target.mode == RemoteAnimationTargetCompat.MODE_OPENING) {
-                        t.setAlpha(target.leash, alpha);
-
-                        // TODO: This isn't correct at the beginning of the animation, but better
-                        // than nothing.
-                        matrix.postTranslate(target.position.x, target.position.y);
-                        t.setMatrix(target.leash, matrix);
-                        t.setWindowCrop(target.leash, crop);
-
-                        if (!skipLauncherChanges) {
-                            t.deferTransactionUntil(target.leash, surface, frameNumber);
-                        }
-                    }
-                    if (isFirstFrame) {
-                        t.show(target.leash);
-                    }
-                }
-                t.setEarlyWakeup();
-                t.apply();
-
-                matrix.reset();
-                isFirstFrame = false;
-            }
-        });
-        return appAnimator;
-    }
-
-    /**
      * Content is everything on screen except the background and the floating view (if any).
      *
      * @param show If true: Animate the content so that it moves upwards and fades in.
@@ -399,9 +268,9 @@
 
             ObjectAnimator alpha = ObjectAnimator.ofFloat(appsView, View.ALPHA, alphas);
             alpha.setDuration(217);
-            alpha.setInterpolator(Interpolators.LINEAR);
+            alpha.setInterpolator(LINEAR);
             ObjectAnimator transY = ObjectAnimator.ofFloat(appsView, View.TRANSLATION_Y, trans);
-            transY.setInterpolator(Interpolators.AGGRESSIVE_EASE);
+            transY.setInterpolator(AGGRESSIVE_EASE);
             transY.setDuration(350);
 
             launcherAnimator.play(alpha);
@@ -420,10 +289,10 @@
 
             ObjectAnimator dragLayerAlpha = ObjectAnimator.ofFloat(mDragLayer, View.ALPHA, alphas);
             dragLayerAlpha.setDuration(217);
-            dragLayerAlpha.setInterpolator(Interpolators.LINEAR);
+            dragLayerAlpha.setInterpolator(LINEAR);
             ObjectAnimator dragLayerTransY = ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y,
                     trans);
-            dragLayerTransY.setInterpolator(Interpolators.AGGRESSIVE_EASE);
+            dragLayerTransY.setInterpolator(AGGRESSIVE_EASE);
             dragLayerTransY.setDuration(350);
 
             launcherAnimator.play(dragLayerAlpha);
@@ -515,8 +384,8 @@
         boolean isBelowCenterY = lp.topMargin < centerY;
         x.setDuration(isBelowCenterY ? APP_LAUNCH_DURATION : APP_LAUNCH_CURVED_DURATION);
         y.setDuration(isBelowCenterY ? APP_LAUNCH_CURVED_DURATION : APP_LAUNCH_DURATION);
-        x.setInterpolator(Interpolators.AGGRESSIVE_EASE);
-        y.setInterpolator(Interpolators.AGGRESSIVE_EASE);
+        x.setInterpolator(AGGRESSIVE_EASE);
+        y.setInterpolator(AGGRESSIVE_EASE);
         appIconAnimatorSet.play(x);
         appIconAnimatorSet.play(y);
 
@@ -534,7 +403,7 @@
         ObjectAnimator alpha = ObjectAnimator.ofFloat(mFloatingView, View.ALPHA, 1f, 0f);
         alpha.setStartDelay(32);
         alpha.setDuration(50);
-        alpha.setInterpolator(Interpolators.LINEAR);
+        alpha.setInterpolator(LINEAR);
         appIconAnimatorSet.play(alpha);
 
         appIconAnimatorSet.addListener(new AnimatorListenerAdapter() {
@@ -569,11 +438,14 @@
 
         ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
         appAnimator.setDuration(APP_LAUNCH_DURATION);
-        appAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+        appAnimator.addUpdateListener(new MultiValueUpdateListener() {
+            // Fade alpha for the app window.
+            FloatProp mAlpha = new FloatProp(0f, 1f, 0, 60, LINEAR);
+
             boolean isFirstFrame = true;
 
             @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
+            public void onUpdate(float percent) {
                 final Surface surface = getSurface(mFloatingView);
                 final long frameNumber = surface != null ? getNextFrameNumber(surface) : -1;
                 if (frameNumber == -1) {
@@ -581,8 +453,7 @@
                     Log.w(TAG, "Failed to animate, surface got destroyed.");
                     return;
                 }
-                final float percent = animation.getAnimatedFraction();
-                final float easePercent = Interpolators.AGGRESSIVE_EASE.getInterpolation(percent);
+                final float easePercent = AGGRESSIVE_EASE.getInterpolation(percent);
 
                 // Calculate app icon size.
                 float iconWidth = bounds.width() * mFloatingView.getScaleX();
@@ -607,11 +478,6 @@
                 float transY0 = floatingViewBounds[1] - offsetY;
                 matrix.postTranslate(transX0, transY0);
 
-                // Fade in the app window.
-                float alphaDuration = 60;
-                float alpha = getValue(0f, 1f, 0, alphaDuration,
-                        appAnimator.getDuration() * percent, Interpolators.LINEAR);
-
                 // Animate the window crop so that it starts off as a square, and then reveals
                 // horizontally.
                 float cropHeight = deviceHeight * easePercent + deviceWidth * (1 - easePercent);
@@ -624,7 +490,7 @@
                 TransactionCompat t = new TransactionCompat();
                 for (RemoteAnimationTargetCompat target : targets) {
                     if (target.mode == MODE_OPENING) {
-                        t.setAlpha(target.leash, alpha);
+                        t.setAlpha(target.leash, mAlpha.value);
 
                         // TODO: This isn't correct at the beginning of the animation, but better
                         // than nothing.
@@ -670,13 +536,7 @@
     }
 
     private boolean launcherIsATargetWithMode(RemoteAnimationTargetCompat[] targets, int mode) {
-        int launcherTaskId = mLauncher.getTaskId();
-        for (RemoteAnimationTargetCompat target : targets) {
-            if (target.mode == mode && target.taskId == launcherTaskId) {
-                return true;
-            }
-        }
-        return false;
+        return taskIsATargetWithMode(targets, mLauncher.getTaskId(), mode);
     }
 
     /**
@@ -730,30 +590,23 @@
 
         ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
         closingAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS);
+        closingAnimator.addUpdateListener(new MultiValueUpdateListener() {
+            FloatProp mDx = new FloatProp(0, endX, 0, 350, AGGRESSIVE_EASE_IN_OUT);
+            FloatProp mScale = new FloatProp(1f, 0.8f, 0, 267, AGGRESSIVE_EASE);
+            FloatProp mAlpha = new FloatProp(1f, 0f, 0, 350, APP_CLOSE_ALPHA);
 
-        closingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
             boolean isFirstFrame = true;
 
             @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                final float percent = animation.getAnimatedFraction();
-                float currentPlayTime = percent * closingAnimator.getDuration();
-
-                float scale = getValue(1f, 0.8f, 0, 267, currentPlayTime,
-                        Interpolators.AGGRESSIVE_EASE);
-
-                float dX = getValue(0, endX, 0, 350, currentPlayTime,
-                        Interpolators.AGGRESSIVE_EASE_IN_OUT);
-
+            public void onUpdate(float percent) {
                 TransactionCompat t = new TransactionCompat();
                 for (RemoteAnimationTargetCompat app : targets) {
                     if (app.mode == RemoteAnimationTargetCompat.MODE_CLOSING) {
-                        t.setAlpha(app.leash, getValue(1f, 0f, 0, 350, currentPlayTime,
-                                Interpolators.APP_CLOSE_ALPHA));
-                        matrix.setScale(scale, scale,
+                        t.setAlpha(app.leash, mAlpha.value);
+                        matrix.setScale(mScale.value, mScale.value,
                                 app.sourceContainerBounds.centerX(),
                                 app.sourceContainerBounds.centerY());
-                        matrix.postTranslate(dX, 0);
+                        matrix.postTranslate(mDx.value, 0);
                         matrix.postTranslate(app.position.x, app.position.y);
                         t.setMatrix(app.leash, matrix);
                     }
@@ -772,6 +625,7 @@
                 isFirstFrame = false;
             }
         });
+
         return closingAnimator;
     }
 
@@ -832,16 +686,4 @@
         return mLauncher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION)
                 == PackageManager.PERMISSION_GRANTED;
     }
-
-    /**
-     * Helper method that allows us to get interpolated values for embedded
-     * animations with a delay and/or different duration.
-     */
-    private static float getValue(float start, float end, float delay, float duration,
-            float currentPlayTime, Interpolator i) {
-        float time = Math.max(0, currentPlayTime - delay);
-        float newPercent = Math.min(1f, time / duration);
-        newPercent = i.getInterpolation(newPercent);
-        return end * newPercent + start * (1 - newPercent);
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 19ef8ab..eb0be89 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -15,16 +15,29 @@
  */
 package com.android.quickstep;
 
+import static com.android.launcher3.LauncherAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
+import static com.android.launcher3.LauncherAppTransitionManagerImpl.STATUS_BAR_TRANSITION_DURATION;
+import static com.android.quickstep.TaskUtils.getRecentsWindowAnimator;
+import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
 import android.app.ActivityOptions;
 import android.content.Intent;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.view.View;
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAnimationRunner;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.badge.BadgeInfo;
 import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.util.SystemUiController;
@@ -32,12 +45,18 @@
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.fallback.RecentsRootView;
+import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.system.ActivityOptionsCompat;
+import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
+import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 /**
  * A simple activity to show the recently launched tasks
  */
 public class RecentsActivity extends BaseDraggingActivity {
 
+    private Handler mUiHandler = new Handler(Looper.getMainLooper());
     private RecentsRootView mRecentsRootView;
     private FallbackRecentsView mFallbackRecentsView;
 
@@ -84,8 +103,49 @@
     }
 
     @Override
-    public ActivityOptions getActivityLaunchOptions(View v, boolean useDefaultLaunchOptions) {
-        return null;
+    public ActivityOptions getActivityLaunchOptions(final View v, boolean useDefaultLaunchOptions) {
+        if (useDefaultLaunchOptions || !(v instanceof TaskView)) {
+            return null;
+        }
+
+        final TaskView taskView = (TaskView) v;
+        RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mUiHandler) {
+
+            @Override
+            public AnimatorSet getAnimator(RemoteAnimationTargetCompat[] targetCompats) {
+                return composeRecentsLaunchAnimator(taskView, targetCompats);
+            }
+        };
+        return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
+                runner, RECENTS_LAUNCH_DURATION,
+                RECENTS_LAUNCH_DURATION - STATUS_BAR_TRANSITION_DURATION));
+    }
+
+    /**
+     * Composes the animations for a launch from the recents list if possible.
+     */
+    private AnimatorSet composeRecentsLaunchAnimator(TaskView taskView,
+            RemoteAnimationTargetCompat[] targets) {
+        AnimatorSet target = new AnimatorSet();
+        boolean activityClosing = taskIsATargetWithMode(targets, getTaskId(), MODE_CLOSING);
+        target.play(getRecentsWindowAnimator(taskView, !activityClosing, targets)
+                .setDuration(RECENTS_LAUNCH_DURATION));
+
+        // Found a visible recents task that matches the opening app, lets launch the app from there
+        if (activityClosing) {
+            Animator adjacentAnimation = mFallbackRecentsView
+                    .createAdjacentPageAnimForTaskLaunch(taskView);
+            adjacentAnimation.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
+            adjacentAnimation.setDuration(RECENTS_LAUNCH_DURATION);
+            adjacentAnimation.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mFallbackRecentsView.resetTaskVisuals();
+                }
+            });
+            target.play(adjacentAnimation);
+        }
+        return target;
     }
 
     @Override
@@ -95,6 +155,7 @@
     protected void onStart() {
         super.onStart();
         UiFactory.onStart(this);
+        mFallbackRecentsView.resetTaskVisuals();
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/TaskUtils.java b/quickstep/src/com/android/quickstep/TaskUtils.java
index 5bf1d07..c66f00f 100644
--- a/quickstep/src/com/android/quickstep/TaskUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskUtils.java
@@ -16,25 +16,47 @@
 
 package com.android.quickstep;
 
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
+import static com.android.systemui.shared.recents.utilities.Utilities.getNextFrameNumber;
+import static com.android.systemui.shared.recents.utilities.Utilities.getSurface;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
+
+import android.animation.ValueAnimator;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.graphics.Matrix;
+import android.graphics.Rect;
 import android.os.UserHandle;
 import android.util.Log;
+import android.view.Surface;
+import android.view.View;
 
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.ItemInfo;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.util.ComponentKey;
+import com.android.quickstep.RecentsAnimationInterpolator.TaskWindowBounds;
+import com.android.quickstep.util.MultiValueUpdateListener;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.TransactionCompat;
 
 /**
  * Contains helpful methods for retrieving data from {@link Task}s.
- * TODO: remove this once we switch to getting the icon and label from IconCache.
  */
 public class TaskUtils {
 
     private static final String TAG = "TaskUtils";
 
+    /**
+     * TODO: remove this once we switch to getting the icon and label from IconCache.
+     */
     public static CharSequence getTitle(Context context, Task task) {
         LauncherAppsCompat launcherAppsCompat = LauncherAppsCompat.getInstance(context);
         UserManagerCompat userManagerCompat = UserManagerCompat.getInstance(context);
@@ -53,4 +75,148 @@
     public static ComponentKey getComponentKeyForTask(Task.TaskKey taskKey) {
         return new ComponentKey(taskKey.getComponent(), UserHandle.of(taskKey.userId));
     }
+
+
+    /**
+     * Try to find a TaskView that corresponds with the component of the launched view.
+     *
+     * If this method returns a non-null TaskView, it will be used in composeRecentsLaunchAnimation.
+     * Otherwise, we will assume we are using a normal app transition, but it's possible that the
+     * opening remote target (which we don't get until onAnimationStart) will resolve to a TaskView.
+     */
+    public static TaskView findTaskViewToLaunch(
+            BaseDraggingActivity activity, View v, RemoteAnimationTargetCompat[] targets) {
+        if (v instanceof TaskView) {
+            return (TaskView) v;
+        }
+        RecentsView recentsView = activity.getOverviewPanel();
+
+        // It's possible that the launched view can still be resolved to a visible task view, check
+        // the task id of the opening task and see if we can find a match.
+        if (v.getTag() instanceof ItemInfo) {
+            ItemInfo itemInfo = (ItemInfo) v.getTag();
+            ComponentName componentName = itemInfo.getTargetComponent();
+            if (componentName != null) {
+                for (int i = 0; i < recentsView.getChildCount(); i++) {
+                    TaskView taskView = recentsView.getPageAt(i);
+                    if (recentsView.isTaskViewVisible(taskView)) {
+                        Task task = taskView.getTask();
+                        if (componentName.equals(task.key.getComponent())) {
+                            return taskView;
+                        }
+                    }
+                }
+            }
+        }
+
+        if (targets == null) {
+            return null;
+        }
+        // Resolve the opening task id
+        int openingTaskId = -1;
+        for (RemoteAnimationTargetCompat target : targets) {
+            if (target.mode == MODE_OPENING) {
+                openingTaskId = target.taskId;
+                break;
+            }
+        }
+
+        // If there is no opening task id, fall back to the normal app icon launch animation
+        if (openingTaskId == -1) {
+            return null;
+        }
+
+        // If the opening task id is not currently visible in overview, then fall back to normal app
+        // icon launch animation
+        TaskView taskView = recentsView.getTaskView(openingTaskId);
+        if (taskView == null || !recentsView.isTaskViewVisible(taskView)) {
+            return null;
+        }
+        return taskView;
+    }
+
+
+    /**
+     * @return Animator that controls the window of the opening targets for the recents launch
+     * animation.
+     */
+    public static ValueAnimator getRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
+            RemoteAnimationTargetCompat[] targets) {
+        final RecentsAnimationInterpolator recentsInterpolator = v.getRecentsInterpolator();
+
+        Rect crop = new Rect();
+        Matrix matrix = new Matrix();
+
+        ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
+        appAnimator.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
+        appAnimator.addUpdateListener(new MultiValueUpdateListener() {
+
+            // Defer fading out the view until after the app window gets faded in
+            FloatProp mViewAlpha = new FloatProp(1f, 0f, 75, 75, LINEAR);
+            FloatProp mTaskAlpha = new FloatProp(0f, 1f, 0, 75, LINEAR);
+
+            boolean isFirstFrame = true;
+
+            @Override
+            public void onUpdate(float percent) {
+
+                final Surface surface = getSurface(v);
+                final long frameNumber = surface != null ? getNextFrameNumber(surface) : -1;
+                if (frameNumber == -1) {
+                    // Booo, not cool! Our surface got destroyed, so no reason to animate anything.
+                    Log.w(TAG, "Failed to animate, surface got destroyed.");
+                    return;
+                }
+                TaskWindowBounds tw = recentsInterpolator.interpolate(percent);
+
+                if (!skipViewChanges) {
+                    v.setScaleX(tw.taskScale);
+                    v.setScaleY(tw.taskScale);
+                    v.setTranslationX(tw.taskX);
+                    v.setTranslationY(tw.taskY);
+                    v.setAlpha(mViewAlpha.value);
+                }
+
+                matrix.setScale(tw.winScale, tw.winScale);
+                matrix.postTranslate(tw.winX, tw.winY);
+                crop.set(tw.winCrop);
+
+                TransactionCompat t = new TransactionCompat();
+                for (RemoteAnimationTargetCompat target : targets) {
+                    if (target.mode == RemoteAnimationTargetCompat.MODE_OPENING) {
+                        t.setAlpha(target.leash, mTaskAlpha.value);
+
+                        // TODO: This isn't correct at the beginning of the animation, but better
+                        // than nothing.
+                        matrix.postTranslate(target.position.x, target.position.y);
+                        t.setMatrix(target.leash, matrix);
+                        t.setWindowCrop(target.leash, crop);
+
+                        if (!skipViewChanges) {
+                            t.deferTransactionUntil(target.leash, surface, frameNumber);
+                        }
+                    }
+                    if (isFirstFrame) {
+                        t.show(target.leash);
+                    }
+                }
+                t.setEarlyWakeup();
+                t.apply();
+
+                matrix.reset();
+                isFirstFrame = false;
+            }
+        });
+        return appAnimator;
+    }
+
+    public static boolean taskIsATargetWithMode(RemoteAnimationTargetCompat[] targets,
+            int taskId, int mode) {
+        for (RemoteAnimationTargetCompat target : targets) {
+            if (target.mode == mode && target.taskId == taskId) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java b/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java
new file mode 100644
index 0000000..e798d5c
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import android.animation.ValueAnimator;
+import android.view.animation.Interpolator;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class to update multiple values with different interpolators and durations during
+ * the same animation.
+ */
+public abstract class MultiValueUpdateListener implements ValueAnimator.AnimatorUpdateListener {
+
+    private final ArrayList<FloatProp> mAllProperties = new ArrayList<>();
+
+    @Override
+    public final void onAnimationUpdate(ValueAnimator animator) {
+        final float percent = animator.getAnimatedFraction();
+        final float currentPlayTime = percent * animator.getDuration();
+
+        for (int i = mAllProperties.size() - 1; i >= 0; i--) {
+            FloatProp prop = mAllProperties.get(i);
+            float time = Math.max(0, currentPlayTime - prop.mDelay);
+            float newPercent = Math.min(1f, time / prop.mDuration);
+            newPercent = prop.mInterpolator.getInterpolation(newPercent);
+            prop.value = prop.mEnd * newPercent + prop.mStart * (1 - newPercent);
+        }
+        onUpdate(percent);
+    }
+
+    public abstract void onUpdate(float percent);
+
+    public final class FloatProp {
+
+        public float value;
+
+        private final float mStart;
+        private final float mEnd;
+        private final float mDelay;
+        private final float mDuration;
+        private final Interpolator mInterpolator;
+
+        public FloatProp(float start, float end, float delay, float duration, Interpolator i) {
+            value = mStart = start;
+            mEnd = end;
+            mDelay = delay;
+            mDuration = duration;
+            mInterpolator = i;
+
+            mAllProperties.add(this);
+        }
+    }
+}