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(); } }