diff options
| author | 2019-01-24 09:20:46 +0000 | |
|---|---|---|
| committer | 2019-01-24 09:20:46 +0000 | |
| commit | 43b9e11886a9e78f02b7ec6a65b93f7ac78cd26d (patch) | |
| tree | 1b5abeb414aace3f224f5e627333982b2237b480 | |
| parent | 82c9a92d5e6d74f75cc0b672a42f2b2a15fbd2ed (diff) | |
| parent | 2289ba174d93a2acf257794dbc497ca384189890 (diff) | |
Merge "Change Transition"
17 files changed, 697 insertions, 124 deletions
diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java index 567b279ead11..1d2cf4b78756 100644 --- a/core/java/android/view/RemoteAnimationTarget.java +++ b/core/java/android/view/RemoteAnimationTarget.java @@ -24,6 +24,8 @@ import static android.view.RemoteAnimationTargetProto.MODE; import static android.view.RemoteAnimationTargetProto.POSITION; import static android.view.RemoteAnimationTargetProto.PREFIX_ORDER_INDEX; import static android.view.RemoteAnimationTargetProto.SOURCE_CONTAINER_BOUNDS; +import static android.view.RemoteAnimationTargetProto.START_BOUNDS; +import static android.view.RemoteAnimationTargetProto.START_LEASH; import static android.view.RemoteAnimationTargetProto.TASK_ID; import static android.view.RemoteAnimationTargetProto.WINDOW_CONFIGURATION; @@ -57,9 +59,15 @@ public class RemoteAnimationTarget implements Parcelable { */ public static final int MODE_CLOSING = 1; + /** + * The app is in the set of resizing apps (eg. mode change) of this transition. + */ + public static final int MODE_CHANGING = 2; + @IntDef(prefix = { "MODE_" }, value = { MODE_OPENING, - MODE_CLOSING + MODE_CLOSING, + MODE_CHANGING }) @Retention(RetentionPolicy.SOURCE) public @interface Mode {} @@ -83,6 +91,13 @@ public class RemoteAnimationTarget implements Parcelable { public final SurfaceControl leash; /** + * The {@link SurfaceControl} for the starting state of a target if this transition is + * MODE_CHANGING, {@code null)} otherwise. This is relative to the app window. + */ + @UnsupportedAppUsage + public final SurfaceControl startLeash; + + /** * Whether the app is translucent and may reveal apps behind. */ @UnsupportedAppUsage @@ -128,6 +143,15 @@ public class RemoteAnimationTarget implements Parcelable { public final Rect sourceContainerBounds; /** + * The starting bounds of the source container in screen space coordinates. This is {@code null} + * if the animation target isn't MODE_CHANGING. Since this is the starting bounds, it's size + * should be equivalent to the size of the starting thumbnail. Note that sourceContainerBounds + * is the end bounds of a change transition. + */ + @UnsupportedAppUsage + public final Rect startBounds; + + /** * The window configuration for the target. */ @UnsupportedAppUsage @@ -141,7 +165,8 @@ public class RemoteAnimationTarget implements Parcelable { public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent, Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position, - Rect sourceContainerBounds, WindowConfiguration windowConfig, boolean isNotInRecents) { + Rect sourceContainerBounds, WindowConfiguration windowConfig, boolean isNotInRecents, + SurfaceControl startLeash, Rect startBounds) { this.mode = mode; this.taskId = taskId; this.leash = leash; @@ -153,6 +178,8 @@ public class RemoteAnimationTarget implements Parcelable { this.sourceContainerBounds = new Rect(sourceContainerBounds); this.windowConfiguration = windowConfig; this.isNotInRecents = isNotInRecents; + this.startLeash = startLeash; + this.startBounds = startBounds == null ? null : new Rect(startBounds); } public RemoteAnimationTarget(Parcel in) { @@ -167,6 +194,8 @@ public class RemoteAnimationTarget implements Parcelable { sourceContainerBounds = in.readParcelable(null); windowConfiguration = in.readParcelable(null); isNotInRecents = in.readBoolean(); + startLeash = in.readParcelable(null); + startBounds = in.readParcelable(null); } @Override @@ -187,6 +216,8 @@ public class RemoteAnimationTarget implements Parcelable { dest.writeParcelable(sourceContainerBounds, 0 /* flags */); dest.writeParcelable(windowConfiguration, 0 /* flags */); dest.writeBoolean(isNotInRecents); + dest.writeParcelable(startLeash, 0 /* flags */); + dest.writeParcelable(startBounds, 0 /* flags */); } public void dump(PrintWriter pw, String prefix) { @@ -215,6 +246,8 @@ public class RemoteAnimationTarget implements Parcelable { position.writeToProto(proto, POSITION); sourceContainerBounds.writeToProto(proto, SOURCE_CONTAINER_BOUNDS); windowConfiguration.writeToProto(proto, WINDOW_CONFIGURATION); + startLeash.writeToProto(proto, START_LEASH); + startBounds.writeToProto(proto, START_BOUNDS); proto.end(token); } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 45c36516b885..6326c591aa04 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -255,6 +255,12 @@ public interface WindowManager extends ViewManager { int TRANSIT_CRASHING_ACTIVITY_CLOSE = 26; /** + * A task is changing windowing modes + * @hide + */ + int TRANSIT_TASK_CHANGE_WINDOWING_MODE = 27; + + /** * @hide */ @IntDef(prefix = { "TRANSIT_" }, value = { @@ -280,7 +286,8 @@ public interface WindowManager extends ViewManager { TRANSIT_KEYGUARD_UNOCCLUDE, TRANSIT_TRANSLUCENT_ACTIVITY_OPEN, TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE, - TRANSIT_CRASHING_ACTIVITY_CLOSE + TRANSIT_CRASHING_ACTIVITY_CLOSE, + TRANSIT_TASK_CHANGE_WINDOWING_MODE }) @Retention(RetentionPolicy.SOURCE) @interface TransitionType {} diff --git a/core/proto/android/view/remote_animation_target.proto b/core/proto/android/view/remote_animation_target.proto index fb4d5bdd1a7f..808c5143fe8e 100644 --- a/core/proto/android/view/remote_animation_target.proto +++ b/core/proto/android/view/remote_animation_target.proto @@ -42,4 +42,6 @@ message RemoteAnimationTargetProto { optional .android.graphics.PointProto position = 8; optional .android.graphics.RectProto source_container_bounds = 9; optional .android.app.WindowConfigurationProto window_configuration = 10; + optional .android.view.SurfaceControlProto start_leash = 11; + optional .android.graphics.RectProto start_bounds = 12; } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 41cab2d7ebd3..0e40a00077b5 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1901,9 +1901,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() { @Override - public int onAppTransitionStartingLocked(int transit, IBinder openToken, - IBinder closeToken, long duration, long statusBarAnimationStartTime, - long statusBarAnimationDuration) { + public int onAppTransitionStartingLocked(int transit, long duration, + long statusBarAnimationStartTime, long statusBarAnimationDuration) { return handleStartTransitionForKeyguardLw(transit, duration); } diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 5f393ef59057..f3a363a30cf8 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -174,6 +174,7 @@ public class AppTransition implements Dump { private int mLastUsedAppTransition = TRANSIT_UNSET; private String mLastOpeningApp; private String mLastClosingApp; + private String mLastChangingApp; private static final int NEXT_TRANSIT_TYPE_NONE = 0; private static final int NEXT_TRANSIT_TYPE_CUSTOM = 1; @@ -318,14 +319,16 @@ public class AppTransition implements Dump { private void setAppTransition(int transit, int flags) { mNextAppTransition = transit; mNextAppTransitionFlags |= flags; - setLastAppTransition(TRANSIT_UNSET, null, null); + setLastAppTransition(TRANSIT_UNSET, null, null, null); updateBooster(); } - void setLastAppTransition(int transit, AppWindowToken openingApp, AppWindowToken closingApp) { + void setLastAppTransition(int transit, AppWindowToken openingApp, AppWindowToken closingApp, + AppWindowToken changingApp) { mLastUsedAppTransition = transit; mLastOpeningApp = "" + openingApp; mLastClosingApp = "" + closingApp; + mLastChangingApp = "" + changingApp; } boolean isReady() { @@ -412,9 +415,7 @@ public class AppTransition implements Dump { * @return bit-map of WindowManagerPolicy#FINISH_LAYOUT_REDO_* to indicate whether another * layout pass needs to be done */ - int goodToGo(int transit, AppWindowToken topOpeningApp, - AppWindowToken topClosingApp, ArraySet<AppWindowToken> openingApps, - ArraySet<AppWindowToken> closingApps) { + int goodToGo(int transit, AppWindowToken topOpeningApp, ArraySet<AppWindowToken> openingApps) { mNextAppTransition = TRANSIT_UNSET; mNextAppTransitionFlags = 0; setAppTransitionState(APP_STATE_RUNNING); @@ -422,8 +423,6 @@ public class AppTransition implements Dump { ? topOpeningApp.getAnimation() : null; int redoLayout = notifyAppTransitionStartingLocked(transit, - topOpeningApp != null ? topOpeningApp.token : null, - topClosingApp != null ? topClosingApp.token : null, topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0, topOpeningAnim != null ? topOpeningAnim.getStatusBarTransitionsStartTime() @@ -500,13 +499,12 @@ public class AppTransition implements Dump { } } - private int notifyAppTransitionStartingLocked(int transit, IBinder openToken, - IBinder closeToken, long duration, long statusBarAnimationStartTime, - long statusBarAnimationDuration) { + private int notifyAppTransitionStartingLocked(int transit, long duration, + long statusBarAnimationStartTime, long statusBarAnimationDuration) { int redoLayout = 0; for (int i = 0; i < mListeners.size(); i++) { - redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(transit, openToken, - closeToken, duration, statusBarAnimationStartTime, statusBarAnimationDuration); + redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(transit, duration, + statusBarAnimationStartTime, statusBarAnimationDuration); } return redoLayout; } @@ -2128,6 +2126,8 @@ public class AppTransition implements Dump { pw.println(mLastOpeningApp); pw.print(prefix); pw.print("mLastClosingApp="); pw.println(mLastClosingApp); + pw.print(prefix); pw.print("mLastChangingApp="); + pw.println(mLastChangingApp); } } @@ -2226,14 +2226,16 @@ public class AppTransition implements Dump { if (dc == null) { return; } - if (isTransitionSet() || !dc.mOpeningApps.isEmpty() || !dc.mClosingApps.isEmpty()) { + if (isTransitionSet() || !dc.mOpeningApps.isEmpty() || !dc.mClosingApps.isEmpty() + || !dc.mChangingApps.isEmpty()) { if (DEBUG_APP_TRANSITIONS) { Slog.v(TAG_WM, "*** APP TRANSITION TIMEOUT." + " displayId=" + dc.getDisplayId() + " isTransitionSet()=" + dc.mAppTransition.isTransitionSet() + " mOpeningApps.size()=" + dc.mOpeningApps.size() - + " mClosingApps.size()=" + dc.mClosingApps.size()); + + " mClosingApps.size()=" + dc.mClosingApps.size() + + " mChangingApps.size()=" + dc.mChangingApps.size()); } setTimeout(); mService.mWindowPlacerLocked.performSurfacePlacement(); diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 775dfa42b8d0..49308b8f92b4 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -89,8 +89,9 @@ public class AppTransitionController { * Handle application transition for given display. */ void handleAppTransitionReady() { - final int appsCount = mDisplayContent.mOpeningApps.size(); - if (!transitionGoodToGo(appsCount, mTempTransitionReasons)) { + mTempTransitionReasons.clear(); + if (!transitionGoodToGo(mDisplayContent.mOpeningApps, mTempTransitionReasons) + || !transitionGoodToGo(mDisplayContent.mChangingApps, mTempTransitionReasons)) { return; } Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady"); @@ -108,21 +109,25 @@ public class AppTransitionController { mDisplayContent.mWallpaperMayChange = false; - int i; - for (i = 0; i < appsCount; i++) { - final AppWindowToken wtoken = mDisplayContent.mOpeningApps.valueAt(i); + int appCount = mDisplayContent.mOpeningApps.size(); + for (int i = 0; i < appCount; ++i) { // Clearing the mAnimatingExit flag before entering animation. It's set to true if app // window is removed, or window relayout to invisible. This also affects window // visibility. We need to clear it *before* maybeUpdateTransitToWallpaper() as the // transition selection depends on wallpaper target visibility. - wtoken.clearAnimatingFlags(); + mDisplayContent.mOpeningApps.valueAtUnchecked(i).clearAnimatingFlags(); + } + appCount = mDisplayContent.mChangingApps.size(); + for (int i = 0; i < appCount; ++i) { + // Clearing for same reason as above. + mDisplayContent.mChangingApps.valueAtUnchecked(i).clearAnimatingFlags(); } // Adjust wallpaper before we pull the lower/upper target, since pending changes // (like the clearAnimatingFlags() above) might affect wallpaper target result. // Or, the opening app window should be a wallpaper target. mWallpaperControllerLocked.adjustWallpaperWindowsForAppTransitionIfNeeded( - mDisplayContent.mOpeningApps); + mDisplayContent.mOpeningApps, mDisplayContent.mChangingApps); // Determine if closing and opening app token sets are wallpaper targets, in which case // special animations are needed. @@ -141,7 +146,7 @@ public class AppTransitionController { // no need to do an animation. This is the case, for example, when this transition is being // done behind a dream window. final ArraySet<Integer> activityTypes = collectActivityTypes(mDisplayContent.mOpeningApps, - mDisplayContent.mClosingApps); + mDisplayContent.mClosingApps, mDisplayContent.mChangingApps); final boolean allowAnimations = mDisplayContent.getDisplayPolicy().allowAppAnimationsLw(); final AppWindowToken animLpToken = allowAnimations ? findAnimLayoutParamsToken(transit, activityTypes) @@ -152,11 +157,15 @@ public class AppTransitionController { final AppWindowToken topClosingApp = allowAnimations ? getTopApp(mDisplayContent.mClosingApps, false /* ignoreHidden */) : null; + final AppWindowToken topChangingApp = allowAnimations + ? getTopApp(mDisplayContent.mChangingApps, false /* ignoreHidden */) + : null; final WindowManager.LayoutParams animLp = getAnimLp(animLpToken); overrideWithRemoteAnimationIfSet(animLpToken, transit, activityTypes); final boolean voiceInteraction = containsVoiceInteraction(mDisplayContent.mOpeningApps) - || containsVoiceInteraction(mDisplayContent.mOpeningApps); + || containsVoiceInteraction(mDisplayContent.mOpeningApps) + || containsVoiceInteraction(mDisplayContent.mChangingApps); final int layoutRedo; mService.mSurfaceAnimationRunner.deferStartingAnimations(); @@ -165,13 +174,14 @@ public class AppTransitionController { handleClosingApps(transit, animLp, voiceInteraction); handleOpeningApps(transit, animLp, voiceInteraction); + handleChangingApps(transit, animLp, voiceInteraction); appTransition.setLastAppTransition(transit, topOpeningApp, - topClosingApp); + topClosingApp, topChangingApp); final int flags = appTransition.getTransitFlags(); layoutRedo = appTransition.goodToGo(transit, topOpeningApp, - topClosingApp, mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps); + mDisplayContent.mOpeningApps); handleNonAppWindowsInTransition(transit, flags); appTransition.postAnimationCallback(); appTransition.clear(); @@ -183,6 +193,7 @@ public class AppTransitionController { mDisplayContent.mOpeningApps.clear(); mDisplayContent.mClosingApps.clear(); + mDisplayContent.mChangingApps.clear(); mDisplayContent.mUnknownAppVisibilityController.clear(); // This has changed the visibility of windows, so perform @@ -237,29 +248,30 @@ public class AppTransitionController { AppWindowToken result; final ArraySet<AppWindowToken> closingApps = mDisplayContent.mClosingApps; final ArraySet<AppWindowToken> openingApps = mDisplayContent.mOpeningApps; + final ArraySet<AppWindowToken> changingApps = mDisplayContent.mChangingApps; // Remote animations always win, but fullscreen tokens override non-fullscreen tokens. - result = lookForHighestTokenWithFilter(closingApps, openingApps, + result = lookForHighestTokenWithFilter(closingApps, openingApps, changingApps, w -> w.getRemoteAnimationDefinition() != null && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes)); if (result != null) { return result; } - result = lookForHighestTokenWithFilter(closingApps, openingApps, + result = lookForHighestTokenWithFilter(closingApps, openingApps, changingApps, w -> w.fillsParent() && w.findMainWindow() != null); if (result != null) { return result; } - return lookForHighestTokenWithFilter(closingApps, openingApps, + return lookForHighestTokenWithFilter(closingApps, openingApps, changingApps, w -> w.findMainWindow() != null); } /** * @return The set of {@link android.app.WindowConfiguration.ActivityType}s contained in the set - * of apps in {@code array1} and {@code array2}. + * of apps in {@code array1}, {@code array2}, and {@code array3}. */ private static ArraySet<Integer> collectActivityTypes(ArraySet<AppWindowToken> array1, - ArraySet<AppWindowToken> array2) { + ArraySet<AppWindowToken> array2, ArraySet<AppWindowToken> array3) { final ArraySet<Integer> result = new ArraySet<>(); for (int i = array1.size() - 1; i >= 0; i--) { result.add(array1.valueAt(i).getActivityType()); @@ -267,19 +279,26 @@ public class AppTransitionController { for (int i = array2.size() - 1; i >= 0; i--) { result.add(array2.valueAt(i).getActivityType()); } + for (int i = array3.size() - 1; i >= 0; i--) { + result.add(array3.valueAt(i).getActivityType()); + } return result; } private static AppWindowToken lookForHighestTokenWithFilter(ArraySet<AppWindowToken> array1, - ArraySet<AppWindowToken> array2, Predicate<AppWindowToken> filter) { - final int array1count = array1.size(); - final int count = array1count + array2.size(); + ArraySet<AppWindowToken> array2, ArraySet<AppWindowToken> array3, + Predicate<AppWindowToken> filter) { + final int array2base = array1.size(); + final int array3base = array2.size() + array2base; + final int count = array3base + array3.size(); int bestPrefixOrderIndex = Integer.MIN_VALUE; AppWindowToken bestToken = null; for (int i = 0; i < count; i++) { - final AppWindowToken wtoken = i < array1count + final AppWindowToken wtoken = i < array2base ? array1.valueAt(i) - : array2.valueAt(i - array1count); + : (i < array3base + ? array2.valueAt(i - array2base) + : array3.valueAt(i - array3base)); final int prefixOrderIndex = wtoken.getPrefixOrderIndex(); if (filter.test(wtoken) && prefixOrderIndex > bestPrefixOrderIndex) { bestPrefixOrderIndex = prefixOrderIndex; @@ -360,6 +379,24 @@ public class AppTransitionController { } } + private void handleChangingApps(int transit, LayoutParams animLp, boolean voiceInteraction) { + final ArraySet<AppWindowToken> apps = mDisplayContent.mChangingApps; + final int appsCount = apps.size(); + for (int i = 0; i < appsCount; i++) { + AppWindowToken wtoken = apps.valueAt(i); + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now changing app" + wtoken); + wtoken.cancelAnimationOnly(); + wtoken.applyAnimationLocked(null, transit, true, false); + wtoken.updateReportedVisibilityLocked(); + mService.openSurfaceTransaction(); + try { + wtoken.showAllWindowsLocked(); + } finally { + mService.closeSurfaceTransaction("handleChangingApps"); + } + } + } + private void handleNonAppWindowsInTransition(int transit, int flags) { if (transit == TRANSIT_KEYGUARD_GOING_AWAY) { if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0 @@ -379,16 +416,15 @@ public class AppTransitionController { } } - private boolean transitionGoodToGo(int appsCount, SparseIntArray outReasons) { + private boolean transitionGoodToGo(ArraySet<AppWindowToken> apps, SparseIntArray outReasons) { if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, - "Checking " + appsCount + " opening apps (frozen=" + "Checking " + apps.size() + " opening apps (frozen=" + mService.mDisplayFrozen + " timeout=" + mDisplayContent.mAppTransition.isTimeout() + ")..."); final ScreenRotationAnimation screenRotationAnimation = mService.mAnimator.getScreenRotationAnimationLocked( Display.DEFAULT_DISPLAY); - outReasons.clear(); if (!mDisplayContent.mAppTransition.isTimeout()) { // Imagine the case where we are changing orientation due to an app transition, but a // previous orientation change is still in progress. We won't process the orientation @@ -404,8 +440,8 @@ public class AppTransitionController { } return false; } - for (int i = 0; i < appsCount; i++) { - AppWindowToken wtoken = mDisplayContent.mOpeningApps.valueAt(i); + for (int i = 0; i < apps.size(); i++) { + AppWindowToken wtoken = apps.valueAt(i); if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Check opening app=" + wtoken + ": allDrawn=" + wtoken.allDrawn + " startingDisplayed=" diff --git a/services/core/java/com/android/server/wm/AppWindowThumbnail.java b/services/core/java/com/android/server/wm/AppWindowThumbnail.java index bb38f3035a6c..29645f68b2fb 100644 --- a/services/core/java/com/android/server/wm/AppWindowThumbnail.java +++ b/services/core/java/com/android/server/wm/AppWindowThumbnail.java @@ -50,9 +50,23 @@ class AppWindowThumbnail implements Animatable { private final SurfaceAnimator mSurfaceAnimator; private final int mWidth; private final int mHeight; + private final boolean mRelative; AppWindowThumbnail(Transaction t, AppWindowToken appToken, GraphicBuffer thumbnailHeader) { + this(t, appToken, thumbnailHeader, false /* relative */); + } + + /** + * @param t Transaction to create the thumbnail in. + * @param appToken {@link AppWindowToken} to associate this thumbnail with. + * @param thumbnailHeader A thumbnail or placeholder for thumbnail to initialize with. + * @param relative Whether this thumbnail will be a child of appToken (and thus positioned + * relative to it) or not. + */ + AppWindowThumbnail(Transaction t, AppWindowToken appToken, GraphicBuffer thumbnailHeader, + boolean relative) { mAppToken = appToken; + mRelative = relative; mSurfaceAnimator = new SurfaceAnimator(this, this::onAnimationFinished, appToken.mWmService); mWidth = thumbnailHeader.getWidth(); @@ -86,6 +100,9 @@ class AppWindowThumbnail implements Animatable { // We parent the thumbnail to the task, and just place it on top of anything else in the // task. t.setLayer(mSurfaceControl, Integer.MAX_VALUE); + if (relative) { + t.reparent(mSurfaceControl, appToken.getSurfaceControl()); + } } void startAnimation(Transaction t, Animation anim) { @@ -101,6 +118,13 @@ class AppWindowThumbnail implements Animatable { mAppToken.mWmService.mSurfaceAnimationRunner), false /* hidden */); } + /** + * Start animation with existing adapter. + */ + void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) { + mSurfaceAnimator.startAnimation(t, anim, hidden); + } + private void onAnimationFinished() { } @@ -147,6 +171,9 @@ class AppWindowThumbnail implements Animatable { @Override public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) { t.setLayer(leash, Integer.MAX_VALUE); + if (mRelative) { + t.reparent(leash, mAppToken.getSurfaceControl()); + } } @Override diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 750c5ca5922e..65b36a092228 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; @@ -33,6 +34,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.TRANSIT_DOCK_TASK_FROM_RECENTS; +import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE; import static android.view.WindowManager.TRANSIT_UNSET; import static android.view.WindowManager.TRANSIT_WALLPAPER_OPEN; @@ -97,6 +99,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; +import android.util.ArraySet; import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; @@ -117,6 +120,7 @@ import com.android.server.LocalServices; import com.android.server.display.ColorDisplayService; import com.android.server.policy.WindowManagerPolicy; import com.android.server.policy.WindowManagerPolicy.StartingSurface; +import com.android.server.wm.RemoteAnimationController.RemoteAnimationRecord; import com.android.server.wm.WindowManagerService.H; import java.io.PrintWriter; @@ -261,7 +265,18 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree /** Whether our surface was set to be showing in the last call to {@link #prepareSurfaces} */ private boolean mLastSurfaceShowing = true; + /** + * This gets used during some open/close transitions as well as during a change transition + * where it represents the starting-state snapshot. + */ private AppWindowThumbnail mThumbnail; + private final Rect mTransitStartRect = new Rect(); + + /** + * This leash is used to "freeze" the app surface in place after the state change, but before + * the animation is ready to start. + */ + private SurfaceControl mTransitChangeLeash = null; /** Have we been asked to have this token keep the screen frozen? */ private boolean mFreezingScreen; @@ -272,6 +287,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree private final Point mTmpPoint = new Point(); private final Rect mTmpRect = new Rect(); + private final Rect mTmpPrevBounds = new Rect(); private RemoteAnimationDefinition mRemoteAnimationDefinition; private AnimatingAppWindowTokenRegistry mAnimatingAppWindowTokenRegistry; @@ -810,6 +826,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree boolean delayed = commitVisibility(null, false, TRANSIT_UNSET, true, mVoiceInteraction); getDisplayContent().mOpeningApps.remove(this); + getDisplayContent().mChangingApps.remove(this); getDisplayContent().mUnknownAppVisibilityController.appRemovedOrHidden(this); mWmService.mTaskSnapshotController.onAppRemoved(this); waitingToShow = false; @@ -1528,6 +1545,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree @Override public void onConfigurationChanged(Configuration newParentConfig) { final int prevWinMode = getWindowingMode(); + mTmpPrevBounds.set(getBounds()); super.onConfigurationChanged(newParentConfig); final int winMode = getWindowingMode(); @@ -1559,9 +1577,68 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree mDisplayContent.mPinnedStackControllerLocked.saveReentrySnapFraction(this, stackBounds); } + } else if (shouldStartChangeTransition(prevWinMode, winMode)) { + initializeChangeTransition(mTmpPrevBounds); + } + } + + private boolean shouldStartChangeTransition(int prevWinMode, int newWinMode) { + if (!isVisible() || getDisplayContent().mAppTransition.isTransitionSet()) { + return false; + } + // Only do an animation into and out-of freeform mode for now. Other mode + // transition animations are currently handled by system-ui. + return (prevWinMode == WINDOWING_MODE_FREEFORM) != (newWinMode == WINDOWING_MODE_FREEFORM); + } + + /** + * Initializes a change transition. Because the app is visible already, there is a small period + * of time where the user can see the app content/window update before the transition starts. + * To prevent this, we immediately take a snapshot and place the app/snapshot into a leash which + * "freezes" the location/crop until the transition starts. + * <p> + * Here's a walk-through of the process: + * 1. Create a temporary leash ("interim-change-leash") and reparent the app to it. + * 2. Set the temporary leash's position/crop to the current state. + * 3. Create a snapshot and place that at the top of the leash to cover up content changes. + * 4. Once the transition is ready, it will reparent the app to the animation leash. + * 5. Detach the interim-change-leash. + */ + private void initializeChangeTransition(Rect startBounds) { + mDisplayContent.prepareAppTransition(TRANSIT_TASK_CHANGE_WINDOWING_MODE, + false /* alwaysKeepCurrent */, 0, false /* forceOverride */); + mDisplayContent.mChangingApps.add(this); + mTransitStartRect.set(startBounds); + + final SurfaceControl.Builder builder = makeAnimationLeash() + .setParent(getAnimationLeashParent()) + .setName(getSurfaceControl() + " - interim-change-leash"); + mTransitChangeLeash = builder.build(); + Transaction t = getPendingTransaction(); + t.setWindowCrop(mTransitChangeLeash, startBounds.width(), startBounds.height()); + t.setPosition(mTransitChangeLeash, startBounds.left, startBounds.top); + t.show(mTransitChangeLeash); + t.reparent(getSurfaceControl(), mTransitChangeLeash); + onAnimationLeashCreated(t, mTransitChangeLeash); + + if (mThumbnail == null && getTask() != null) { + final TaskSnapshotController snapshotCtrl = mWmService.mTaskSnapshotController; + final ArraySet<Task> tasks = new ArraySet<>(); + tasks.add(getTask()); + snapshotCtrl.snapshotTasks(tasks); + snapshotCtrl.addSkipClosingAppSnapshotTasks(tasks); + final ActivityManager.TaskSnapshot snapshot = snapshotCtrl.getSnapshot( + getTask().mTaskId, getTask().mUserId, false /* restoreFromDisk */, + false /* reducedResolution */); + mThumbnail = new AppWindowThumbnail(t, this, snapshot.getSnapshot(), + true /* relative */); } } + boolean isInChangeTransition() { + return mTransitChangeLeash != null || isChangeTransition(mTransit); + } + @Override void checkAppWindowsReadyToShow() { if (allDrawn == mLastAllDrawn) { @@ -2242,6 +2319,15 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree return getBounds(); } + private static boolean isChangeTransition(int transit) { + return transit == TRANSIT_TASK_CHANGE_WINDOWING_MODE; + } + + private int getDefaultChangeTransitionDuration() { + return (int) (AppTransition.DEFAULT_APP_TRANSITION_DURATION + * mWmService.getTransitionAnimationScaleLocked()); + } + boolean applyAnimationLocked(WindowManager.LayoutParams lp, int transit, boolean enter, boolean isVoiceInteraction) { @@ -2260,13 +2346,35 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AWT#applyAnimationLocked"); if (okToAnimate()) { final AnimationAdapter adapter; + AnimationAdapter thumbnailAdapter = null; getAnimationBounds(mTmpPoint, mTmpRect); + boolean isChanging = isChangeTransition(transit) && mThumbnail != null; + // Delaying animation start isn't compatible with remote animations at all. if (getDisplayContent().mAppTransition.getRemoteAnimationController() != null && !mSurfaceAnimator.isAnimationStartDelayed()) { - adapter = getDisplayContent().mAppTransition.getRemoteAnimationController() - .createAnimationAdapter(this, mTmpPoint, mTmpRect); + RemoteAnimationRecord adapters = + getDisplayContent().mAppTransition.getRemoteAnimationController() + .createRemoteAnimationRecord(this, mTmpPoint, mTmpRect, + (isChanging ? mTransitStartRect : null)); + adapter = adapters.mAdapter; + thumbnailAdapter = adapters.mThumbnailAdapter; + } else if (isChanging) { + int duration = getDefaultChangeTransitionDuration(); + mTmpRect.offsetTo(mTmpPoint.x, mTmpPoint.y); + adapter = new LocalAnimationAdapter( + new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect, + getDisplayContent().getDisplayInfo(), duration, + true /* isAppAnimation */, false /* isThumbnail */), + mWmService.mSurfaceAnimationRunner); + thumbnailAdapter = new LocalAnimationAdapter( + new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect, + getDisplayContent().getDisplayInfo(), duration, + true /* isAppAnimation */, true /* isThumbnail */), + mWmService.mSurfaceAnimationRunner); + mTransit = transit; + mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags(); } else { final int appStackClipMode = getDisplayContent().mAppTransition.getAppStackClipMode(); @@ -2294,6 +2402,10 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree if (adapter.getShowWallpaper()) { mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; } + if (thumbnailAdapter != null) { + mThumbnail.startAnimation( + getPendingTransaction(), thumbnailAdapter, !isVisible()); + } } } else { cancelAnimation(); @@ -2429,6 +2541,17 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree final DisplayContent dc = getDisplayContent(); dc.assignStackOrdering(); + + if (leash == mTransitChangeLeash) { + // This is a temporary state so skip any animation notifications + return; + } else if (mTransitChangeLeash != null) { + // unparent mTransitChangeLeash for clean-up + t.hide(mTransitChangeLeash); + t.reparent(mTransitChangeLeash, null); + mTransitChangeLeash = null; + } + if (mAnimatingAppWindowTokenRegistry != null) { mAnimatingAppWindowTokenRegistry.notifyStarting(this); } @@ -2487,6 +2610,12 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree + " okToAnimate=" + okToAnimate() + " startingDisplayed=" + startingDisplayed); + // clean up thumbnail window + if (mThumbnail != null) { + mThumbnail.destroy(); + mThumbnail = null; + } + // WindowState.onExitAnimationDone might modify the children list, so make a copy and then // traverse the copy. final ArrayList<WindowState> children = new ArrayList<>(mChildren); @@ -2518,14 +2647,30 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree @Override void cancelAnimation() { - super.cancelAnimation(); + cancelAnimationOnly(); clearThumbnail(); + if (mTransitChangeLeash != null) { + getPendingTransaction().hide(mTransitChangeLeash); + getPendingTransaction().reparent(mTransitChangeLeash, null); + mTransitChangeLeash = null; + } + } + + /** + * This only cancels the animation. It doesn't do other teardown like cleaning-up thumbnail + * or interim leashes. + * <p> + * Used when canceling in preparation for starting a new animation. + */ + void cancelAnimationOnly() { + super.cancelAnimation(); } boolean isWaitingForTransitionStart() { return getDisplayContent().mAppTransition.isTransitionSet() && (getDisplayContent().mOpeningApps.contains(this) - || getDisplayContent().mClosingApps.contains(this)); + || getDisplayContent().mClosingApps.contains(this) + || getDisplayContent().mChangingApps.contains(this)); } public int getTransit() { @@ -2835,6 +2980,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree void removeFromPendingTransition() { if (isWaitingForTransitionStart() && mDisplayContent != null) { mDisplayContent.mOpeningApps.remove(this); + mDisplayContent.mChangingApps.remove(this); mDisplayContent.mClosingApps.remove(this); } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 8fefd352e027..8f976e74670d 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -250,6 +250,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo final ArraySet<AppWindowToken> mOpeningApps = new ArraySet<>(); final ArraySet<AppWindowToken> mClosingApps = new ArraySet<>(); + final ArraySet<AppWindowToken> mChangingApps = new ArraySet<>(); final UnknownAppVisibilityController mUnknownAppVisibilityController; BoundsAnimationController mBoundsAnimationController; @@ -2454,6 +2455,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // Clear all transitions & screen frozen states when removing display. mOpeningApps.clear(); mClosingApps.clear(); + mChangingApps.clear(); mUnknownAppVisibilityController.clear(); mAppTransition.removeAppTransitionTimeoutCallbacks(); handleAnimatingStoppedAndTransition(); @@ -3284,7 +3286,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } - if (!mOpeningApps.isEmpty() || !mClosingApps.isEmpty()) { + if (!mOpeningApps.isEmpty() || !mClosingApps.isEmpty() || !mChangingApps.isEmpty()) { pw.println(); if (mOpeningApps.size() > 0) { pw.print(" mOpeningApps="); pw.println(mOpeningApps); @@ -3292,6 +3294,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo if (mClosingApps.size() > 0) { pw.print(" mClosingApps="); pw.println(mClosingApps); } + if (mChangingApps.size() > 0) { + pw.print(" mChangingApps="); pw.println(mChangingApps); + } } mUnknownAppVisibilityController.dump(pw, " "); diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index fb82cb0b033b..105ff0674ef0 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -624,7 +624,7 @@ public class RecentsAnimationController implements DeathRecipient { mTarget = new RemoteAnimationTarget(mTask.mTaskId, mode, mCapturedLeash, !topApp.fillsParent(), mainWindow.mWinAnimator.mLastClipRect, insets, mTask.getPrefixOrderIndex(), mPosition, mBounds, - mTask.getWindowConfiguration(), mIsRecentTaskInvisible); + mTask.getWindowConfiguration(), mIsRecentTaskInvisible, null, null); return mTarget; } diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index f5acdccf07f4..f760b39c5332 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -57,7 +57,7 @@ class RemoteAnimationController implements DeathRecipient { private final WindowManagerService mService; private final RemoteAnimationAdapter mRemoteAnimationAdapter; - private final ArrayList<RemoteAnimationAdapterWrapper> mPendingAnimations = new ArrayList<>(); + private final ArrayList<RemoteAnimationRecord> mPendingAnimations = new ArrayList<>(); private final Rect mTmpRect = new Rect(); private final Handler mHandler; private final Runnable mTimeoutRunnable = () -> cancelAnimation("timeoutRunnable"); @@ -74,21 +74,22 @@ class RemoteAnimationController implements DeathRecipient { } /** - * Creates an animation for each individual {@link AppWindowToken}. + * Creates an animation record for each individual {@link AppWindowToken}. * * @param appWindowToken The app to animate. * @param position The position app bounds, in screen coordinates. - * @param stackBounds The stack bounds of the app. - * @return The adapter to be run on the app. + * @param stackBounds The stack bounds of the app relative to position. + * @param startBounds The stack bounds before the transition, in screen coordinates + * @return The record representing animation(s) to run on the app. */ - AnimationAdapter createAnimationAdapter(AppWindowToken appWindowToken, Point position, - Rect stackBounds) { + RemoteAnimationRecord createRemoteAnimationRecord(AppWindowToken appWindowToken, + Point position, Rect stackBounds, Rect startBounds) { if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createAnimationAdapter(): token=" + appWindowToken); - final RemoteAnimationAdapterWrapper adapter = new RemoteAnimationAdapterWrapper( - appWindowToken, position, stackBounds); - mPendingAnimations.add(adapter); - return adapter; + final RemoteAnimationRecord adapters = + new RemoteAnimationRecord(appWindowToken, position, stackBounds, startBounds); + mPendingAnimations.add(adapters); + return adapters; } /** @@ -148,7 +149,7 @@ class RemoteAnimationController implements DeathRecipient { final StringWriter sw = new StringWriter(); final FastPrintWriter pw = new FastPrintWriter(sw); for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { - mPendingAnimations.get(i).dump(pw, ""); + mPendingAnimations.get(i).mAdapter.dump(pw, ""); } pw.close(); Slog.i(TAG, sw.toString()); @@ -158,19 +159,26 @@ class RemoteAnimationController implements DeathRecipient { if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createAnimations()"); final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>(); for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { - final RemoteAnimationAdapterWrapper wrapper = mPendingAnimations.get(i); - final RemoteAnimationTarget target = wrapper.createRemoteAppAnimation(); + final RemoteAnimationRecord wrappers = mPendingAnimations.get(i); + final RemoteAnimationTarget target = wrappers.createRemoteAnimationTarget(); if (target != null) { - if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tAdd token=" + wrapper.mAppWindowToken); + if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tAdd token=" + wrappers.mAppWindowToken); targets.add(target); } else { if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tRemove token=" - + wrapper.mAppWindowToken); + + wrappers.mAppWindowToken); // We can't really start an animation but we still need to make sure to finish the // pending animation that was started by SurfaceAnimator - if (wrapper.mCapturedFinishCallback != null) { - wrapper.mCapturedFinishCallback.onAnimationFinished(wrapper); + if (wrappers.mAdapter != null + && wrappers.mAdapter.mCapturedFinishCallback != null) { + wrappers.mAdapter.mCapturedFinishCallback + .onAnimationFinished(wrappers.mAdapter); + } + if (wrappers.mThumbnailAdapter != null + && wrappers.mThumbnailAdapter.mCapturedFinishCallback != null) { + wrappers.mThumbnailAdapter.mCapturedFinishCallback + .onAnimationFinished(wrappers.mThumbnailAdapter); } mPendingAnimations.remove(i); } @@ -190,9 +198,16 @@ class RemoteAnimationController implements DeathRecipient { if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "onAnimationFinished(): Notify animation finished:"); for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { - final RemoteAnimationAdapterWrapper adapter = mPendingAnimations.get(i); - adapter.mCapturedFinishCallback.onAnimationFinished(adapter); - if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\t" + adapter.mAppWindowToken); + final RemoteAnimationRecord adapters = mPendingAnimations.get(i); + if (adapters.mAdapter != null) { + adapters.mAdapter.mCapturedFinishCallback + .onAnimationFinished(adapters.mAdapter); + } + if (adapters.mThumbnailAdapter != null) { + adapters.mThumbnailAdapter.mCapturedFinishCallback + .onAnimationFinished(adapters.mThumbnailAdapter); + } + if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\t" + adapters.mAppWindowToken); } } catch (Exception e) { Slog.e(TAG, "Failed to finish remote animation", e); @@ -282,47 +297,84 @@ class RemoteAnimationController implements DeathRecipient { } }; - private class RemoteAnimationAdapterWrapper implements AnimationAdapter { - - private final AppWindowToken mAppWindowToken; - private SurfaceControl mCapturedLeash; - private OnAnimationFinishedCallback mCapturedFinishCallback; - private final Point mPosition = new Point(); - private final Rect mStackBounds = new Rect(); - private RemoteAnimationTarget mTarget; - - RemoteAnimationAdapterWrapper(AppWindowToken appWindowToken, Point position, - Rect stackBounds) { + /** + * Contains information about a remote-animation for one AppWindowToken. This keeps track of, + * potentially, multiple animating surfaces (AdapterWrappers) associated with one + * Window/Transition. For example, a change transition has an adapter controller for the + * main window and an adapter controlling the start-state snapshot. + * <p> + * This can be thought of as a bridge between the information that the remote animator sees (via + * {@link RemoteAnimationTarget}) and what the server sees (the + * {@link RemoteAnimationAdapterWrapper}(s) interfacing with the moving surfaces). + */ + public class RemoteAnimationRecord { + RemoteAnimationAdapterWrapper mAdapter; + RemoteAnimationAdapterWrapper mThumbnailAdapter = null; + RemoteAnimationTarget mTarget; + final AppWindowToken mAppWindowToken; + final Rect mStartBounds; + + RemoteAnimationRecord(AppWindowToken appWindowToken, Point endPos, Rect endBounds, + Rect startBounds) { mAppWindowToken = appWindowToken; - mPosition.set(position.x, position.y); - mStackBounds.set(stackBounds); + mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, endBounds); + if (startBounds != null) { + mStartBounds = new Rect(startBounds); + mTmpRect.set(startBounds); + mTmpRect.offsetTo(0, 0); + mThumbnailAdapter = + new RemoteAnimationAdapterWrapper(this, new Point(0, 0), mTmpRect); + } else { + mStartBounds = null; + } } - RemoteAnimationTarget createRemoteAppAnimation() { + RemoteAnimationTarget createRemoteAnimationTarget() { final Task task = mAppWindowToken.getTask(); final WindowState mainWindow = mAppWindowToken.findMainWindow(); - if (task == null || mainWindow == null || mCapturedFinishCallback == null - || mCapturedLeash == null) { + if (task == null || mainWindow == null || mAdapter == null + || mAdapter.mCapturedFinishCallback == null + || mAdapter.mCapturedLeash == null) { return null; } final Rect insets = new Rect(); mainWindow.getContentInsets(insets); InsetUtils.addInsets(insets, mAppWindowToken.getLetterboxInsets()); mTarget = new RemoteAnimationTarget(task.mTaskId, getMode(), - mCapturedLeash, !mAppWindowToken.fillsParent(), + mAdapter.mCapturedLeash, !mAppWindowToken.fillsParent(), mainWindow.mWinAnimator.mLastClipRect, insets, - mAppWindowToken.getPrefixOrderIndex(), mPosition, mStackBounds, - task.getWindowConfiguration(), false /*isNotInRecents*/); + mAppWindowToken.getPrefixOrderIndex(), mAdapter.mPosition, + mAdapter.mStackBounds, task.getWindowConfiguration(), false /*isNotInRecents*/, + mThumbnailAdapter != null ? mThumbnailAdapter.mCapturedLeash : null, + mStartBounds); return mTarget; } private int getMode() { - if (mAppWindowToken.getDisplayContent().mOpeningApps.contains(mAppWindowToken)) { + final DisplayContent dc = mAppWindowToken.getDisplayContent(); + if (dc.mOpeningApps.contains(mAppWindowToken)) { return RemoteAnimationTarget.MODE_OPENING; + } else if (dc.mChangingApps.contains(mAppWindowToken)) { + return RemoteAnimationTarget.MODE_CHANGING; } else { return RemoteAnimationTarget.MODE_CLOSING; } } + } + + private class RemoteAnimationAdapterWrapper implements AnimationAdapter { + private final RemoteAnimationRecord mRecord; + SurfaceControl mCapturedLeash; + private OnAnimationFinishedCallback mCapturedFinishCallback; + private final Point mPosition = new Point(); + private final Rect mStackBounds = new Rect(); + + RemoteAnimationAdapterWrapper(RemoteAnimationRecord record, Point position, + Rect stackBounds) { + mRecord = record; + mPosition.set(position.x, position.y); + mStackBounds.set(stackBounds); + } @Override public boolean getShowWallpaper() { @@ -340,7 +392,7 @@ class RemoteAnimationController implements DeathRecipient { if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "startAnimation"); // Restore z-layering, position and stack crop until client has a chance to modify it. - t.setLayer(animationLeash, mAppWindowToken.getPrefixOrderIndex()); + t.setLayer(animationLeash, mRecord.mAppWindowToken.getPrefixOrderIndex()); t.setPosition(animationLeash, mPosition.x, mPosition.y); mTmpRect.set(mStackBounds); mTmpRect.offsetTo(0, 0); @@ -351,7 +403,14 @@ class RemoteAnimationController implements DeathRecipient { @Override public void onAnimationCancelled(SurfaceControl animationLeash) { - mPendingAnimations.remove(this); + if (mRecord.mAdapter == this) { + mRecord.mAdapter = null; + } else { + mRecord.mThumbnailAdapter = null; + } + if (mRecord.mAdapter == null && mRecord.mThumbnailAdapter == null) { + mPendingAnimations.remove(mRecord); + } if (mPendingAnimations.isEmpty()) { mHandler.removeCallbacks(mTimeoutRunnable); releaseFinishedCallback(); @@ -373,10 +432,10 @@ class RemoteAnimationController implements DeathRecipient { @Override public void dump(PrintWriter pw, String prefix) { - pw.print(prefix); pw.print("token="); pw.println(mAppWindowToken); - if (mTarget != null) { + pw.print(prefix); pw.print("token="); pw.println(mRecord.mAppWindowToken); + if (mRecord.mTarget != null) { pw.print(prefix); pw.println("Target:"); - mTarget.dump(pw, prefix + " "); + mRecord.mTarget.dump(pw, prefix + " "); } else { pw.print(prefix); pw.println("Target: null"); } @@ -385,8 +444,8 @@ class RemoteAnimationController implements DeathRecipient { @Override public void writeToProto(ProtoOutputStream proto) { final long token = proto.start(REMOTE); - if (mTarget != null) { - mTarget.writeToProto(proto, TARGET); + if (mRecord.mTarget != null) { + mRecord.mTarget.writeToProto(proto, TARGET); } proto.end(token); } diff --git a/services/core/java/com/android/server/wm/StatusBarController.java b/services/core/java/com/android/server/wm/StatusBarController.java index b4de75b287fa..6db606d2a30b 100644 --- a/services/core/java/com/android/server/wm/StatusBarController.java +++ b/services/core/java/com/android/server/wm/StatusBarController.java @@ -61,9 +61,8 @@ public class StatusBarController extends BarController { } @Override - public int onAppTransitionStartingLocked(int transit, IBinder openToken, - IBinder closeToken, long duration, long statusBarAnimationStartTime, - long statusBarAnimationDuration) { + public int onAppTransitionStartingLocked(int transit, long duration, + long statusBarAnimationStartTime, long statusBarAnimationDuration) { mHandler.post(() -> { StatusBarManagerInternal statusBar = getStatusBarInternal(); if (statusBar != null && mWin != null) { diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 15239c7e57e1..c15afc547085 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -661,7 +661,8 @@ class WallpaperController { * Adjusts the wallpaper windows if the input display has a pending wallpaper layout or one of * the opening apps should be a wallpaper target. */ - void adjustWallpaperWindowsForAppTransitionIfNeeded(ArraySet<AppWindowToken> openingApps) { + void adjustWallpaperWindowsForAppTransitionIfNeeded(ArraySet<AppWindowToken> openingApps, + ArraySet<AppWindowToken> changingApps) { boolean adjust = false; if ((mDisplayContent.pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) { adjust = true; @@ -673,6 +674,15 @@ class WallpaperController { break; } } + if (!adjust) { + for (int i = changingApps.size() - 1; i >= 0; --i) { + final AppWindowToken token = changingApps.valueAt(i); + if (token.windowsCanBeWallpaperTarget()) { + adjust = true; + break; + } + } + } } if (adjust) { diff --git a/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java new file mode 100644 index 000000000000..7dd7c4f5c958 --- /dev/null +++ b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java @@ -0,0 +1,203 @@ +/* + * 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.server.wm; + +import static com.android.server.wm.AnimationAdapter.STATUS_BAR_TRANSITION_DURATION; +import static com.android.server.wm.AnimationSpecProto.WINDOW; +import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION; + +import android.graphics.Matrix; +import android.graphics.Rect; +import android.os.SystemClock; +import android.util.proto.ProtoOutputStream; +import android.view.DisplayInfo; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.AnimationSet; +import android.view.animation.ClipRectAnimation; +import android.view.animation.ScaleAnimation; +import android.view.animation.Transformation; +import android.view.animation.TranslateAnimation; + +import com.android.server.wm.LocalAnimationAdapter.AnimationSpec; + +import java.io.PrintWriter; + +/** + * Animation spec for changing window animations. + */ +public class WindowChangeAnimationSpec implements AnimationSpec { + + private final ThreadLocal<TmpValues> mThreadLocalTmps = ThreadLocal.withInitial(TmpValues::new); + private final boolean mIsAppAnimation; + private final Rect mStartBounds; + private final Rect mEndBounds; + private final Rect mTmpRect = new Rect(); + + private Animation mAnimation; + private final boolean mIsThumbnail; + + public WindowChangeAnimationSpec(Rect startBounds, Rect endBounds, DisplayInfo displayInfo, + long duration, boolean isAppAnimation, boolean isThumbnail) { + mStartBounds = new Rect(startBounds); + mEndBounds = new Rect(endBounds); + mIsAppAnimation = isAppAnimation; + mIsThumbnail = isThumbnail; + createBoundsInterpolator(duration, displayInfo); + } + + @Override + public boolean getShowWallpaper() { + return false; + } + + @Override + public int getBackgroundColor() { + return 0; + } + + @Override + public long getDuration() { + return mAnimation.getDuration(); + } + + /** + * This animator behaves slightly differently depending on whether the window is growing + * or shrinking: + * If growing, it will do a clip-reveal after quicker fade-out/scale of the smaller (old) + * snapshot. + * If shrinking, it will do an opposite clip-reveal on the old snapshot followed by a quicker + * fade-out of the bigger (old) snapshot while simultaneously shrinking the new window into + * place. + * @param duration + * @param displayInfo + */ + private void createBoundsInterpolator(long duration, DisplayInfo displayInfo) { + boolean growing = mEndBounds.width() - mStartBounds.width() + + mEndBounds.height() - mStartBounds.height() >= 0; + float scalePart = 0.7f; + long scalePeriod = (long) (duration * scalePart); + float startScaleX = scalePart * ((float) mStartBounds.width()) / mEndBounds.width() + + (1.f - scalePart); + float startScaleY = scalePart * ((float) mStartBounds.height()) / mEndBounds.height() + + (1.f - scalePart); + if (mIsThumbnail) { + AnimationSet animSet = new AnimationSet(true); + Animation anim = new AlphaAnimation(1.f, 0.f); + anim.setDuration(scalePeriod); + if (!growing) { + anim.setStartOffset(duration - scalePeriod); + } + animSet.addAnimation(anim); + float endScaleX = 1.f / startScaleX; + float endScaleY = 1.f / startScaleY; + anim = new ScaleAnimation(endScaleX, endScaleX, endScaleY, endScaleY); + anim.setDuration(duration); + animSet.addAnimation(anim); + mAnimation = animSet; + mAnimation.initialize(mStartBounds.width(), mStartBounds.height(), + mEndBounds.width(), mEndBounds.height()); + } else { + AnimationSet animSet = new AnimationSet(true); + final Animation scaleAnim = new ScaleAnimation(startScaleX, 1, startScaleY, 1); + scaleAnim.setDuration(scalePeriod); + if (!growing) { + scaleAnim.setStartOffset(duration - scalePeriod); + } + animSet.addAnimation(scaleAnim); + final Animation translateAnim = new TranslateAnimation(mStartBounds.left, + mEndBounds.left, mStartBounds.top, mEndBounds.top); + translateAnim.setDuration(duration); + animSet.addAnimation(translateAnim); + Rect startClip = new Rect(mStartBounds); + Rect endClip = new Rect(mEndBounds); + startClip.offsetTo(0, 0); + endClip.offsetTo(0, 0); + final Animation clipAnim = new ClipRectAnimation(startClip, endClip); + clipAnim.setDuration(duration); + animSet.addAnimation(clipAnim); + mAnimation = animSet; + mAnimation.initialize(mStartBounds.width(), mStartBounds.height(), + displayInfo.appWidth, displayInfo.appHeight); + } + } + + @Override + public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) { + final TmpValues tmp = mThreadLocalTmps.get(); + if (mIsThumbnail) { + mAnimation.getTransformation(currentPlayTime, tmp.mTransformation); + t.setMatrix(leash, tmp.mTransformation.getMatrix(), tmp.mFloats); + t.setAlpha(leash, tmp.mTransformation.getAlpha()); + } else { + mAnimation.getTransformation(currentPlayTime, tmp.mTransformation); + final Matrix matrix = tmp.mTransformation.getMatrix(); + t.setMatrix(leash, matrix, tmp.mFloats); + + // The following applies an inverse scale to the clip-rect so that it crops "after" the + // scale instead of before. + tmp.mVecs[1] = tmp.mVecs[2] = 0; + tmp.mVecs[0] = tmp.mVecs[3] = 1; + matrix.mapVectors(tmp.mVecs); + tmp.mVecs[0] = 1.f / tmp.mVecs[0]; + tmp.mVecs[3] = 1.f / tmp.mVecs[3]; + final Rect clipRect = tmp.mTransformation.getClipRect(); + mTmpRect.left = (int) (clipRect.left * tmp.mVecs[0] + 0.5f); + mTmpRect.right = (int) (clipRect.right * tmp.mVecs[0] + 0.5f); + mTmpRect.top = (int) (clipRect.top * tmp.mVecs[3] + 0.5f); + mTmpRect.bottom = (int) (clipRect.bottom * tmp.mVecs[3] + 0.5f); + t.setWindowCrop(leash, mTmpRect); + } + } + + @Override + public long calculateStatusBarTransitionStartTime() { + long uptime = SystemClock.uptimeMillis(); + return Math.max(uptime, uptime + ((long) (((float) mAnimation.getDuration()) * 0.99f)) + - STATUS_BAR_TRANSITION_DURATION); + } + + @Override + public boolean canSkipFirstFrame() { + return false; + } + + @Override + public boolean needsEarlyWakeup() { + return mIsAppAnimation; + } + + @Override + public void dump(PrintWriter pw, String prefix) { + pw.print(prefix); pw.println(mAnimation.getDuration()); + } + + @Override + public void writeToProtoInner(ProtoOutputStream proto) { + final long token = proto.start(WINDOW); + proto.write(ANIMATION, mAnimation.toString()); + proto.end(token); + } + + private static class TmpValues { + final Transformation mTransformation = new Transformation(); + final float[] mFloats = new float[9]; + final float[] mVecs = new float[4]; + } +} diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 5267e7e55793..e204697e46cf 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -118,8 +118,6 @@ public abstract class WindowManagerInternal { * * @param transit transition type indicating what kind of transition gets run, must be one * of AppTransition.TRANSIT_* values - * @param openToken the token for the opening app - * @param closeToken the token for the closing app * @param duration the total duration of the transition * @param statusBarAnimationStartTime the desired start time for all visual animations in * the status bar caused by this app transition in uptime millis @@ -131,8 +129,8 @@ public abstract class WindowManagerInternal { * {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_WALLPAPER}, * or {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_ANIM}. */ - public int onAppTransitionStartingLocked(int transit, IBinder openToken, IBinder closeToken, - long duration, long statusBarAnimationStartTime, long statusBarAnimationDuration) { + public int onAppTransitionStartingLocked(int transit, long duration, + long statusBarAnimationStartTime, long statusBarAnimationDuration) { return 0; } diff --git a/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java index 1c5391ed3a6c..9ce579512eda 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java @@ -106,7 +106,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { } public void notifyTransitionStarting(int transit) { - mListener.onAppTransitionStartingLocked(transit, null, null, 0, 0, 0); + mListener.onAppTransitionStartingLocked(transit, 0, 0, 0); } public void notifyTransitionFinished() { diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java index 413b6f4b3905..9478be90b5c3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -25,6 +25,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMor import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.eq; import android.graphics.Point; @@ -43,6 +44,7 @@ import androidx.test.filters.SmallTest; import com.android.server.testutils.OffsettableClock; import com.android.server.testutils.TestHandler; +import com.android.server.wm.RemoteAnimationController.RemoteAnimationRecord; import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import org.junit.Before; @@ -60,8 +62,10 @@ import org.mockito.MockitoAnnotations; public class RemoteAnimationControllerTest extends WindowTestsBase { @Mock SurfaceControl mMockLeash; + @Mock SurfaceControl mMockThumbnailLeash; @Mock Transaction mMockTransaction; @Mock OnAnimationFinishedCallback mFinishedCallback; + @Mock OnAnimationFinishedCallback mThumbnailFinishedCallback; @Mock IRemoteAnimationRunner mMockRunner; private RemoteAnimationAdapter mAdapter; private RemoteAnimationController mController; @@ -84,8 +88,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); mDisplayContent.mOpeningApps.add(win.mAppToken); try { - final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken, - new Point(50, 100), new Rect(50, 100, 150, 150)); + final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); mController.goodToGo(); mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); @@ -117,8 +121,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { @Test public void testCancel() throws Exception { final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); - final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken, - new Point(50, 100), new Rect(50, 100, 150, 150)); + final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); mController.goodToGo(); @@ -129,8 +133,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { @Test public void testTimeout() throws Exception { final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); - final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken, - new Point(50, 100), new Rect(50, 100, 150, 150)); + final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); mController.goodToGo(); @@ -147,8 +151,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { mWm.setAnimationScale(2, 5.0f); final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); - final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken, - new Point(50, 100), new Rect(50, 100, 150, 150)); + final AnimationAdapter adapter = mController.createRemoteAnimationRecord( + win.mAppToken, new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); mController.goodToGo(); @@ -176,8 +180,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { @Test public void testNotReallyStarted() { final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); - mController.createAnimationAdapter(win.mAppToken, - new Point(50, 100), new Rect(50, 100, 150, 150)); + mController.createRemoteAnimationRecord(win.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150), null); mController.goodToGo(); verifyNoMoreInteractionsExceptAsBinder(mMockRunner); } @@ -186,10 +190,10 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { public void testOneNotStarted() throws Exception { final WindowState win1 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin1"); final WindowState win2 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin2"); - mController.createAnimationAdapter(win1.mAppToken, - new Point(50, 100), new Rect(50, 100, 150, 150)); - final AnimationAdapter adapter = mController.createAnimationAdapter(win2.mAppToken, - new Point(50, 100), new Rect(50, 100, 150, 150)); + mController.createRemoteAnimationRecord(win1.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150), null); + final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win2.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); mController.goodToGo(); mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); @@ -205,8 +209,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { @Test public void testRemovedBeforeStarted() { final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); - final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken, - new Point(50, 100), new Rect(50, 100, 150, 150)); + final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); win.mAppToken.removeImmediately(); mController.goodToGo(); @@ -214,6 +218,49 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { verify(mFinishedCallback).onAnimationFinished(eq(adapter)); } + @Test + public void testChange() throws Exception { + final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); + mDisplayContent.mChangingApps.add(win.mAppToken); + try { + final RemoteAnimationRecord record = mController.createRemoteAnimationRecord( + win.mAppToken, new Point(50, 100), new Rect(50, 100, 150, 150), + new Rect(0, 0, 200, 200)); + assertNotNull(record.mThumbnailAdapter); + ((AnimationAdapter) record.mAdapter) + .startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + ((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash, + mMockTransaction, mThumbnailFinishedCallback); + mController.goodToGo(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor = + ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class); + verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture()); + assertEquals(1, appsCaptor.getValue().length); + final RemoteAnimationTarget app = appsCaptor.getValue()[0]; + assertEquals(RemoteAnimationTarget.MODE_CHANGING, app.mode); + assertEquals(new Point(50, 100), app.position); + assertEquals(new Rect(50, 100, 150, 150), app.sourceContainerBounds); + assertEquals(new Rect(0, 0, 200, 200), app.startBounds); + assertEquals(mMockLeash, app.leash); + assertEquals(mMockThumbnailLeash, app.startLeash); + assertEquals(win.mWinAnimator.mLastClipRect, app.clipRect); + assertEquals(false, app.isTranslucent); + verify(mMockTransaction).setLayer(mMockLeash, app.prefixOrderIndex); + verify(mMockTransaction).setPosition(mMockLeash, app.position.x, app.position.y); + verify(mMockTransaction).setWindowCrop(mMockLeash, new Rect(0, 0, 200, 200)); + verify(mMockTransaction).setPosition(mMockThumbnailLeash, 0, 0); + + finishedCaptor.getValue().onAnimationFinished(); + verify(mFinishedCallback).onAnimationFinished(eq(record.mAdapter)); + verify(mThumbnailFinishedCallback).onAnimationFinished(eq(record.mThumbnailAdapter)); + } finally { + mDisplayContent.mChangingApps.clear(); + } + } + private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) { verify(binder, atLeast(0)).asBinder(); verifyNoMoreInteractions(binder); |