Add ability to cancel task window transitions.

Bug: 25392381

Change-Id: I9ab08c81bb72a14a60d8e7148cecfea9bf1cafe5
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 5994d4f..bb09829 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -163,6 +163,16 @@
      */
     void setScreenCaptureDisabled(int userId, boolean disabled);
 
+    /**
+     * Cancels the window transitions for the given task.
+     */
+    void cancelTaskWindowTransition(int taskId);
+
+    /**
+     * Cancels the thumbnail transitions for the given task.
+     */
+    void cancelTaskThumbnailTransition(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 b641aaf..9e00b7aa 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -533,6 +533,15 @@
                 boolean hasRepKeyTimeElapsed = (SystemClock.elapsedRealtime() -
                         mLastTabKeyEventTime) > mConfig.altTabKeyDelay;
                 if (event.getRepeatCount() <= 0 || hasRepKeyTimeElapsed) {
+                    // As we iterate to the next/previous task, cancel any current/lagging window
+                    // transition animations
+                    if (mConfig.launchedToTaskId != -1) {
+                        SystemServicesProxy ssp =
+                                RecentsTaskLoader.getInstance().getSystemServicesProxy();
+                        ssp.cancelThumbnailTransition(getTaskId());
+                        ssp.cancelWindowTransition(mConfig.launchedToTaskId);
+                    }
+
                     // Focus the next task in the stack
                     final boolean backward = event.isShiftPressed();
                     mRecentsView.focusNextTask(!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 d5c9253..51bb00b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -59,6 +59,7 @@
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.app.AssistUtils;
@@ -321,6 +322,29 @@
         return mAm.isInHomeStack(taskId);
     }
 
+    /**
+     * 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();
+        }
+    }
+
+    public void cancelThumbnailTransition(int taskId) {
+        if (mWm == null) return;
+
+        try {
+            WindowManagerGlobal.getWindowManagerService().cancelTaskThumbnailTransition(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 6a28311..3c7d48c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.recents.views;
 
+import android.app.Activity;
 import android.app.ActivityOptions;
 import android.app.TaskStackBuilder;
 import android.content.Context;
@@ -25,6 +26,7 @@
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.IRemoteCallback;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -35,6 +37,7 @@
 import android.view.View;
 import android.view.WindowInsets;
 import android.view.WindowManagerGlobal;
+import android.view.animation.AnimationUtils;
 import android.widget.FrameLayout;
 
 import com.android.internal.logging.MetricsLogger;
@@ -502,6 +505,29 @@
     }
     /**** TaskStackView.TaskStackCallbacks Implementation ****/
 
+    /**
+     * Cancels any running window transitions for the launched task (the task animating into
+     * Recents).
+     */
+    private void cancelLaunchedTaskWindowTransitionWithDelay(final Task task, long delay) {
+        final SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+        if (mConfig.launchedToTaskId != -1 &&
+                mConfig.launchedToTaskId != task.key.id) {
+            final Handler handler = new Handler();
+            handler.postDelayed(new Runnable() {
+              @Override
+              public void run() {
+                  ssp.cancelThumbnailTransition(((Activity) getContext()).getTaskId());
+                  ssp.cancelWindowTransition(mConfig.launchedToTaskId);
+              }
+            }, delay);
+        }
+    }
+
+    private void cancelLaunchedTaskWindowTransition(final Task task) {
+        cancelLaunchedTaskWindowTransitionWithDelay(task, 0);
+    }
+
     @Override
     public void onTaskViewClicked(final TaskStackView stackView, final TaskView tv,
                                   final TaskStack stack, final Task task, final boolean lockToTask) {
@@ -533,6 +559,9 @@
         // Compute the thumbnail to scale up from
         final SystemServicesProxy ssp =
                 RecentsTaskLoader.getInstance().getSystemServicesProxy();
+               final long enterDuration =
+                       AnimationUtils.loadAnimation(getContext(), R.anim.recents_from_unknown_enter)
+                       .getDuration();
         ActivityOptions opts = null;
         if (task.thumbnail != null && task.thumbnail.getWidth() > 0 &&
                 task.thumbnail.getHeight() > 0) {
@@ -542,6 +571,10 @@
                     boolean mTriggered = false;
                     @Override
                     public void onAnimationStarted() {
+                        // If we are launching into another task, cancel the previous task's
+                        // window transition
+                        cancelLaunchedTaskWindowTransitionWithDelay(task, enterDuration / 2);
+
                         if (!mTriggered) {
                             postDelayed(new Runnable() {
                                 @Override
@@ -553,7 +586,10 @@
                         }
                     }
                 };
+            } else {
+                cancelLaunchedTaskWindowTransitionWithDelay(task, enterDuration / 2);
             }
+
             if (tv != null) {
                 postDrawHeaderThumbnailTransitionRunnable(tv, offsetX, offsetY, transform,
                         animStartedListener);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 0c3cf65..daf28cf 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -107,6 +107,21 @@
         }
     }
 
+    /**
+     * 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();
+        }
+    }
+
+    void cancelTaskThumbnailTransition() {
+        for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
+            mAppTokens.get(activityNdx).mAppAnimator.clearThumbnail();
+        }
+    }
+
     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 71bbdb6..15ae804 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5216,6 +5216,26 @@
         }
     }
 
+    @Override
+    public void cancelTaskWindowTransition(int taskId) {
+        synchronized (mWindowMap) {
+            Task task = mTaskIdToTask.get(taskId);
+            if (task != null) {
+                task.cancelTaskWindowTransition();
+            }
+        }
+    }
+
+    @Override
+    public void cancelTaskThumbnailTransition(int taskId) {
+        synchronized (mWindowMap) {
+            Task task = mTaskIdToTask.get(taskId);
+            if (task != null) {
+                task.cancelTaskThumbnailTransition();
+            }
+        }
+    }
+
     public void addTask(int taskId, int stackId, boolean toTop) {
         synchronized (mWindowMap) {
             if (DEBUG_STACK) Slog.i(TAG, "addTask: adding taskId=" + taskId
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 82012c1..17db317 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -511,4 +511,12 @@
         // TODO Auto-generated method stub
         return null;
     }
+
+    @Override
+    public void cancelTaskWindowTransition(int taskId) {
+    }
+
+    @Override
+    public void cancelTaskThumbnailTransition(int taskId) {
+    }
 }