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/Android.mk b/Android.mk
index f0c0117..71bba0f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -259,6 +259,7 @@
core/java/android/view/accessibility/IAccessibilityManager.aidl \
core/java/android/view/accessibility/IAccessibilityManagerClient.aidl \
core/java/android/view/IApplicationToken.aidl \
+ core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl \
core/java/android/view/IAssetAtlas.aidl \
core/java/android/view/IGraphicsStats.aidl \
core/java/android/view/IInputFilter.aidl \
diff --git a/core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl b/core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl
new file mode 100644
index 0000000..622b9dd
--- /dev/null
+++ b/core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2015 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 android.view;
+
+import android.view.AppTransitionAnimationSpec;
+
+/**
+ * A cross-process future to fetch the specifications for app transitions.
+ *
+ * {@hide}
+ */
+interface IAppTransitionAnimationSpecsFuture {
+ AppTransitionAnimationSpec[] get();
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index e8a1e32..6ba07e1 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -28,6 +28,7 @@
import android.os.Bundle;
import android.os.IRemoteCallback;
import android.view.IApplicationToken;
+import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.IOnKeyguardExitResult;
import android.view.IRotationWatcher;
import android.view.IWindowSession;
@@ -140,6 +141,15 @@
void overridePendingAppTransitionMultiThumb(in AppTransitionAnimationSpec[] specs,
IRemoteCallback startedCallback, IRemoteCallback finishedCallback, boolean scaleUp);
void overridePendingAppTransitionInPlace(String packageName, int anim);
+
+ /**
+ * Like overridePendingAppTransitionMultiThumb, but uses a future to supply the specs. This is
+ * used for recents, where generating the thumbnails of the specs takes a non-trivial amount of
+ * time, so we want to move that off the critical path for starting the new activity.
+ */
+ void overridePendingAppTransitionMultiThumbFuture(
+ IAppTransitionAnimationSpecsFuture specsFuture, IRemoteCallback startedCallback,
+ boolean scaleUp);
void executeAppTransition();
void setAppStartingWindow(IBinder token, String pkg, int theme,
in CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index ab05bb0..f800785 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -104,9 +104,6 @@
// Runnables to finish the Recents activity
FinishRecentsRunnable mFinishLaunchHomeRunnable;
- // Runnable to be executed after we paused ourselves
- Runnable mAfterPauseRunnable;
-
// The trigger to automatically launch the current task
DozeTrigger mIterateTrigger = new DozeTrigger(500, new Runnable() {
@Override
@@ -426,9 +423,10 @@
@Override
protected void onPause() {
super.onPause();
- if (mAfterPauseRunnable != null) {
- mRecentsView.post(mAfterPauseRunnable);
- mAfterPauseRunnable = null;
+
+ if (Constants.DebugFlags.App.EnableFastToggleRecents) {
+ // Stop the fast-toggle dozer
+ mIterateTrigger.stopDozing();
}
if (Constants.DebugFlags.App.EnableFastToggleRecents) {
@@ -591,11 +589,6 @@
mFinishLaunchHomeRunnable.run();
}
- @Override
- public void runAfterPause(Runnable r) {
- mAfterPauseRunnable = r;
- }
-
/**** EventBus events ****/
public final void onBusEvent(ToggleRecentsEvent event) {
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 1f80460..9a5e141 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -17,6 +17,7 @@
package com.android.systemui.recents.views;
import android.app.ActivityOptions;
+import android.app.ActivityOptions.OnAnimationStartedListener;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -30,6 +31,7 @@
import android.util.Log;
import android.util.SparseArray;
import android.view.AppTransitionAnimationSpec;
+import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -38,6 +40,8 @@
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
+
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
@@ -81,7 +85,6 @@
public void onTaskViewClicked();
public void onTaskLaunchFailed();
public void onAllTaskViewsDismissed();
- public void runAfterPause(Runnable r);
}
LayoutInflater mInflater;
@@ -104,6 +107,9 @@
Rect mSystemInsets = new Rect();
+ @GuardedBy("this")
+ List<AppTransitionAnimationSpec> mAppTransitionAnimationSpecs;
+
public RecentsView(Context context) {
super(context);
}
@@ -412,56 +418,37 @@
}
}
- private void postDrawHeaderThumbnailTransitionRunnable(final TaskStackView view,
- final TaskView clickedView, final int offsetX, final int offsetY,
- final float stackScroll,
- final ActivityOptions.OnAnimationStartedListener animStartedListener,
- final int destinationStack) {
- Runnable r = new Runnable() {
+ private IAppTransitionAnimationSpecsFuture getAppTransitionFuture(final TaskStackView stackView,
+ final TaskView clickedTask, final int offsetX, final int offsetY,
+ final float stackScroll, final int destinationStack) {
+ return new IAppTransitionAnimationSpecsFuture.Stub() {
@Override
- public void run() {
- overrideDrawHeaderThumbnailTransition(view, clickedView, offsetX, offsetY,
- stackScroll, animStartedListener, destinationStack);
-
- }
- };
-
- mCb.runAfterPause(r);
- }
-
- private void overrideDrawHeaderThumbnailTransition(TaskStackView stackView,
- TaskView clickedTask, int offsetX, int offsetY, float stackScroll,
- final ActivityOptions.OnAnimationStartedListener animStartedListener,
- int destinationStack) {
- List<AppTransitionAnimationSpec> specs = getAppTransitionAnimationSpecs(stackView,
- clickedTask, offsetX, offsetY, stackScroll, destinationStack);
- if (specs == null) {
- return;
- }
-
- IRemoteCallback.Stub callback = new IRemoteCallback.Stub() {
- @Override
- public void sendResult(Bundle data) throws RemoteException {
+ public AppTransitionAnimationSpec[] get() throws RemoteException {
post(new Runnable() {
@Override
public void run() {
- if (animStartedListener != null) {
- animStartedListener.onAnimationStarted();
+ synchronized (RecentsView.this) {
+ mAppTransitionAnimationSpecs = getAppTransitionAnimationSpecs(stackView,
+ clickedTask, offsetX, offsetY, stackScroll, destinationStack);
+ RecentsView.this.notifyAll();
}
}
});
+ synchronized (RecentsView.this) {
+ while (mAppTransitionAnimationSpecs == null) {
+ try {
+ RecentsView.this.wait();
+ } catch (InterruptedException e) {}
+ }
+ if (mAppTransitionAnimationSpecs == null) {
+ return null;
+ }
+ AppTransitionAnimationSpec[] specs
+ = new AppTransitionAnimationSpec[mAppTransitionAnimationSpecs.size()];
+ return mAppTransitionAnimationSpecs.toArray(specs);
+ }
}
};
-
- AppTransitionAnimationSpec[] specsArray =
- new AppTransitionAnimationSpec[specs.size()];
- try {
- WindowManagerGlobal.getWindowManagerService().overridePendingAppTransitionMultiThumb(
- specs.toArray(specsArray), callback, null, true /* scaleUp */);
-
- } catch (RemoteException e) {
- Log.w(TAG, "Error overriding app transition", e);
- }
}
private List<AppTransitionAnimationSpec> getAppTransitionAnimationSpecs(TaskStackView stackView,
@@ -613,8 +600,9 @@
// Compute the thumbnail to scale up from
final SystemServicesProxy ssp = Recents.getSystemServices();
boolean screenPinningRequested = false;
- ActivityOptions opts = null;
+ ActivityOptions opts = ActivityOptions.makeBasic();
ActivityOptions.OnAnimationStartedListener animStartedListener = null;
+ final IAppTransitionAnimationSpecsFuture transitionFuture;
if (task.thumbnail != null && task.thumbnail.getWidth() > 0 &&
task.thumbnail.getHeight() > 0) {
animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
@@ -636,21 +624,18 @@
}
}
};
- 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);
+ transitionFuture = getAppTransitionFuture(stackView, tv, offsetX, offsetY, stackScroll,
+ destinationStack);
screenPinningRequested = true;
} else {
- opts = ActivityOptions.makeBasic();
+ transitionFuture = null;
}
if (boundsValid) {
opts.setBounds(bounds.isEmpty() ? null : bounds);
}
final ActivityOptions launchOpts = opts;
final boolean finalScreenPinningRequested = screenPinningRequested;
+ final OnAnimationStartedListener finalAnimStartedListener = animStartedListener;
final Runnable launchRunnable = new Runnable() {
@Override
public void run() {
@@ -678,6 +663,28 @@
MetricsLogger.count(getContext(), "overview_task_launch_failed", 1);
}
}
+ if (transitionFuture != null) {
+ IRemoteCallback.Stub callback = new IRemoteCallback.Stub() {
+ @Override
+ public void sendResult(Bundle data) throws RemoteException {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ if (finalAnimStartedListener != null) {
+ finalAnimStartedListener.onAnimationStarted();
+ }
+ }
+ });
+ }
+ };
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .overridePendingAppTransitionMultiThumbFuture(transitionFuture,
+ callback, true /* scaleUp */);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to override transition: " + e);
+ }
+ }
}
};
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();