Supply app transition specs with a future

Because we retain activity surfaces now, the app transition specs
which were calculated/generated after the onPause() call when going
from recents -> app were too slow. Instead, supply a cross-process
future, which gets fetched when the window manager is about to be
ready to execute the app transition. In practice, this still gets
executed immediately after the onPause call.

If we have a retained surface, this adds some latency, but since we
absolutely need the specs to execute the transition, we have that
latency no matter where exactly we generate the specs.

If we don't have a retained surface, the specs are not calculated on
the critical path, so it's faster.

Bug: 19940527
Change-Id: I80d2c6f6b3a6568a70339619ecefbc3bd8409bd8
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 25a23fd..68034c8 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -49,9 +49,11 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
+import android.os.RemoteException;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.AppTransitionAnimationSpec;
+import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.WindowManager;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
@@ -71,6 +73,8 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 // State management of app transitions.  When we are preparing for a
 // transition, mNextAppTransition will be the kind of transition to
@@ -168,6 +172,8 @@
     // Keyed by task id.
     private final SparseArray<AppTransitionAnimationSpec> mNextAppTransitionAnimationsSpecs
             = new SparseArray<>();
+    private IAppTransitionAnimationSpecsFuture mNextAppTransitionAnimationsSpecsFuture;
+    private boolean mNextAppTransitionAnimationsSpecsPending;
     private AppTransitionAnimationSpec mDefaultNextAppTransitionAnimationSpec;
 
     private Rect mNextAppTransitionInsets = new Rect();
@@ -200,10 +206,16 @@
     private int mCurrentUserId = 0;
 
     private final ArrayList<AppTransitionListener> mListeners = new ArrayList<>();
+    private final ExecutorService mDefaultExecutor = Executors.newSingleThreadExecutor();
+    private final Object mServiceLock;
+    private final WindowSurfacePlacer mWindowSurfacePlacer;
 
-    AppTransition(Context context, Handler h) {
+    AppTransition(Context context, Handler h, Object serviceLock,
+            WindowSurfacePlacer windowSurfacePlacer) {
         mContext = context;
         mH = h;
+        mServiceLock = serviceLock;
+        mWindowSurfacePlacer = windowSurfacePlacer;
         mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
                 com.android.internal.R.interpolator.linear_out_slow_in);
         mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
@@ -262,6 +274,7 @@
 
     void setReady() {
         mAppTransitionState = APP_STATE_READY;
+        fetchAppTransitionSpecsFromFuture();
     }
 
     boolean isRunning() {
@@ -296,6 +309,14 @@
         return mNextAppTransitionScaleUp;
     }
 
+    /**
+     * @return true if and only if we are currently fetching app transition specs from the future
+     *         passed into {@link #overridePendingAppTransitionMultiThumbFuture}
+     */
+    boolean isFetchingAppTransitionsSpecs() {
+        return mNextAppTransitionAnimationsSpecsPending;
+    }
+
     private boolean prepare() {
         if (!isRunning()) {
             mAppTransitionState = APP_STATE_IDLE;
@@ -1408,6 +1429,24 @@
         }
     }
 
+    void overridePendingAppTransitionMultiThumbFuture(
+            IAppTransitionAnimationSpecsFuture specsFuture, IRemoteCallback callback,
+            boolean scaleUp) {
+        if (isTransitionSet()) {
+            mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP
+                    : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
+            mNextAppTransitionPackage = null;
+            mDefaultNextAppTransitionAnimationSpec = null;
+            mNextAppTransitionAnimationsSpecs.clear();
+            mNextAppTransitionAnimationsSpecsFuture = specsFuture;
+            mNextAppTransitionScaleUp = scaleUp;
+            postAnimationCallback();
+            mNextAppTransitionCallback = callback;
+        } else {
+            postAnimationCallback();
+        }
+    }
+
     void overrideInPlaceAppTransition(String packageName, int anim) {
         if (isTransitionSet()) {
             mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE;
@@ -1419,6 +1458,35 @@
         }
     }
 
+    /**
+     * If a future is set for the app transition specs, fetch it in another thread.
+     */
+    private void fetchAppTransitionSpecsFromFuture() {
+        if (mNextAppTransitionAnimationsSpecsFuture != null) {
+            mNextAppTransitionAnimationsSpecsPending = true;
+            final IAppTransitionAnimationSpecsFuture future
+                    = mNextAppTransitionAnimationsSpecsFuture;
+            mNextAppTransitionAnimationsSpecsFuture = null;
+            mDefaultExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    AppTransitionAnimationSpec[] specs = null;
+                    try {
+                        specs = future.get();
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "Failed to fetch app transition specs: " + e);
+                    }
+                    synchronized (mServiceLock) {
+                        mNextAppTransitionAnimationsSpecsPending = false;
+                        overridePendingAppTransitionMultiThumb(specs, mNextAppTransitionCallback,
+                                null /* finishedCallback */, mNextAppTransitionScaleUp);
+                        mWindowSurfacePlacer.requestTraversal();
+                    }
+                }
+            });
+        }
+    }
+
     @Override
     public String toString() {
         return "mNextAppTransition=" + appTransitionToString(mNextAppTransition);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 59f420a..6b00d08 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -117,6 +117,7 @@
 import android.view.DropPermissionHolder;
 import android.view.Gravity;
 import android.view.IApplicationToken;
+import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.IInputFilter;
 import android.view.IOnKeyguardExitResult;
 import android.view.IRotationWatcher;
@@ -908,7 +909,7 @@
                 PowerManager.PARTIAL_WAKE_LOCK, "SCREEN_FROZEN");
         mScreenFrozenLock.setReferenceCounted(false);
 
-        mAppTransition = new AppTransition(context, mH);
+        mAppTransition = new AppTransition(context, mH, mWindowMap, mWindowPlacerLocked);
         mAppTransition.registerListenerLocked(mActivityManagerAppTransitionNotifier);
 
         mActivityManager = ActivityManagerNative.getDefault();
@@ -3661,8 +3662,8 @@
             int startY, int targetWidth, int targetHeight, IRemoteCallback startedCallback,
             boolean scaleUp) {
         synchronized(mWindowMap) {
-            mAppTransition.overridePendingAppTransitionAspectScaledThumb(srcThumb, startX,
-                    startY, targetWidth, targetHeight, startedCallback, scaleUp);
+            mAppTransition.overridePendingAppTransitionAspectScaledThumb(srcThumb, startX, startY,
+                    targetWidth, targetHeight, startedCallback, scaleUp);
         }
     }
 
@@ -3702,6 +3703,16 @@
     }
 
     @Override
+    public void overridePendingAppTransitionMultiThumbFuture(
+            IAppTransitionAnimationSpecsFuture specsFuture, IRemoteCallback callback,
+            boolean scaleUp) {
+        synchronized(mWindowMap) {
+            mAppTransition.overridePendingAppTransitionMultiThumbFuture(specsFuture, callback,
+                    scaleUp);
+        }
+    }
+
+    @Override
     public void endProlongedAnimations() {
         synchronized (mWindowMap) {
             for (final WindowState win : mWindowMap.values()) {
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index a8fb77e..d1fc3bc 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -1260,6 +1260,12 @@
                 }
             }
 
+            // We also need to wait for the specs to be fetched, if needed.
+            if (mService.mAppTransition.isFetchingAppTransitionsSpecs()) {
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "isFetchingAppTransitionSpecs=true");
+                return false;
+            }
+
             // If the wallpaper is visible, we need to check it's ready too.
             return !mWallpaperControllerLocked.isWallpaperVisible() ||
                     mWallpaperControllerLocked.wallpaperTransitionReady();