From 55bddd83ea1be57b823413382775667f2c5c916a Mon Sep 17 00:00:00 2001 From: Evan Rosky Date: Wed, 29 Jan 2020 13:07:18 -0800 Subject: [PATCH] Generalize change transition into WindowContainer Created a SurfaceFreezer class which lives in WindowContainer and manages/will-manage per-container freezing (snapshots). This replaces the one-off change transition code. Change Transitions used to create its own temporary leash on initialization and that leash would be replaced/cleaned-up as soon as the animation leash was created. Now, the SurfaceFreezer creates the animation leash immediately and SurfaceAnimator can take a SurfaceFreezer instance when it starts an animation. At this point it will take the leash that was already created by the freezer and use that. This removes the messy reparenting/cleanup. To deal with this, though, leash callbacks into Animatable needed an extra stage: onLeashAnimationStarting. This is called when SurfaceAnimatior is actually starting to animate the leash. Next, DC.mChangingApps was converted to list of WindowContainers rather than ActivityRecords. Some of the existing change code was cleaned up (ie. there was some visibility stuff that doesn't make sense because changing apps are visible->visible) and some of the Activity-specific functions were generalized. For now, there are a couple things that use the top-activity for changing Tasks. The result of this means that windowing-mode change transition can now fully live at the Task level. This also should allow freezing at any hierarchy level which enables app-freezes that don't freeze the whole screen and potentially mixed seamless/snapshot-based rotations. Bug: 149490428 Test: existing tests pass. Manually open app in freeform and maximize/restore it. Change-Id: Ib32524ebbbb084a98442d3d035897306a11ee6c2 (cherry picked from commit e55b9e0f9d0377af313591d9e0c260d41c7fe3d1) --- data/etc/services.core.protolog.json | 6 + .../server/wm/ActivityMetricsLogger.java | 17 +- .../com/android/server/wm/ActivityRecord.java | 168 ++---------- .../com/android/server/wm/AppTransition.java | 4 +- .../server/wm/AppTransitionController.java | 94 ++++--- .../com/android/server/wm/DisplayContent.java | 14 +- .../server/wm/RecentsAnimationController.java | 2 +- .../server/wm/RemoteAnimationController.java | 2 +- .../android/server/wm/SurfaceAnimator.java | 81 ++++-- .../com/android/server/wm/SurfaceFreezer.java | 245 ++++++++++++++++++ .../core/java/com/android/server/wm/Task.java | 91 ++++++- .../server/wm/WallpaperController.java | 12 +- .../android/server/wm/WindowContainer.java | 70 ++++- .../ActivityMetricsLaunchObserverTests.java | 2 +- .../server/wm/AppChangeTransitionTests.java | 26 +- .../wm/RemoteAnimationControllerTest.java | 4 +- 16 files changed, 550 insertions(+), 288 deletions(-) create mode 100644 services/core/java/com/android/server/wm/SurfaceFreezer.java diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 145fd8b824cf7..99605ad64cb89 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -697,6 +697,12 @@ "group": "WM_DEBUG_FOCUS_LIGHT", "at": "com\/android\/server\/wm\/RootWindowContainer.java" }, + "-668956537": { + "message": " THUMBNAIL %s: CREATE", + "level": "INFO", + "group": "WM_SHOW_TRANSACTIONS", + "at": "com\/android\/server\/wm\/SurfaceFreezer.java" + }, "-666510420": { "message": "With display frozen, orientationChangeComplete=%b", "level": "VERBOSE", diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index 68a71881b898d..4fea36c040f92 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -275,8 +275,9 @@ class ActivityMetricsLogger { } /** @return {@code true} if the activity matches a launched activity in this transition. */ - boolean contains(ActivityRecord r) { - return r == mLastLaunchedActivity || mPendingDrawActivities.contains(r); + boolean contains(WindowContainer wc) { + final ActivityRecord r = AppTransitionController.getAppFromContainer(wc); + return r != null && (r == mLastLaunchedActivity || mPendingDrawActivities.contains(r)); } /** Called when the activity is drawn or won't be drawn. */ @@ -435,10 +436,10 @@ class ActivityMetricsLogger { /** @return Non-null {@link TransitionInfo} if the activity is found in an active transition. */ @Nullable - private TransitionInfo getActiveTransitionInfo(ActivityRecord r) { + private TransitionInfo getActiveTransitionInfo(WindowContainer wc) { for (int i = mTransitionInfoList.size() - 1; i >= 0; i--) { final TransitionInfo info = mTransitionInfoList.get(i); - if (info.contains(r)) { + if (info.contains(wc)) { return info; } } @@ -623,19 +624,19 @@ class ActivityMetricsLogger { * @param activityToReason A map from activity to a reason integer, which must be on of * ActivityTaskManagerInternal.APP_TRANSITION_* reasons. */ - void notifyTransitionStarting(ArrayMap activityToReason) { + void notifyTransitionStarting(ArrayMap activityToReason) { if (DEBUG_METRICS) Slog.i(TAG, "notifyTransitionStarting"); final long timestampNs = SystemClock.elapsedRealtimeNanos(); for (int index = activityToReason.size() - 1; index >= 0; index--) { - final ActivityRecord r = activityToReason.keyAt(index); - final TransitionInfo info = getActiveTransitionInfo(r); + final WindowContainer wc = activityToReason.keyAt(index); + final TransitionInfo info = getActiveTransitionInfo(wc); if (info == null || info.mLoggedTransitionStarting) { // Ignore any subsequent notifyTransitionStarting. continue; } if (DEBUG_METRICS) { - Slog.i(TAG, "notifyTransitionStarting activity=" + r + " info=" + info); + Slog.i(TAG, "notifyTransitionStarting activity=" + wc + " info=" + info); } info.mCurrentTransitionDelayMs = info.calculateDelay(timestampNs); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index d715ed408d51f..e5b8403d2d8d0 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -40,7 +40,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; -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.activityTypeToString; @@ -106,7 +105,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.TRANSIT_ACTIVITY_CLOSE; 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_TASK_CLOSE; import static android.view.WindowManager.TRANSIT_TASK_OPEN_BEHIND; import static android.view.WindowManager.TRANSIT_UNSET; @@ -284,7 +282,6 @@ import android.view.DisplayInfo; import android.view.IAppTransitionAnimationSpecsFuture; import android.view.IApplicationToken; import android.view.InputApplicationHandle; -import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationDefinition; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; @@ -577,12 +574,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A */ private boolean mCurrentLaunchCanTurnScreenOn = true; - /** - * 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; - /** Whether our surface was set to be showing in the last call to {@link #prepareSurfaces} */ private boolean mLastSurfaceShowing = true; @@ -1329,15 +1320,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mDisplayContent.executeAppTransition(); } - if (prevDc.mChangingApps.remove(this)) { - // This gets called *after* the ActivityRecord has been reparented to the new display. - // That reparenting resulted in this window changing modes (eg. FREEFORM -> FULLSCREEN), - // so this token is now "frozen" while waiting for the animation to start on prevDc - // (which will be cancelled since the window is no-longer a child). However, since this - // is no longer a child of prevDc, this won't be notified of the cancelled animation, - // so we need to cancel the change transition here. - clearChangeLeash(getPendingTransaction(), true /* cancel */); - } prevDc.mClosingApps.remove(this); if (prevDc.mFocusedApp == this) { @@ -3092,7 +3074,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A commitVisibility(false /* visible */, true /* performLayout */); getDisplayContent().mOpeningApps.remove(this); - getDisplayContent().mChangingApps.remove(this); + getDisplayContent().mChangingContainers.remove(this); getDisplayContent().mUnknownAppVisibilityController.appRemovedOrHidden(this); mWmService.mTaskSnapshotController.onAppRemoved(this); mStackSupervisor.getActivityMetricsLogger().notifyActivityRemoved(this); @@ -3995,13 +3977,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A appToken, visible, appTransition, isVisible(), mVisibleRequested, Debug.getCallers(6)); + onChildVisibilityRequested(visible); + final DisplayContent displayContent = getDisplayContent(); displayContent.mOpeningApps.remove(this); displayContent.mClosingApps.remove(this); - if (isInChangeTransition()) { - clearChangeLeash(getPendingTransaction(), true /* cancel */); - } - displayContent.mChangingApps.remove(this); waitingToShow = false; mVisibleRequested = visible; mLastDeferHidingClient = deferHidingClient; @@ -5805,11 +5785,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return !isSplitScreenPrimary || allowSplitScreenPrimaryAnimation; } - @Override - boolean isChangingAppTransition() { - return task != null ? task.isChangingAppTransition() : super.isChangingAppTransition(); - } - /** * Creates a layer to apply crop to an animation. */ @@ -5830,84 +5805,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A this, endDeferFinishCallback); } - private boolean shouldStartChangeTransition(int prevWinMode, int newWinMode) { - if (mWmService.mDisableTransitionAnimation - || !isVisible() - || getDisplayContent().mAppTransition.isTransitionSet() - || getSurfaceControl() == null) { - 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); - } - @Override boolean isWaitingForTransitionStart() { final DisplayContent dc = getDisplayContent(); return dc != null && dc.mAppTransition.isTransitionSet() && (dc.mOpeningApps.contains(this) || dc.mClosingApps.contains(this) - || dc.mChangingApps.contains(this)); + || dc.mChangingContainers.contains(this)); } - /** - * 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. - *

- * 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); - - // Skip creating snapshot if this transition is controlled by a remote animator which - // doesn't need it. - ArraySet activityTypes = new ArraySet<>(); - activityTypes.add(getActivityType()); - RemoteAnimationAdapter adapter = - mDisplayContent.mAppTransitionController.getRemoteAnimationOverride( - this, TRANSIT_TASK_CHANGE_WINDOWING_MODE, activityTypes); - if (adapter != null && !adapter.getChangeNeedsSnapshot()) { - return; - } - - if (mThumbnail == null && task != null && !hasCommittedReparentToAnimationLeash()) { - SurfaceControl.ScreenshotGraphicBuffer snapshot = - mWmService.mTaskSnapshotController.createTaskSnapshot( - task, 1 /* scaleFraction */); - if (snapshot != null) { - mThumbnail = new WindowContainerThumbnail(mWmService.mSurfaceFactory, t, this, - snapshot.getGraphicBuffer(), true /* relative */); - } - } - } - - @Override - public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) { + private int getAnimationLayer() { // The leash is parented to the animation layer. We need to preserve the z-order by using // the prefix order index, but we boost if necessary. - int layer = 0; + int layer; if (!inPinnedWindowingMode()) { layer = getPrefixOrderIndex(); } else { @@ -5920,21 +5830,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mNeedsZBoost) { layer += Z_BOOST_BASE; } - if (!mNeedsAnimationBoundsLayer) { - t.setLayer(leash, layer); - } + return layer; + } - 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 - clearChangeLeash(t, false /* cancel */); - } + @Override + public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) { + t.setLayer(leash, getAnimationLayer()); + getDisplayContent().assignStackOrdering(); + } + @Override + public void onLeashAnimationStarting(Transaction t, SurfaceControl leash) { if (mAnimatingActivityRegistry != null) { mAnimatingActivityRegistry.notifyStarting(this); } @@ -5962,7 +5868,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // surface size has already same as the animating container. t.setWindowCrop(mAnimationBoundsLayer, mTmpRect); } - t.setLayer(mAnimationBoundsLayer, layer); + t.setLayer(leash, 0); + t.setLayer(mAnimationBoundsLayer, getAnimationLayer()); // Reparent leash to animation bounds layer. t.reparent(leash, mAnimationBoundsLayer); @@ -5994,10 +5901,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return mLastSurfaceShowing; } - boolean isInChangeTransition() { - return mTransitChangeLeash != null || AppTransition.isChangeTransit(mTransit); - } - void attachThumbnailAnimation() { if (!isAnimating(PARENTS)) { return; @@ -6134,31 +6037,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } - /** - * @param cancel {@code true} if clearing the leash due to cancelling instead of transferring - * to another leash. - */ - private void clearChangeLeash(Transaction t, boolean cancel) { - if (mTransitChangeLeash == null) { - return; - } - if (cancel) { - clearThumbnail(); - SurfaceControl sc = getSurfaceControl(); - SurfaceControl parentSc = getParentSurfaceControl(); - // Don't reparent if surface is getting destroyed - if (parentSc != null && sc != null) { - t.reparent(sc, getParentSurfaceControl()); - } - } - t.hide(mTransitChangeLeash); - t.remove(mTransitChangeLeash); - mTransitChangeLeash = null; - if (cancel) { - onAnimationLeashLost(t); - } - } - void clearAnimatingFlags() { boolean wallpaperMightChange = false; for (int i = mChildren.size() - 1; i >= 0; i--) { @@ -6174,7 +6052,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A void cancelAnimation() { cancelAnimationOnly(); clearThumbnail(); - clearChangeLeash(getPendingTransaction(), true /* cancel */); + mSurfaceFreezer.unfreeze(getPendingTransaction()); } /** @@ -6219,6 +6097,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mRemoteAnimationDefinition = null; } + @Override RemoteAnimationDefinition getRemoteAnimationDefinition() { return mRemoteAnimationDefinition; } @@ -6679,8 +6558,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } } - final int prevWinMode = getWindowingMode(); - mTmpPrevBounds.set(getBounds()); super.onConfigurationChanged(newParentConfig); if (shouldUseSizeCompatMode()) { @@ -6705,12 +6582,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } - final int newWinMode = getWindowingMode(); - if ((prevWinMode != newWinMode) && (mDisplayContent != null) - && shouldStartChangeTransition(prevWinMode, newWinMode)) { - initializeChangeTransition(mTmpPrevBounds); - } - // Configuration's equality doesn't consider seq so if only seq number changes in resolved // override configuration. Therefore ConfigurationContainer doesn't change merged override // configuration, but it's used to push configuration changes so explicitly update that. @@ -7595,6 +7466,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + @Override void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); proto.write(HASH_CODE, System.identityHashCode(this)); diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 8cf08814096ef..ca09537f6ad01 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -2308,14 +2308,14 @@ public class AppTransition implements Dump { } notifyAppTransitionTimeoutLocked(); if (isTransitionSet() || !dc.mOpeningApps.isEmpty() || !dc.mClosingApps.isEmpty() - || !dc.mChangingApps.isEmpty()) { + || !dc.mChangingContainers.isEmpty()) { ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "*** APP TRANSITION TIMEOUT. displayId=%d isTransitionSet()=%b " + "mOpeningApps.size()=%d mClosingApps.size()=%d " + "mChangingApps.size()=%d", dc.getDisplayId(), dc.mAppTransition.isTransitionSet(), dc.mOpeningApps.size(), dc.mClosingApps.size(), - dc.mChangingApps.size()); + dc.mChangingContainers.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 3f4e79162ed9d..0912b2e8b52f0 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -55,6 +55,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACT import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import android.annotation.NonNull; import android.os.Trace; import android.util.ArrayMap; import android.util.ArraySet; @@ -86,7 +87,7 @@ public class AppTransitionController { private final WallpaperController mWallpaperControllerLocked; private RemoteAnimationDefinition mRemoteAnimationDefinition = null; - private final ArrayMap mTempTransitionReasons = new ArrayMap<>(); + private final ArrayMap mTempTransitionReasons = new ArrayMap<>(); AppTransitionController(WindowManagerService service, DisplayContent displayContent) { mService = service; @@ -104,7 +105,8 @@ public class AppTransitionController { void handleAppTransitionReady() { mTempTransitionReasons.clear(); if (!transitionGoodToGo(mDisplayContent.mOpeningApps, mTempTransitionReasons) - || !transitionGoodToGo(mDisplayContent.mChangingApps, mTempTransitionReasons)) { + || !transitionGoodToGo(mDisplayContent.mChangingContainers, + mTempTransitionReasons)) { return; } Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady"); @@ -130,17 +132,21 @@ public class AppTransitionController { // transition selection depends on wallpaper target visibility. mDisplayContent.mOpeningApps.valueAtUnchecked(i).clearAnimatingFlags(); } - appCount = mDisplayContent.mChangingApps.size(); + appCount = mDisplayContent.mChangingContainers.size(); for (int i = 0; i < appCount; ++i) { // Clearing for same reason as above. - mDisplayContent.mChangingApps.valueAtUnchecked(i).clearAnimatingFlags(); + final ActivityRecord activity = getAppFromContainer( + mDisplayContent.mChangingContainers.valueAtUnchecked(i)); + if (activity != null) { + activity.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.mChangingApps); + mDisplayContent.mOpeningApps); // Determine if closing and opening app token sets are wallpaper targets, in which case // special animations are needed. @@ -159,7 +165,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 activityTypes = collectActivityTypes(mDisplayContent.mOpeningApps, - mDisplayContent.mClosingApps, mDisplayContent.mChangingApps); + mDisplayContent.mClosingApps, mDisplayContent.mChangingContainers); final boolean allowAnimations = mDisplayContent.getDisplayPolicy().allowAppAnimationsLw(); final ActivityRecord animLpActivity = allowAnimations ? findAnimLayoutParamsToken(transit, activityTypes) @@ -171,14 +177,13 @@ public class AppTransitionController { ? getTopApp(mDisplayContent.mClosingApps, false /* ignoreHidden */) : null; final ActivityRecord topChangingApp = allowAnimations - ? getTopApp(mDisplayContent.mChangingApps, false /* ignoreHidden */) + ? getTopApp(mDisplayContent.mChangingContainers, false /* ignoreHidden */) : null; final WindowManager.LayoutParams animLp = getAnimLp(animLpActivity); overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes); final boolean voiceInteraction = containsVoiceInteraction(mDisplayContent.mOpeningApps) - || containsVoiceInteraction(mDisplayContent.mOpeningApps) - || containsVoiceInteraction(mDisplayContent.mChangingApps); + || containsVoiceInteraction(mDisplayContent.mOpeningApps); final int layoutRedo; mService.mSurfaceAnimationRunner.deferStartingAnimations(); @@ -206,7 +211,7 @@ public class AppTransitionController { mDisplayContent.mOpeningApps.clear(); mDisplayContent.mClosingApps.clear(); - mDisplayContent.mChangingApps.clear(); + mDisplayContent.mChangingContainers.clear(); mDisplayContent.mUnknownAppVisibilityController.clear(); // This has changed the visibility of windows, so perform @@ -235,9 +240,9 @@ public class AppTransitionController { return mainWindow != null ? mainWindow.mAttrs : null; } - RemoteAnimationAdapter getRemoteAnimationOverride(ActivityRecord animLpActivity, + RemoteAnimationAdapter getRemoteAnimationOverride(@NonNull WindowContainer container, @TransitionType int transit, ArraySet activityTypes) { - final RemoteAnimationDefinition definition = animLpActivity.getRemoteAnimationDefinition(); + final RemoteAnimationDefinition definition = container.getRemoteAnimationDefinition(); if (definition != null) { final RemoteAnimationAdapter adapter = definition.getAdapter(transit, activityTypes); if (adapter != null) { @@ -271,6 +276,11 @@ public class AppTransitionController { } } + static ActivityRecord getAppFromContainer(WindowContainer wc) { + return wc.asTask() != null ? wc.asTask().getTopNonFinishingActivity() + : wc.asActivityRecord(); + } + /** * @return The window token that determines the animation theme. */ @@ -279,14 +289,14 @@ public class AppTransitionController { ActivityRecord result; final ArraySet closingApps = mDisplayContent.mClosingApps; final ArraySet openingApps = mDisplayContent.mOpeningApps; - final ArraySet changingApps = mDisplayContent.mChangingApps; + final ArraySet changingApps = mDisplayContent.mChangingContainers; // Remote animations always win, but fullscreen tokens override non-fullscreen tokens. result = lookForHighestTokenWithFilter(closingApps, openingApps, changingApps, w -> w.getRemoteAnimationDefinition() != null && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes)); if (result != null) { - return result; + return getAppFromContainer(result); } result = lookForHighestTokenWithFilter(closingApps, openingApps, changingApps, w -> w.fillsParent() && w.findMainWindow() != null); @@ -302,7 +312,7 @@ public class AppTransitionController { * of apps in {@code array1}, {@code array2}, and {@code array3}. */ private static ArraySet collectActivityTypes(ArraySet array1, - ArraySet array2, ArraySet array3) { + ArraySet array2, ArraySet array3) { final ArraySet result = new ArraySet<>(); for (int i = array1.size() - 1; i >= 0; i--) { result.add(array1.valueAt(i).getActivityType()); @@ -317,7 +327,7 @@ public class AppTransitionController { } private static ActivityRecord lookForHighestTokenWithFilter(ArraySet array1, - ArraySet array2, ArraySet array3, + ArraySet array2, ArraySet array3, Predicate filter) { final int array2base = array1.size(); final int array3base = array2.size() + array2base; @@ -325,15 +335,16 @@ public class AppTransitionController { int bestPrefixOrderIndex = Integer.MIN_VALUE; ActivityRecord bestToken = null; for (int i = 0; i < count; i++) { - final ActivityRecord wtoken = i < array2base + final WindowContainer wtoken = i < array2base ? array1.valueAt(i) : (i < array3base ? array2.valueAt(i - array2base) : array3.valueAt(i - array3base)); final int prefixOrderIndex = wtoken.getPrefixOrderIndex(); - if (filter.test(wtoken) && prefixOrderIndex > bestPrefixOrderIndex) { + final ActivityRecord r = getAppFromContainer(wtoken); + if (r != null && filter.test(r) && prefixOrderIndex > bestPrefixOrderIndex) { bestPrefixOrderIndex = prefixOrderIndex; - bestToken = wtoken; + bestToken = r; } } return bestToken; @@ -589,21 +600,13 @@ public class AppTransitionController { } private void handleChangingApps(@TransitionType int transit) { - final ArraySet apps = mDisplayContent.mChangingApps; + final ArraySet apps = mDisplayContent.mChangingContainers; final int appsCount = apps.size(); for (int i = 0; i < appsCount; i++) { - ActivityRecord activity = apps.valueAt(i); - ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Now changing app %s", activity); - activity.cancelAnimationOnly(); - activity.applyAnimation(null, transit, true, false, + WindowContainer wc = apps.valueAt(i); + ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Now changing app %s", wc); + wc.applyAnimation(null, transit, true, false, null /* animationFinishedCallback */); - activity.updateReportedVisibilityLocked(); - mService.openSurfaceTransaction(); - try { - activity.showAllWindowsLocked(); - } finally { - mService.closeSurfaceTransaction("handleChangingApps"); - } } } @@ -628,8 +631,8 @@ public class AppTransitionController { } } - private boolean transitionGoodToGo(ArraySet apps, - ArrayMap outReasons) { + private boolean transitionGoodToGo(ArraySet apps, + ArrayMap outReasons) { ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Checking %d opening apps (frozen=%b timeout=%b)...", apps.size(), mService.mDisplayFrozen, mDisplayContent.mAppTransition.isTimeout()); @@ -652,13 +655,17 @@ public class AppTransitionController { return false; } for (int i = 0; i < apps.size(); i++) { - ActivityRecord activity = apps.valueAt(i); + WindowContainer wc = apps.valueAt(i); + final ActivityRecord activity = getAppFromContainer(wc); + if (activity == null) { + continue; + } ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, - "Check opening app=%s: allDrawn=%b startingDisplayed=%b " - + "startingMoved=%b isRelaunching()=%b startingWindow=%s", - activity, activity.allDrawn, activity.startingDisplayed, - activity.startingMoved, activity.isRelaunching(), - activity.startingWindow); + "Check opening app=%s: allDrawn=%b startingDisplayed=%b " + + "startingMoved=%b isRelaunching()=%b startingWindow=%s", + activity, activity.allDrawn, activity.startingDisplayed, + activity.startingMoved, activity.isRelaunching(), + activity.startingWindow); final boolean allDrawn = activity.allDrawn && !activity.isRelaunching(); @@ -838,7 +845,7 @@ public class AppTransitionController { @VisibleForTesting boolean isTransitWithinTask(@TransitionType int transit, Task task) { if (task == null - || !mDisplayContent.mChangingApps.isEmpty()) { + || !mDisplayContent.mChangingContainers.isEmpty()) { // if there is no task, then we can't constrain to the task. // if anything is changing, it can animate outside its task. return false; @@ -882,12 +889,13 @@ public class AppTransitionController { * {@link ActivityRecord#isVisible}. * @return The top {@link ActivityRecord}. */ - private ActivityRecord getTopApp(ArraySet apps, boolean ignoreInvisible) { + private ActivityRecord getTopApp(ArraySet apps, + boolean ignoreInvisible) { int topPrefixOrderIndex = Integer.MIN_VALUE; ActivityRecord topApp = null; for (int i = apps.size() - 1; i >= 0; i--) { - final ActivityRecord app = apps.valueAt(i); - if (ignoreInvisible && !app.isVisible()) { + final ActivityRecord app = getAppFromContainer(apps.valueAt(i)); + if (app == null || ignoreInvisible && !app.isVisible()) { continue; } final int prefixOrderIndex = app.getPrefixOrderIndex(); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index e468810e0fdf6..30e8da2c23f79 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -91,7 +91,6 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STATES; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_STACK; import static com.android.server.wm.DisplayContentProto.APP_TRANSITION; -import static com.android.server.wm.DisplayContentProto.CHANGING_APPS; import static com.android.server.wm.DisplayContentProto.CLOSING_APPS; import static com.android.server.wm.DisplayContentProto.DISPLAY_FRAMES; import static com.android.server.wm.DisplayContentProto.DISPLAY_INFO; @@ -314,7 +313,7 @@ class DisplayContent extends WindowContainer mOpeningApps = new ArraySet<>(); final ArraySet mClosingApps = new ArraySet<>(); - final ArraySet mChangingApps = new ArraySet<>(); + final ArraySet mChangingContainers = new ArraySet<>(); final UnknownAppVisibilityController mUnknownAppVisibilityController; private MetricsLogger mMetricsLogger; @@ -2695,7 +2694,7 @@ class DisplayContent extends WindowContainer= 0; i--) { mClosingApps.valueAt(i).writeIdentifierToProto(proto, CLOSING_APPS); } - for (int i = mChangingApps.size() - 1; i >= 0; i--) { - mChangingApps.valueAt(i).writeIdentifierToProto(proto, CHANGING_APPS); - } proto.write(SINGLE_TASK_INSTANCE, mSingleTaskInstance); final ActivityStack focusedStack = getFocusedStack(); @@ -3670,7 +3666,7 @@ class DisplayContent extends WindowContainer 0) { pw.print(" mOpeningApps="); pw.println(mOpeningApps); @@ -3678,8 +3674,8 @@ class DisplayContent extends WindowContainer 0) { pw.print(" mClosingApps="); pw.println(mClosingApps); } - if (mChangingApps.size() > 0) { - pw.print(" mChangingApps="); pw.println(mChangingApps); + if (mChangingContainers.size() > 0) { + pw.print(" mChangingApps="); pw.println(mChangingContainers); } } diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index e69551a0fde4f..9468bff7dff35 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -500,7 +500,7 @@ public class RecentsAnimationController implements DeathRecipient { } if (mTargetActivityRecord != null) { - final ArrayMap reasons = new ArrayMap<>(1); + final ArrayMap reasons = new ArrayMap<>(1); reasons.put(mTargetActivityRecord, APP_TRANSITION_RECENTS_ANIM); mService.mAtmService.mStackSupervisor.getActivityMetricsLogger() .notifyTransitionStarting(reasons); diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index d2dbab841f16e..0eb9daf26d47e 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -387,7 +387,7 @@ class RemoteAnimationController implements DeathRecipient { final ActivityRecord topActivity = mWindowContainer.getTopMostActivity(); if (dc.mOpeningApps.contains(topActivity)) { return RemoteAnimationTarget.MODE_OPENING; - } else if (dc.mChangingApps.contains(topActivity)) { + } else if (dc.mChangingContainers.contains(topActivity)) { return RemoteAnimationTarget.MODE_CHANGING; } else { return RemoteAnimationTarget.MODE_CLOSING; diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java index 7164cd85230bb..1e54e69c0635e 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimator.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java @@ -128,7 +128,8 @@ class SurfaceAnimator { */ void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden, @AnimationType int type, - @Nullable OnAnimationFinishedCallback animationFinishedCallback) { + @Nullable OnAnimationFinishedCallback animationFinishedCallback, + @Nullable SurfaceFreezer freezer) { cancelAnimation(t, true /* restarting */, true /* forwardCancel */); mAnimation = anim; mAnimationType = type; @@ -139,9 +140,14 @@ class SurfaceAnimator { cancelAnimation(); return; } - mLeash = createAnimationLeash(surface, t, - mAnimatable.getSurfaceWidth(), mAnimatable.getSurfaceHeight(), hidden); - mAnimatable.onAnimationLeashCreated(t, mLeash); + mLeash = freezer != null ? freezer.takeLeashForAnimation() : null; + if (mLeash == null) { + mLeash = createAnimationLeash(mAnimatable, surface, t, + mAnimatable.getSurfaceWidth(), mAnimatable.getSurfaceHeight(), 0 /* x */, + 0 /* y */, hidden); + mAnimatable.onAnimationLeashCreated(t, mLeash); + } + mAnimatable.onLeashAnimationStarting(t, mLeash); if (mAnimationStartDelayed) { if (DEBUG_ANIM) Slog.i(TAG, "Animation start delayed"); return; @@ -149,6 +155,12 @@ class SurfaceAnimator { mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback); } + void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden, + @AnimationType int type, + @Nullable OnAnimationFinishedCallback animationFinishedCallback) { + startAnimation(t, anim, hidden, type, animationFinishedCallback, null /* freezer */); + } + void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden, @AnimationType int type) { startAnimation(t, anim, hidden, type, null /* animationFinishedCallback */); @@ -311,15 +323,31 @@ class SurfaceAnimator { } private void reset(Transaction t, boolean destroyLeash) { - final SurfaceControl surface = mAnimatable.getSurfaceControl(); - final SurfaceControl parent = mAnimatable.getParentSurfaceControl(); + mService.mAnimationTransferMap.remove(mAnimation); + mAnimation = null; + mAnimationFinishedCallback = null; + mAnimationType = ANIMATION_TYPE_NONE; + if (mLeash == null) { + return; + } + SurfaceControl leash = mLeash; + mLeash = null; + final boolean scheduleAnim = removeLeash(t, mAnimatable, leash, destroyLeash); + if (scheduleAnim) { + mService.scheduleAnimationLocked(); + } + } + static boolean removeLeash(Transaction t, Animatable animatable, @NonNull SurfaceControl leash, + boolean destroy) { boolean scheduleAnim = false; + final SurfaceControl surface = animatable.getSurfaceControl(); + final SurfaceControl parent = animatable.getParentSurfaceControl(); // If the surface was destroyed or the leash is invalid, we don't care to reparent it back. // Note that we also set this variable to true even if the parent isn't valid anymore, in // order to ensure onAnimationLeashLost still gets called in this case. - final boolean reparent = mLeash != null && surface != null; + final boolean reparent = surface != null; if (reparent) { if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to original parent: " + parent); // We shouldn't really need these isValid checks but we do @@ -329,40 +357,30 @@ class SurfaceAnimator { scheduleAnim = true; } } - mService.mAnimationTransferMap.remove(mAnimation); - if (mLeash != null && destroyLeash) { - t.remove(mLeash); + if (destroy) { + t.remove(leash); scheduleAnim = true; } - mLeash = null; - mAnimation = null; - mAnimationFinishedCallback = null; - mAnimationType = ANIMATION_TYPE_NONE; if (reparent) { // Make sure to inform the animatable after the surface was reparented (or reparent // wasn't possible, but we still need to invoke the callback) - mAnimatable.onAnimationLeashLost(t); + animatable.onAnimationLeashLost(t); scheduleAnim = true; } - - if (scheduleAnim) { - mService.scheduleAnimationLocked(); - } + return scheduleAnim; } - private SurfaceControl createAnimationLeash(SurfaceControl surface, Transaction t, int width, - int height, boolean hidden) { + static SurfaceControl createAnimationLeash(Animatable animatable, SurfaceControl surface, + Transaction t, int width, int height, int x, int y, boolean hidden) { if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to leash"); - final SurfaceControl.Builder builder = mAnimatable.makeAnimationLeash() - .setParent(mAnimatable.getAnimationLeashParent()) + final SurfaceControl.Builder builder = animatable.makeAnimationLeash() + .setParent(animatable.getAnimationLeashParent()) .setHidden(hidden) .setName(surface + " - animation-leash"); final SurfaceControl leash = builder.build(); t.setWindowCrop(leash, width, height); - - // TODO: rely on builder.setHidden(hidden) instead of show and setAlpha when b/138459974 is - // fixed. + t.setPosition(leash, x, y); t.show(leash); t.setAlpha(leash, hidden ? 0 : 1); @@ -489,13 +507,22 @@ class SurfaceAnimator { void commitPendingTransaction(); /** - * Called when the was created. + * Called when the animation leash is created. Note that this is also called by + * {@link SurfaceFreezer}, so this doesn't mean we're about to start animating. * * @param t The transaction to use to apply any necessary changes. * @param leash The leash that was created. */ void onAnimationLeashCreated(Transaction t, SurfaceControl leash); + /** + * Called when the animator is about to start animating the leash. + * + * @param t The transaction to use to apply any necessary changes. + * @param leash The leash that was created. + */ + default void onLeashAnimationStarting(Transaction t, SurfaceControl leash) { } + /** * Called when the leash is being destroyed, or when the leash is being transferred to * another SurfaceAnimator. diff --git a/services/core/java/com/android/server/wm/SurfaceFreezer.java b/services/core/java/com/android/server/wm/SurfaceFreezer.java new file mode 100644 index 0000000000000..20435ea7dfafb --- /dev/null +++ b/services/core/java/com/android/server/wm/SurfaceFreezer.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2020 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.ProtoLogGroup.WM_SHOW_TRANSACTIONS; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.GraphicBuffer; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.view.Surface; +import android.view.SurfaceControl; + +import com.android.server.protolog.common.ProtoLog; + +import java.util.function.Supplier; + +/** + * This class handles "freezing" of an Animatable. The Animatable in question should implement + * Freezable. + * + * The point of this is to enable WindowContainers to each be capable of freezing themselves. + * Freezing means taking a snapshot and placing it above everything in the sub-hierarchy. + * The "placing above" requires that a parent surface be inserted above the target surface so that + * the target surface and the snapshot are siblings. + * + * The overall flow for a transition using this would be: + * 1. Set transition and record animatable in mChangingApps + * 2. Call {@link #freeze} to set-up the leashes and cover with a snapshot. + * 3. When transition participants are ready, start SurfaceAnimator with this as a parameter + * 4. SurfaceAnimator will then {@link #takeLeashForAnimation} instead of creating another leash. + * 5. The animation system should eventually clean this up via {@link #unfreeze}. + */ +class SurfaceFreezer { + + private final Freezable mAnimatable; + private final WindowManagerService mWmService; + private SurfaceControl mLeash; + Snapshot mSnapshot = null; + final Rect mFreezeBounds = new Rect(); + + /** + * @param animatable The object to animate. + */ + SurfaceFreezer(Freezable animatable, WindowManagerService service) { + mAnimatable = animatable; + mWmService = service; + } + + /** + * Freeze the target surface. This is done by creating a leash (inserting a parent surface + * above the target surface) and then taking a snapshot and placing it over the target surface. + * + * @param startBounds The original bounds (on screen) of the surface we are snapshotting. + */ + void freeze(SurfaceControl.Transaction t, Rect startBounds) { + mFreezeBounds.set(startBounds); + + mLeash = SurfaceAnimator.createAnimationLeash(mAnimatable, mAnimatable.getSurfaceControl(), + t, startBounds.width(), startBounds.height(), startBounds.left, startBounds.top, + false /* hidden */); + mAnimatable.onAnimationLeashCreated(t, mLeash); + + SurfaceControl freezeTarget = mAnimatable.getFreezeSnapshotTarget(); + if (freezeTarget != null) { + GraphicBuffer snapshot = createSnapshotBuffer(freezeTarget, startBounds); + if (snapshot != null) { + mSnapshot = new Snapshot(mWmService.mSurfaceFactory, t, snapshot, mLeash); + } + } + } + + /** + * Used by {@link SurfaceAnimator}. This "transfers" the leash to be used for animation. + * By transferring the leash, this will no longer try to clean-up the leash when finished. + */ + SurfaceControl takeLeashForAnimation() { + SurfaceControl out = mLeash; + mLeash = null; + return out; + } + + /** + * Clean-up the snapshot and remove leash. If the leash was taken, this just cleans-up the + * snapshot. + */ + void unfreeze(SurfaceControl.Transaction t) { + if (mSnapshot != null) { + mSnapshot.destroy(t); + } + if (mLeash == null) { + return; + } + SurfaceControl leash = mLeash; + mLeash = null; + final boolean scheduleAnim = SurfaceAnimator.removeLeash(t, mAnimatable, leash, + false /* destroy */); + if (scheduleAnim) { + mWmService.scheduleAnimationLocked(); + } + } + + boolean hasLeash() { + return mLeash != null; + } + + private static GraphicBuffer createSnapshotBuffer(@NonNull SurfaceControl target, + @Nullable Rect bounds) { + Rect cropBounds = null; + if (bounds != null) { + cropBounds = new Rect(bounds); + cropBounds.offsetTo(0, 0); + } + final SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer = + SurfaceControl.captureLayers( + target, cropBounds, 1.f /* frameScale */, PixelFormat.RGBA_8888); + final GraphicBuffer buffer = screenshotBuffer != null ? screenshotBuffer.getGraphicBuffer() + : null; + if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) { + return null; + } + return buffer; + } + + class Snapshot { + private SurfaceControl mSurfaceControl; + private AnimationAdapter mAnimation; + private SurfaceAnimator.OnAnimationFinishedCallback mFinishedCallback; + + /** + * @param t Transaction to create the thumbnail in. + * @param thumbnailHeader A thumbnail or placeholder for thumbnail to initialize with. + */ + Snapshot(Supplier surfaceFactory, SurfaceControl.Transaction t, + GraphicBuffer thumbnailHeader, SurfaceControl parent) { + Surface drawSurface = surfaceFactory.get(); + // We can't use a delegating constructor since we need to + // reference this::onAnimationFinished + final int width = thumbnailHeader.getWidth(); + final int height = thumbnailHeader.getHeight(); + + mSurfaceControl = mAnimatable.makeAnimationLeash() + .setName("snapshot anim: " + mAnimatable.toString()) + .setBufferSize(width, height) + .setFormat(PixelFormat.TRANSLUCENT) + .setParent(parent) + .build(); + + ProtoLog.i(WM_SHOW_TRANSACTIONS, " THUMBNAIL %s: CREATE", mSurfaceControl); + + // Transfer the thumbnail to the surface + drawSurface.copyFrom(mSurfaceControl); + drawSurface.attachAndQueueBuffer(thumbnailHeader); + drawSurface.release(); + t.show(mSurfaceControl); + + // We parent the thumbnail to the container, and just place it on top of anything else + // in the container. + t.setLayer(mSurfaceControl, Integer.MAX_VALUE); + } + + void destroy(SurfaceControl.Transaction t) { + if (mSurfaceControl == null) { + return; + } + t.remove(mSurfaceControl); + mSurfaceControl = null; + } + + /** + * Starts an animation. + * + * @param anim The object that bridges the controller, {@link SurfaceAnimator}, with the + * component responsible for running the animation. It runs the animation with + * {@link AnimationAdapter#startAnimation} once the hierarchy with + * the Leash has been set up. + * @param animationFinishedCallback The callback being triggered when the animation + * finishes. + */ + void startAnimation(SurfaceControl.Transaction t, AnimationAdapter anim, int type, + @Nullable SurfaceAnimator.OnAnimationFinishedCallback animationFinishedCallback) { + cancelAnimation(t, true /* restarting */); + mAnimation = anim; + mFinishedCallback = animationFinishedCallback; + if (mSurfaceControl == null) { + cancelAnimation(t, false /* restarting */); + return; + } + mAnimation.startAnimation(mSurfaceControl, t, type, animationFinishedCallback); + } + + /** + * Cancels the animation, and resets the leash. + * + * @param t The transaction to use for all cancelling surface operations. + * @param restarting Whether we are restarting the animation. + */ + private void cancelAnimation(SurfaceControl.Transaction t, boolean restarting) { + final SurfaceControl leash = mSurfaceControl; + final AnimationAdapter animation = mAnimation; + final SurfaceAnimator.OnAnimationFinishedCallback animationFinishedCallback = + mFinishedCallback; + mAnimation = null; + mFinishedCallback = null; + if (animation != null) { + animation.onAnimationCancelled(leash); + if (!restarting) { + if (animationFinishedCallback != null) { + animationFinishedCallback.onAnimationFinished( + ANIMATION_TYPE_APP_TRANSITION, animation); + } + } + } + if (!restarting) { + // TODO: do we need to destroy? + destroy(t); + } + } + } + + /** freezable */ + public interface Freezable extends SurfaceAnimator.Animatable { + /** + * @return The surface to take a snapshot of. If this returns {@code null}, no snapshot + * will be generated (but the rest of the freezing logic will still happen). + */ + @Nullable SurfaceControl getFreezeSnapshotTarget(); + } +} diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 76805e9f6342d..07e17e8f4048a 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -58,6 +58,7 @@ import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.SurfaceControl.METADATA_TASK_ID; +import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE; import static com.android.internal.policy.DecorView.DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP; import static com.android.internal.policy.DecorView.DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP; @@ -78,6 +79,9 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.ActivityTaskManagerService.TAG_STACK; import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER; +import static com.android.server.wm.IdentifierProto.HASH_CODE; +import static com.android.server.wm.IdentifierProto.TITLE; +import static com.android.server.wm.IdentifierProto.USER_ID; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; @@ -119,10 +123,13 @@ import android.os.Trace; import android.os.UserHandle; import android.provider.Settings; import android.service.voice.IVoiceInteractionSession; +import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.Slog; +import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; import android.view.ITaskOrganizer; +import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; import android.view.Surface; import android.view.SurfaceControl; @@ -1884,6 +1891,8 @@ class Task extends WindowContainer { .setBounds(mLastNonFullscreenBounds); } + final int prevWinMode = getWindowingMode(); + mTmpPrevBounds.set(getBounds()); final boolean wasInMultiWindowMode = inMultiWindowMode(); super.onConfigurationChanged(newParentConfig); if (wasInMultiWindowMode != inMultiWindowMode()) { @@ -1891,6 +1900,12 @@ class Task extends WindowContainer { updateShadowsRadius(isFocused(), getPendingTransaction()); } + final int newWinMode = getWindowingMode(); + if ((prevWinMode != newWinMode) && (mDisplayContent != null) + && shouldStartChangeTransition(prevWinMode, newWinMode)) { + initializeChangeTransition(mTmpPrevBounds); + } + // If the configuration supports persistent bounds (eg. Freeform), keep track of the // current (non-fullscreen) bounds for persistence. if (getWindowConfiguration().persistTaskBounds()) { @@ -1904,6 +1919,63 @@ class Task extends WindowContainer { saveLaunchingStateIfNeeded(); } + /** + * Initializes a change transition. See {@link SurfaceFreezer} for more information. + */ + private void initializeChangeTransition(Rect startBounds) { + mDisplayContent.prepareAppTransition(TRANSIT_TASK_CHANGE_WINDOWING_MODE, + false /* alwaysKeepCurrent */, 0, false /* forceOverride */); + mDisplayContent.mChangingContainers.add(this); + + mSurfaceFreezer.freeze(getPendingTransaction(), startBounds); + } + + private boolean shouldStartChangeTransition(int prevWinMode, int newWinMode) { + if (mWmService.mDisableTransitionAnimation + || !isVisible() + || getDisplayContent().mAppTransition.isTransitionSet() + || getSurfaceControl() == null) { + 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); + } + + @VisibleForTesting + boolean isInChangeTransition() { + return mSurfaceFreezer.hasLeash() || AppTransition.isChangeTransit(mTransit); + } + + @Override + public SurfaceControl getFreezeSnapshotTarget() { + final int transit = mDisplayContent.mAppTransition.getAppTransition(); + if (!AppTransition.isChangeTransit(transit)) { + return null; + } + // Skip creating snapshot if this transition is controlled by a remote animator which + // doesn't need it. + final ArraySet activityTypes = new ArraySet<>(); + activityTypes.add(getActivityType()); + final RemoteAnimationAdapter adapter = + mDisplayContent.mAppTransitionController.getRemoteAnimationOverride( + this, transit, activityTypes); + if (adapter != null && !adapter.getChangeNeedsSnapshot()) { + return null; + } + return getSurfaceControl(); + } + + @Override + void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(HASH_CODE, System.identityHashCode(this)); + proto.write(USER_ID, mUserId); + proto.write(TITLE, intent != null && intent.getComponent() != null + ? intent.getComponent().flattenToShortString() : "Task"); + proto.end(token); + } + /** * Saves launching state if necessary so that we can launch the activity to its latest state. * It only saves state if this task has been shown to user and it's in fullscreen or freeform @@ -2614,12 +2686,22 @@ class Task extends WindowContainer { if (!isRootTask) { adjustBoundsForDisplayChangeIfNeeded(dc); } + final DisplayContent prevDc = mDisplayContent; super.onDisplayChanged(dc); if (!isRootTask) { final int displayId = (dc != null) ? dc.getDisplayId() : INVALID_DISPLAY; mWmService.mAtmService.getTaskChangeNotificationController().notifyTaskDisplayChanged( mTaskId, displayId); } + if (prevDc != null && prevDc.mChangingContainers.remove(this)) { + // This gets called *after* this has been reparented to the new display. + // That reparenting resulted in this window changing modes (eg. FREEFORM -> FULLSCREEN), + // so this token is now "frozen" while waiting for the animation to start on prevDc + // (which will be cancelled since the window is no-longer a child). However, since this + // is no longer a child of prevDc, this won't be notified of the cancelled animation, + // so we need to cancel the change transition here. + mSurfaceFreezer.unfreeze(getPendingTransaction()); + } } /** @@ -3010,15 +3092,6 @@ class Task extends WindowContainer { return forAllTasks((t) -> { return t != this && t.isTaskAnimating(); }); } - /** - * @return {@code true} if changing app transition is running. - */ - @Override - boolean isChangingAppTransition() { - final ActivityRecord activity = getTopVisibleActivity(); - return activity != null && getDisplayContent().mChangingApps.contains(activity); - } - @Override RemoteAnimationTarget createRemoteAnimationTarget( RemoteAnimationController.RemoteAnimationRecord record) { diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 137d122cd6d53..669eb7804d415 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -669,8 +669,7 @@ 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 openingApps, - ArraySet changingApps) { + void adjustWallpaperWindowsForAppTransitionIfNeeded(ArraySet openingApps) { boolean adjust = false; if ((mDisplayContent.pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) { adjust = true; @@ -682,15 +681,6 @@ class WallpaperController { break; } } - if (!adjust) { - for (int i = changingApps.size() - 1; i >= 0; --i) { - final ActivityRecord activity = changingApps.valueAt(i); - if (activity.windowsCanBeWallpaperTarget()) { - adjust = true; - break; - } - } - } } if (adjust) { diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index aaaabf2b75607..a0a70dc6a1d76 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -25,9 +25,13 @@ import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.content.res.Configuration.ORIENTATION_UNDEFINED; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; +import static android.os.UserHandle.USER_NULL; import static android.view.SurfaceControl.Transaction; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; +import static com.android.server.wm.IdentifierProto.HASH_CODE; +import static com.android.server.wm.IdentifierProto.TITLE; +import static com.android.server.wm.IdentifierProto.USER_ID; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION; @@ -64,6 +68,7 @@ import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; import android.view.IWindowContainer; import android.view.MagnificationSpec; +import android.view.RemoteAnimationDefinition; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.view.SurfaceControl.Builder; @@ -94,7 +99,7 @@ import java.util.function.Predicate; * changes are made to this class. */ class WindowContainer extends ConfigurationContainer - implements Comparable, Animatable, + implements Comparable, Animatable, SurfaceFreezer.Freezable, BLASTSyncEngine.TransactionReadyListener { private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowContainer" : TAG_WM; @@ -169,6 +174,7 @@ class WindowContainer extends ConfigurationContainer< * Applied as part of the animation pass in "prepareSurfaces". */ protected final SurfaceAnimator mSurfaceAnimator; + final SurfaceFreezer mSurfaceFreezer; protected final WindowManagerService mWmService; private final Point mTmpPos = new Point(); @@ -252,7 +258,6 @@ class WindowContainer extends ConfigurationContainer< * where it represents the starting-state snapshot. */ WindowContainerThumbnail mThumbnail; - final Rect mTransitStartRect = new Rect(); final Point mTmpPoint = new Point(); protected final Rect mTmpRect = new Rect(); final Rect mTmpPrevBounds = new Rect(); @@ -277,6 +282,7 @@ class WindowContainer extends ConfigurationContainer< mWmService = wms; mPendingTransaction = wms.mTransactionFactory.get(); mSurfaceAnimator = new SurfaceAnimator(this, this::onAnimationFinished, wms); + mSurfaceFreezer = new SurfaceFreezer(this, wms); } @Override @@ -834,7 +840,7 @@ class WindowContainer extends ConfigurationContainer< * @return {@code true} if the container is in changing app transition. */ boolean isChangingAppTransition() { - return false; + return mDisplayContent != null && mDisplayContent.mChangingContainers.contains(this); } void sendAppVisibilityToClients() { @@ -885,6 +891,31 @@ class WindowContainer extends ConfigurationContainer< return false; } + /** + * Called when the visibility of a child is asked to change. This is before visibility actually + * changes (eg. a transition animation might play out first). + */ + void onChildVisibilityRequested(boolean visible) { + // If we are changing visibility, then a snapshot isn't necessary and we are no-longer + // part of a change transition. + mSurfaceFreezer.unfreeze(getPendingTransaction()); + if (mDisplayContent != null) { + mDisplayContent.mChangingContainers.remove(this); + } + WindowContainer parent = getParent(); + if (parent != null) { + parent.onChildVisibilityRequested(visible); + } + } + + void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(HASH_CODE, System.identityHashCode(this)); + proto.write(USER_ID, USER_NULL); + proto.write(TITLE, "WindowContainer"); + proto.end(token); + } + /** * Returns {@code true} if this container is focusable. Generally, if a parent is not focusable, * this will not be focusable either. @@ -1917,7 +1948,8 @@ class WindowContainer extends ConfigurationContainer< // TODO: This should use isVisible() but because isVisible has a really weird meaning at // the moment this doesn't work for all animatable window containers. - mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback); + mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback, + mSurfaceFreezer); } void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden, @@ -1933,6 +1965,11 @@ class WindowContainer extends ConfigurationContainer< mSurfaceAnimator.cancelAnimation(); } + @Override + public SurfaceControl getFreezeSnapshotTarget() { + return null; + } + @Override public Builder makeAnimationLeash() { return makeSurface().setContainerLayer(); @@ -2002,8 +2039,9 @@ class WindowContainer extends ConfigurationContainer< getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; } if (thumbnailAdapter != null) { - mThumbnail.startAnimation( - getPendingTransaction(), thumbnailAdapter, !isVisible()); + mSurfaceFreezer.mSnapshot.startAnimation(getPendingTransaction(), + thumbnailAdapter, ANIMATION_TYPE_APP_TRANSITION, + (type, anim) -> { }); } } } else { @@ -2049,7 +2087,7 @@ class WindowContainer extends ConfigurationContainer< if (controller != null && !mSurfaceAnimator.isAnimationStartDelayed()) { final RemoteAnimationController.RemoteAnimationRecord adapters = controller.createRemoteAnimationRecord(this, mTmpPoint, mTmpRect, - (isChanging ? mTransitStartRect : null)); + (isChanging ? mSurfaceFreezer.mFreezeBounds : null)); resultAdapters = new Pair<>(adapters.mAdapter, adapters.mThumbnailAdapter); } else if (isChanging) { final float durationScale = mWmService.getTransitionAnimationScaleLocked(); @@ -2057,14 +2095,15 @@ class WindowContainer extends ConfigurationContainer< mTmpRect.offsetTo(mTmpPoint.x, mTmpPoint.y); final AnimationAdapter adapter = new LocalAnimationAdapter( - new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect, displayInfo, - durationScale, true /* isAppAnimation */, false /* isThumbnail */), + new WindowChangeAnimationSpec(mSurfaceFreezer.mFreezeBounds, mTmpRect, + displayInfo, durationScale, true /* isAppAnimation */, + false /* isThumbnail */), getSurfaceAnimationRunner()); - final AnimationAdapter thumbnailAdapter = mThumbnail != null - ? new LocalAnimationAdapter(new WindowChangeAnimationSpec(mTransitStartRect, - mTmpRect, displayInfo, durationScale, true /* isAppAnimation */, - true /* isThumbnail */), getSurfaceAnimationRunner()) + final AnimationAdapter thumbnailAdapter = mSurfaceFreezer.mSnapshot != null + ? new LocalAnimationAdapter(new WindowChangeAnimationSpec( + mSurfaceFreezer.mFreezeBounds, mTmpRect, displayInfo, durationScale, + true /* isAppAnimation */, true /* isThumbnail */), getSurfaceAnimationRunner()) : null; resultAdapters = new Pair<>(adapter, thumbnailAdapter); mTransit = transit; @@ -2182,6 +2221,7 @@ class WindowContainer extends ConfigurationContainer< @Override public void onAnimationLeashLost(Transaction t) { mLastLayer = -1; + mSurfaceFreezer.unfreeze(t); reassignLayer(t); } @@ -2322,6 +2362,10 @@ class WindowContainer extends ConfigurationContainer< mSurfaceControl = sc; } + RemoteAnimationDefinition getRemoteAnimationDefinition() { + return null; + } + /** Cheap way of doing cast and instanceof. */ Task asTask() { return null; diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java index 7204a811fbe2d..e1ce431fc97c5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java @@ -255,7 +255,7 @@ public class ActivityMetricsLaunchObserverTests extends ActivityTestsBase { } private void notifyTransitionStarting(ActivityRecord activity) { - final ArrayMap reasons = new ArrayMap<>(); + final ArrayMap reasons = new ArrayMap<>(); reasons.put(activity, ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN); mActivityMetricsLogger.notifyTransitionStarting(reasons); } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java index 8c2b2939e5408..4cb50c7a9e4de 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java @@ -103,12 +103,12 @@ public class AppChangeTransitionTests extends WindowTestsBase { setUpOnDisplay(mDisplayContent); mTask.setWindowingMode(WINDOWING_MODE_FREEFORM); - assertEquals(1, mDisplayContent.mChangingApps.size()); + assertEquals(1, mDisplayContent.mChangingContainers.size()); // Verify we are in a change transition, but without a snapshot. // Though, the test will actually have crashed by now if a snapshot is attempted. - assertNull(mActivity.getThumbnail()); - assertTrue(mActivity.isInChangeTransition()); + assertNull(mTask.mSurfaceFreezer.mSnapshot); + assertTrue(mTask.isInChangeTransition()); waitUntilHandlersIdle(); mActivity.removeImmediately(); @@ -121,14 +121,14 @@ public class AppChangeTransitionTests extends WindowTestsBase { setUpOnDisplay(mDisplayContent); mTask.setWindowingMode(WINDOWING_MODE_FREEFORM); - assertEquals(1, mDisplayContent.mChangingApps.size()); - assertTrue(mActivity.isInChangeTransition()); + assertEquals(1, mDisplayContent.mChangingContainers.size()); + assertTrue(mTask.isInChangeTransition()); // Removing the app-token from the display should clean-up the // the change leash. mDisplayContent.removeAppToken(mActivity.token); - assertEquals(0, mDisplayContent.mChangingApps.size()); - assertFalse(mActivity.isInChangeTransition()); + assertEquals(0, mDisplayContent.mChangingContainers.size()); + assertFalse(mTask.isInChangeTransition()); waitUntilHandlersIdle(); mActivity.removeImmediately(); @@ -152,8 +152,8 @@ public class AppChangeTransitionTests extends WindowTestsBase { assertEquals(WINDOWING_MODE_FULLSCREEN, mTask.getWindowingMode()); // Make sure we're not waiting for a change animation (no leash) - assertFalse(mActivity.isInChangeTransition()); - assertNull(mActivity.getThumbnail()); + assertFalse(mTask.isInChangeTransition()); + assertNull(mActivity.mSurfaceFreezer.mSnapshot); waitUntilHandlersIdle(); mActivity.removeImmediately(); @@ -165,13 +165,13 @@ public class AppChangeTransitionTests extends WindowTestsBase { setUpOnDisplay(mDisplayContent); mTask.setWindowingMode(WINDOWING_MODE_FREEFORM); - assertEquals(1, mDisplayContent.mChangingApps.size()); - assertTrue(mActivity.isInChangeTransition()); + assertEquals(1, mDisplayContent.mChangingContainers.size()); + assertTrue(mTask.isInChangeTransition()); // Changing visibility should cancel the change transition and become closing mActivity.setVisibility(false, false); - assertEquals(0, mDisplayContent.mChangingApps.size()); - assertFalse(mActivity.isInChangeTransition()); + assertEquals(0, mDisplayContent.mChangingContainers.size()); + assertFalse(mTask.isInChangeTransition()); waitUntilHandlersIdle(); mActivity.removeImmediately(); 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 3a724a140ffb5..c7f94efdfde0d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -247,7 +247,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { @Test public void testChange() throws Exception { final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); - mDisplayContent.mChangingApps.add(win.mActivityRecord); + mDisplayContent.mChangingContainers.add(win.mActivityRecord); try { final RemoteAnimationRecord record = mController.createRemoteAnimationRecord( win.mActivityRecord, new Point(50, 100), new Rect(50, 100, 150, 150), @@ -290,7 +290,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { verify(mThumbnailFinishedCallback).onAnimationFinished( eq(ANIMATION_TYPE_WINDOW_ANIMATION), eq(record.mThumbnailAdapter)); } finally { - mDisplayContent.mChangingApps.clear(); + mDisplayContent.mChangingContainers.clear(); } }