Add ability to cancel task window transitions.

Bug: 25392381

Change-Id: I45f48edc21c058df0e4c22ceaf7e9aef5899a29c
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index f18b35d..c01c6df 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -201,6 +201,11 @@
      */
     void setScreenCaptureDisabled(int userId, boolean disabled);
 
+    /**
+     * Cancels the window transitions for the given task.
+     */
+    void cancelTaskWindowTransition(int taskId);
+
     // These can only be called with the SET_ORIENTATION permission.
     /**
      * Update the current screen rotation based on the current state of
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 4d575bc..b5a1a93 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -517,6 +517,15 @@
                 boolean hasRepKeyTimeElapsed = (SystemClock.elapsedRealtime() -
                         mLastTabKeyEventTime) > altTabKeyDelay;
                 if (event.getRepeatCount() <= 0 || hasRepKeyTimeElapsed) {
+                    // As we iterate to the next/previous task, cancel any current/lagging window
+                    // transition animations
+                    RecentsConfiguration config = Recents.getConfiguration();
+                    RecentsActivityLaunchState launchState = config.getLaunchState();
+                    if (launchState.launchedToTaskId != -1) {
+                        SystemServicesProxy ssp = Recents.getSystemServices();
+                        ssp.cancelWindowTransition(launchState.launchedToTaskId);
+                    }
+
                     // Focus the next task in the stack
                     final boolean backward = event.isShiftPressed();
                     if (backward) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 141562c..81a949d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -57,6 +57,7 @@
 import android.util.Pair;
 import android.view.Display;
 import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
 import com.android.internal.app.AssistUtils;
 import com.android.internal.os.BackgroundThread;
@@ -335,6 +336,19 @@
         return stackInfo != null;
     }
 
+    /**
+     * Cancels the current window transtion to/from Recents for the given task id.
+     */
+    public void cancelWindowTransition(int taskId) {
+        if (mWm == null) return;
+
+        try {
+            WindowManagerGlobal.getWindowManagerService().cancelTaskWindowTransition(taskId);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
     /** Returns the top task thumbnail for the given task id */
     public Bitmap getTaskThumbnail(int taskId) {
         if (mAm == null) return null;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 422e917..a5b7aaf 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -44,6 +44,7 @@
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsActivity;
+import com.android.systemui.recents.RecentsActivityLaunchState;
 import com.android.systemui.recents.RecentsAppWidgetHostView;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.events.EventBus;
@@ -587,6 +588,20 @@
         return new AppTransitionAnimationSpec(taskId, b, rect);
     }
 
+    /**
+     * Cancels any running window transitions for the launched task (the task animating into
+     * Recents).
+     */
+    private void cancelLaunchedTaskWindowTransition(final Task task) {
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        RecentsConfiguration config = Recents.getConfiguration();
+        RecentsActivityLaunchState launchState = config.getLaunchState();
+        if (launchState.launchedToTaskId != -1 &&
+                launchState.launchedToTaskId != task.key.id) {
+            ssp.cancelWindowTransition(launchState.launchedToTaskId);
+        }
+    }
+
     /**** TaskStackView.TaskStackCallbacks Implementation ****/
 
     @Override
@@ -617,34 +632,37 @@
 
         // Compute the thumbnail to scale up from
         final SystemServicesProxy ssp = Recents.getSystemServices();
+        boolean screenPinningRequested = false;
         ActivityOptions opts = null;
         ActivityOptions.OnAnimationStartedListener animStartedListener = null;
         if (task.thumbnail != null && task.thumbnail.getWidth() > 0 &&
                 task.thumbnail.getHeight() > 0) {
-            if (lockToTask) {
-                animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
-                    boolean mTriggered = false;
-                    @Override
-                    public void onAnimationStarted() {
-                        if (!mTriggered) {
-                            postDelayed(new Runnable() {
-                                @Override
-                                public void run() {
-                                    EventBus.getDefault().send(new ScreenPinningRequestEvent(
-                                            getContext(), ssp));
-                                }
-                            }, 350);
-                            mTriggered = true;
-                        }
+            animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
+                @Override
+                public void onAnimationStarted() {
+                    // If we are launching into another task, cancel the previous task's
+                    // window transition
+                    cancelLaunchedTaskWindowTransition(task);
+
+                    if (lockToTask) {
+                        // Request screen pinning after the animation runs
+                        postDelayed(new Runnable() {
+                            @Override
+                            public void run() {
+                                EventBus.getDefault().send(new ScreenPinningRequestEvent(
+                                        getContext(), ssp));
+                            }
+                        }, 350);
                     }
-                };
-            }
+                }
+            };
             postDrawHeaderThumbnailTransitionRunnable(stackView, tv, offsetX, offsetY, stackScroll,
                     animStartedListener, destinationStack);
             opts = ActivityOptions.makeThumbnailAspectScaleUpAnimation(sourceView,
                     Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8).createAshmemBitmap(),
                     offsetX, offsetY, (int) transform.rect.width(), (int) transform.rect.height(),
                     sourceView.getHandler(), animStartedListener);
+            screenPinningRequested = true;
         } else {
             opts = ActivityOptions.makeBasic();
         }
@@ -652,7 +670,7 @@
             opts.setBounds(bounds.isEmpty() ? null : bounds);
         }
         final ActivityOptions launchOpts = opts;
-        final boolean screenPinningRequested = (animStartedListener == null) && lockToTask;
+        final boolean finalScreenPinningRequested = screenPinningRequested;
         final Runnable launchRunnable = new Runnable() {
             @Override
             public void run() {
@@ -660,9 +678,11 @@
                     // Bring an active task to the foreground
                     ssp.moveTaskToFront(task.key.id, launchOpts);
                 } else {
-                    if (ssp.startActivityFromRecents(getContext(), task.key.id,
-                            task.activityLabel, launchOpts)) {
-                        if (screenPinningRequested) {
+                    if (ssp.startActivityFromRecents(getContext(), task.key.id, task.activityLabel,
+                            launchOpts)) {
+                        if (!finalScreenPinningRequested) {
+                            // If we have not requested this already to be run after the window
+                            // transition, then just run it now
                             EventBus.getDefault().send(new ScreenPinningRequestEvent(
                                     getContext(), ssp));
                         }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index ad44196..d4d99af 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -320,6 +320,15 @@
         }
     }
 
+    /**
+     * Cancels any running app transitions associated with the task.
+     */
+    void cancelTaskWindowTransition() {
+        for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
+            mAppTokens.get(activityNdx).mAppAnimator.clearAnimation();
+        }
+    }
+
     boolean showForAllUsers() {
         final int tokensCount = mAppTokens.size();
         return (tokensCount != 0) && mAppTokens.get(tokensCount - 1).showForAllUsers;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 75680a0..bcfc8f2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4607,6 +4607,16 @@
         }
     }
 
+    @Override
+    public void cancelTaskWindowTransition(int taskId) {
+        synchronized (mWindowMap) {
+            Task task = mTaskIdToTask.get(taskId);
+            if (task != null) {
+                task.cancelTaskWindowTransition();
+            }
+        }
+    }
+
     public void addTask(int taskId, int stackId, boolean toTop) {
         synchronized (mWindowMap) {
             if (DEBUG_STACK) Slog.i(TAG, "addTask: adding taskId=" + taskId