Loading data/etc/services.core.protolog.json +12 −0 Original line number Diff line number Diff line Loading @@ -2647,6 +2647,12 @@ "group": "WM_DEBUG_ANIM", "at": "com\/android\/server\/wm\/WindowContainer.java" }, "390947100": { "message": "Screenshotting %s [%s]", "level": "VERBOSE", "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/Transition.java" }, "397382873": { "message": "Moving to PAUSED: %s %s", "level": "VERBOSE", Loading Loading @@ -4171,6 +4177,12 @@ "group": "WM_DEBUG_FOCUS_LIGHT", "at": "com\/android\/server\/wm\/InputMonitor.java" }, "2004282287": { "message": "Override sync-method for %s because seamless rotating", "level": "VERBOSE", "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/Transition.java" }, "2010476671": { "message": "Animation done in %s: reportedVisible=%b okToDisplay=%b okToAnimate=%b startingDisplayed=%b", "level": "VERBOSE", Loading services/core/java/com/android/server/wm/BLASTSyncEngine.java +23 −9 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE; import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Trace; import android.util.ArraySet; import android.util.Slog; Loading Loading @@ -63,6 +64,15 @@ import java.util.ArrayList; class BLASTSyncEngine { private static final String TAG = "BLASTSyncEngine"; /** No specific method. Used by override specifiers. */ public static final int METHOD_UNDEFINED = -1; /** No sync method. Apps will draw/present internally and just report. */ public static final int METHOD_NONE = 0; /** Sync with BLAST. Apps will draw and then send the buffer to be applied in sync. */ public static final int METHOD_BLAST = 1; interface TransactionReadyListener { void onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction); } Loading @@ -85,6 +95,7 @@ class BLASTSyncEngine { */ class SyncGroup { final int mSyncId; final int mSyncMethod; final TransactionReadyListener mListener; final Runnable mOnTimeout; boolean mReady = false; Loading @@ -92,8 +103,9 @@ class BLASTSyncEngine { private SurfaceControl.Transaction mOrphanTransaction = null; private String mTraceName; private SyncGroup(TransactionReadyListener listener, int id, String name) { private SyncGroup(TransactionReadyListener listener, int id, String name, int method) { mSyncId = id; mSyncMethod = method; mListener = listener; mOnTimeout = () -> { Slog.w(TAG, "Sync group " + mSyncId + " timeout"); Loading Loading @@ -271,16 +283,13 @@ class BLASTSyncEngine { * Prepares a {@link SyncGroup} that is not active yet. Caller must call {@link #startSyncSet} * before calling {@link #addToSyncSet(int, WindowContainer)} on any {@link WindowContainer}. */ SyncGroup prepareSyncSet(TransactionReadyListener listener, String name) { return new SyncGroup(listener, mNextSyncId++, name); SyncGroup prepareSyncSet(TransactionReadyListener listener, String name, int method) { return new SyncGroup(listener, mNextSyncId++, name, method); } int startSyncSet(TransactionReadyListener listener) { return startSyncSet(listener, BLAST_TIMEOUT_DURATION, ""); } int startSyncSet(TransactionReadyListener listener, long timeoutMs, String name) { final SyncGroup s = prepareSyncSet(listener, name); int startSyncSet(TransactionReadyListener listener, long timeoutMs, String name, int method) { final SyncGroup s = prepareSyncSet(listener, name, method); startSyncSet(s, timeoutMs); return s.mSyncId; } Loading @@ -302,6 +311,11 @@ class BLASTSyncEngine { scheduleTimeout(s, timeoutMs); } @Nullable SyncGroup getSyncSet(int id) { return mActiveSyncs.get(id); } boolean hasActiveSync() { return mActiveSyncs.size() != 0; } Loading services/core/java/com/android/server/wm/DisplayContent.java +8 −0 Original line number Diff line number Diff line Loading @@ -3304,6 +3304,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mAsyncRotationController.keepAppearanceInPreviousRotation(); } } else if (isRotationChanging()) { if (displayChange != null) { final boolean seamless = mDisplayRotation.shouldRotateSeamlessly( displayChange.getStartRotation(), displayChange.getEndRotation(), false /* forceUpdate */); if (seamless) { t.onSeamlessRotating(this); } } mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN); controller.mTransitionMetricsReporter.associate(t, startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN)); Loading services/core/java/com/android/server/wm/Task.java +8 −3 Original line number Diff line number Diff line Loading @@ -1889,8 +1889,7 @@ class Task extends TaskFragment { } final int newWinMode = getWindowingMode(); if ((prevWinMode != newWinMode) && (mDisplayContent != null) && shouldStartChangeTransition(prevWinMode, newWinMode)) { if (shouldStartChangeTransition(prevWinMode, mTmpPrevBounds)) { initializeChangeTransition(mTmpPrevBounds); } Loading Loading @@ -2141,10 +2140,16 @@ class Task extends TaskFragment { bounds.offset(horizontalDiff, verticalDiff); } private boolean shouldStartChangeTransition(int prevWinMode, int newWinMode) { private boolean shouldStartChangeTransition(int prevWinMode, @NonNull Rect prevBounds) { if (!isLeafTask() || !canStartChangeTransition()) { return false; } final int newWinMode = getWindowingMode(); if (mTransitionController.inTransition(this)) { final Rect newBounds = getConfiguration().windowConfiguration.getBounds(); return prevWinMode != newWinMode || prevBounds.width() != newBounds.width() || prevBounds.height() != newBounds.height(); } // 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); Loading services/core/java/com/android/server/wm/Transition.java +179 −2 Original line number Diff line number Diff line Loading @@ -68,6 +68,7 @@ import android.app.ActivityManager; import android.content.pm.ActivityInfo; import android.graphics.Point; import android.graphics.Rect; import android.hardware.HardwareBuffer; import android.os.Binder; import android.os.IBinder; import android.os.IRemoteCallback; Loading Loading @@ -205,6 +206,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe /** @see #setCanPipOnFinish */ private boolean mCanPipOnFinish = true; private boolean mIsSeamlessRotation = false; private IContainerFreezer mContainerFreezer = null; Transition(@TransitionType int type, @TransitionFlags int flags, TransitionController controller, BLASTSyncEngine syncEngine) { mType = type; Loading Loading @@ -265,10 +269,31 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe return mTargetDisplays.contains(dc); } /** Set a transition to be a seamless-rotation. */ void setSeamlessRotation(@NonNull WindowContainer wc) { final ChangeInfo info = mChanges.get(wc); if (info == null) return; info.mFlags = info.mFlags | ChangeInfo.FLAG_SEAMLESS_ROTATION; onSeamlessRotating(wc.getDisplayContent()); } /** * Called when it's been determined that this is transition is a seamless rotation. This should * be called before any WM changes have happened. */ void onSeamlessRotating(@NonNull DisplayContent dc) { // Don't need to do anything special if everything is using BLAST sync already. if (mSyncEngine.getSyncSet(mSyncId).mSyncMethod == BLASTSyncEngine.METHOD_BLAST) return; if (mContainerFreezer == null) { mContainerFreezer = new ScreenshotFreezer(); } mIsSeamlessRotation = true; final WindowState top = dc.getDisplayPolicy().getTopFullscreenOpaqueWindow(); if (top != null) { top.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST; ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Override sync-method for %s " + "because seamless rotating", top.getName()); } } /** Loading @@ -285,6 +310,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } } /** Only for testing. */ void setContainerFreezer(IContainerFreezer freezer) { mContainerFreezer = freezer; } @TransitionState int getState() { return mState; Loading Loading @@ -314,13 +344,18 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe return mState == STATE_COLLECTING || mState == STATE_STARTED; } /** Starts collecting phase. Once this starts, all relevant surface operations are sync. */ @VisibleForTesting void startCollecting(long timeoutMs) { startCollecting(timeoutMs, TransitionController.SYNC_METHOD); } /** Starts collecting phase. Once this starts, all relevant surface operations are sync. */ void startCollecting(long timeoutMs, int method) { if (mState != STATE_PENDING) { throw new IllegalStateException("Attempting to re-use a transition"); } mState = STATE_COLLECTING; mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG); mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG, method); mController.mTransitionTracer.logState(this); } Loading Loading @@ -414,6 +449,37 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mChanges.get(wc).mExistenceChanged = true; } /** * Records that a particular container is changing visibly (ie. something about it is changing * while it remains visible). This only effects windows that are already in the collecting * transition. */ void collectVisibleChange(WindowContainer wc) { if (mSyncEngine.getSyncSet(mSyncId).mSyncMethod == BLASTSyncEngine.METHOD_BLAST) { // All windows are synced already. return; } if (!isInTransition(wc)) return; if (mContainerFreezer == null) { mContainerFreezer = new ScreenshotFreezer(); } Transition.ChangeInfo change = mChanges.get(wc); if (change == null || !change.mVisible || !wc.isVisibleRequested()) return; // Note: many more tests have already been done by caller. mContainerFreezer.freeze(wc, change.mAbsoluteBounds); } /** * @return {@code true} if `wc` is a participant or is a descendant of one. */ boolean isInTransition(WindowContainer wc) { for (WindowContainer p = wc; p != null; p = p.getParent()) { if (mParticipants.contains(p)) return true; } return false; } /** * Specifies configuration change explicitly for the window container, so it can be chosen as * transition target. This is usually used with transition mode Loading Loading @@ -531,6 +597,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe displays.add(target.getDisplayContent()); } } // Remove screenshot layers if necessary if (mContainerFreezer != null) { mContainerFreezer.cleanUp(t); } // Need to update layers on involved displays since they were all paused while // the animation played. This puts the layers back into the correct order. mController.mBuildingFinishLayers = true; Loading Loading @@ -1982,4 +2052,111 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe return sortedTargets; } } /** * Interface for freezing a container's content during sync preparation. Really just one impl * but broken into an interface for testing (since you can't take screenshots in unit tests). */ interface IContainerFreezer { /** * Makes sure a particular window is "frozen" for the remainder of a sync. * * @return whether the freeze was successful. It fails if `wc` is already in a frozen window * or is not visible/ready. */ boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds); /** Populates `t` with operations that clean-up any state created to set-up the freeze. */ void cleanUp(SurfaceControl.Transaction t); } /** * Freezes container content by taking a screenshot. Because screenshots are heavy, usage of * any container "freeze" is currently explicit. WM code needs to be prudent about which * containers to freeze. */ @VisibleForTesting private class ScreenshotFreezer implements IContainerFreezer { /** Values are the screenshot "surfaces" or null if it was frozen via BLAST override. */ private final ArrayMap<WindowContainer, SurfaceControl> mSnapshots = new ArrayMap<>(); /** Takes a screenshot and puts it at the top of the container's surface. */ @Override public boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds) { if (!wc.isVisibleRequested()) return false; // Check if any parents have already been "frozen". If so, `wc` is already part of that // snapshot, so just skip it. for (WindowContainer p = wc; p != null; p = p.getParent()) { if (mSnapshots.containsKey(p)) return false; } if (mIsSeamlessRotation) { WindowState top = wc.getDisplayContent() == null ? null : wc.getDisplayContent().getDisplayPolicy().getTopFullscreenOpaqueWindow(); if (top != null && (top == wc || top.isDescendantOf(wc))) { // Don't use screenshots for seamless windows: these will use BLAST even if not // BLAST mode. mSnapshots.put(wc, null); return true; } } ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Screenshotting %s [%s]", wc.toString(), bounds.toString()); Rect cropBounds = new Rect(bounds); cropBounds.offsetTo(0, 0); SurfaceControl.LayerCaptureArgs captureArgs = new SurfaceControl.LayerCaptureArgs.Builder(wc.getSurfaceControl()) .setSourceCrop(cropBounds) .setCaptureSecureLayers(true) .setAllowProtected(true) .build(); SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = SurfaceControl.captureLayers(captureArgs); final HardwareBuffer buffer = screenshotBuffer == null ? null : screenshotBuffer.getHardwareBuffer(); if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) { // This can happen when display is not ready. Slog.w(TAG, "Failed to capture screenshot for " + wc); return false; } SurfaceControl snapshotSurface = wc.makeAnimationLeash() .setName("transition snapshot: " + wc.toString()) .setOpaque(true) .setParent(wc.getSurfaceControl()) .setSecure(screenshotBuffer.containsSecureLayers()) .setCallsite("Transition.ScreenshotSync") .setBLASTLayer() .build(); mSnapshots.put(wc, snapshotSurface); SurfaceControl.Transaction t = wc.mWmService.mTransactionFactory.get(); t.setBuffer(snapshotSurface, buffer); t.setDataSpace(snapshotSurface, screenshotBuffer.getColorSpace().getDataSpace()); t.show(snapshotSurface); // Place it on top of anything else in the container. t.setLayer(snapshotSurface, Integer.MAX_VALUE); t.apply(); t.close(); // Detach the screenshot on the sync transaction (the screenshot is just meant to // freeze the window until the sync transaction is applied (with all its other // corresponding changes), so this is how we unfreeze it. wc.getSyncTransaction().reparent(snapshotSurface, null /* newParent */); return true; } @Override public void cleanUp(SurfaceControl.Transaction t) { for (int i = 0; i < mSnapshots.size(); ++i) { SurfaceControl snap = mSnapshots.valueAt(i); // May be null if it was frozen via BLAST override. if (snap == null) continue; t.remove(snap); } } } } Loading
data/etc/services.core.protolog.json +12 −0 Original line number Diff line number Diff line Loading @@ -2647,6 +2647,12 @@ "group": "WM_DEBUG_ANIM", "at": "com\/android\/server\/wm\/WindowContainer.java" }, "390947100": { "message": "Screenshotting %s [%s]", "level": "VERBOSE", "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/Transition.java" }, "397382873": { "message": "Moving to PAUSED: %s %s", "level": "VERBOSE", Loading Loading @@ -4171,6 +4177,12 @@ "group": "WM_DEBUG_FOCUS_LIGHT", "at": "com\/android\/server\/wm\/InputMonitor.java" }, "2004282287": { "message": "Override sync-method for %s because seamless rotating", "level": "VERBOSE", "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/Transition.java" }, "2010476671": { "message": "Animation done in %s: reportedVisible=%b okToDisplay=%b okToAnimate=%b startingDisplayed=%b", "level": "VERBOSE", Loading
services/core/java/com/android/server/wm/BLASTSyncEngine.java +23 −9 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE; import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Trace; import android.util.ArraySet; import android.util.Slog; Loading Loading @@ -63,6 +64,15 @@ import java.util.ArrayList; class BLASTSyncEngine { private static final String TAG = "BLASTSyncEngine"; /** No specific method. Used by override specifiers. */ public static final int METHOD_UNDEFINED = -1; /** No sync method. Apps will draw/present internally and just report. */ public static final int METHOD_NONE = 0; /** Sync with BLAST. Apps will draw and then send the buffer to be applied in sync. */ public static final int METHOD_BLAST = 1; interface TransactionReadyListener { void onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction); } Loading @@ -85,6 +95,7 @@ class BLASTSyncEngine { */ class SyncGroup { final int mSyncId; final int mSyncMethod; final TransactionReadyListener mListener; final Runnable mOnTimeout; boolean mReady = false; Loading @@ -92,8 +103,9 @@ class BLASTSyncEngine { private SurfaceControl.Transaction mOrphanTransaction = null; private String mTraceName; private SyncGroup(TransactionReadyListener listener, int id, String name) { private SyncGroup(TransactionReadyListener listener, int id, String name, int method) { mSyncId = id; mSyncMethod = method; mListener = listener; mOnTimeout = () -> { Slog.w(TAG, "Sync group " + mSyncId + " timeout"); Loading Loading @@ -271,16 +283,13 @@ class BLASTSyncEngine { * Prepares a {@link SyncGroup} that is not active yet. Caller must call {@link #startSyncSet} * before calling {@link #addToSyncSet(int, WindowContainer)} on any {@link WindowContainer}. */ SyncGroup prepareSyncSet(TransactionReadyListener listener, String name) { return new SyncGroup(listener, mNextSyncId++, name); SyncGroup prepareSyncSet(TransactionReadyListener listener, String name, int method) { return new SyncGroup(listener, mNextSyncId++, name, method); } int startSyncSet(TransactionReadyListener listener) { return startSyncSet(listener, BLAST_TIMEOUT_DURATION, ""); } int startSyncSet(TransactionReadyListener listener, long timeoutMs, String name) { final SyncGroup s = prepareSyncSet(listener, name); int startSyncSet(TransactionReadyListener listener, long timeoutMs, String name, int method) { final SyncGroup s = prepareSyncSet(listener, name, method); startSyncSet(s, timeoutMs); return s.mSyncId; } Loading @@ -302,6 +311,11 @@ class BLASTSyncEngine { scheduleTimeout(s, timeoutMs); } @Nullable SyncGroup getSyncSet(int id) { return mActiveSyncs.get(id); } boolean hasActiveSync() { return mActiveSyncs.size() != 0; } Loading
services/core/java/com/android/server/wm/DisplayContent.java +8 −0 Original line number Diff line number Diff line Loading @@ -3304,6 +3304,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mAsyncRotationController.keepAppearanceInPreviousRotation(); } } else if (isRotationChanging()) { if (displayChange != null) { final boolean seamless = mDisplayRotation.shouldRotateSeamlessly( displayChange.getStartRotation(), displayChange.getEndRotation(), false /* forceUpdate */); if (seamless) { t.onSeamlessRotating(this); } } mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN); controller.mTransitionMetricsReporter.associate(t, startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN)); Loading
services/core/java/com/android/server/wm/Task.java +8 −3 Original line number Diff line number Diff line Loading @@ -1889,8 +1889,7 @@ class Task extends TaskFragment { } final int newWinMode = getWindowingMode(); if ((prevWinMode != newWinMode) && (mDisplayContent != null) && shouldStartChangeTransition(prevWinMode, newWinMode)) { if (shouldStartChangeTransition(prevWinMode, mTmpPrevBounds)) { initializeChangeTransition(mTmpPrevBounds); } Loading Loading @@ -2141,10 +2140,16 @@ class Task extends TaskFragment { bounds.offset(horizontalDiff, verticalDiff); } private boolean shouldStartChangeTransition(int prevWinMode, int newWinMode) { private boolean shouldStartChangeTransition(int prevWinMode, @NonNull Rect prevBounds) { if (!isLeafTask() || !canStartChangeTransition()) { return false; } final int newWinMode = getWindowingMode(); if (mTransitionController.inTransition(this)) { final Rect newBounds = getConfiguration().windowConfiguration.getBounds(); return prevWinMode != newWinMode || prevBounds.width() != newBounds.width() || prevBounds.height() != newBounds.height(); } // 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); Loading
services/core/java/com/android/server/wm/Transition.java +179 −2 Original line number Diff line number Diff line Loading @@ -68,6 +68,7 @@ import android.app.ActivityManager; import android.content.pm.ActivityInfo; import android.graphics.Point; import android.graphics.Rect; import android.hardware.HardwareBuffer; import android.os.Binder; import android.os.IBinder; import android.os.IRemoteCallback; Loading Loading @@ -205,6 +206,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe /** @see #setCanPipOnFinish */ private boolean mCanPipOnFinish = true; private boolean mIsSeamlessRotation = false; private IContainerFreezer mContainerFreezer = null; Transition(@TransitionType int type, @TransitionFlags int flags, TransitionController controller, BLASTSyncEngine syncEngine) { mType = type; Loading Loading @@ -265,10 +269,31 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe return mTargetDisplays.contains(dc); } /** Set a transition to be a seamless-rotation. */ void setSeamlessRotation(@NonNull WindowContainer wc) { final ChangeInfo info = mChanges.get(wc); if (info == null) return; info.mFlags = info.mFlags | ChangeInfo.FLAG_SEAMLESS_ROTATION; onSeamlessRotating(wc.getDisplayContent()); } /** * Called when it's been determined that this is transition is a seamless rotation. This should * be called before any WM changes have happened. */ void onSeamlessRotating(@NonNull DisplayContent dc) { // Don't need to do anything special if everything is using BLAST sync already. if (mSyncEngine.getSyncSet(mSyncId).mSyncMethod == BLASTSyncEngine.METHOD_BLAST) return; if (mContainerFreezer == null) { mContainerFreezer = new ScreenshotFreezer(); } mIsSeamlessRotation = true; final WindowState top = dc.getDisplayPolicy().getTopFullscreenOpaqueWindow(); if (top != null) { top.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST; ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Override sync-method for %s " + "because seamless rotating", top.getName()); } } /** Loading @@ -285,6 +310,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } } /** Only for testing. */ void setContainerFreezer(IContainerFreezer freezer) { mContainerFreezer = freezer; } @TransitionState int getState() { return mState; Loading Loading @@ -314,13 +344,18 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe return mState == STATE_COLLECTING || mState == STATE_STARTED; } /** Starts collecting phase. Once this starts, all relevant surface operations are sync. */ @VisibleForTesting void startCollecting(long timeoutMs) { startCollecting(timeoutMs, TransitionController.SYNC_METHOD); } /** Starts collecting phase. Once this starts, all relevant surface operations are sync. */ void startCollecting(long timeoutMs, int method) { if (mState != STATE_PENDING) { throw new IllegalStateException("Attempting to re-use a transition"); } mState = STATE_COLLECTING; mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG); mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG, method); mController.mTransitionTracer.logState(this); } Loading Loading @@ -414,6 +449,37 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mChanges.get(wc).mExistenceChanged = true; } /** * Records that a particular container is changing visibly (ie. something about it is changing * while it remains visible). This only effects windows that are already in the collecting * transition. */ void collectVisibleChange(WindowContainer wc) { if (mSyncEngine.getSyncSet(mSyncId).mSyncMethod == BLASTSyncEngine.METHOD_BLAST) { // All windows are synced already. return; } if (!isInTransition(wc)) return; if (mContainerFreezer == null) { mContainerFreezer = new ScreenshotFreezer(); } Transition.ChangeInfo change = mChanges.get(wc); if (change == null || !change.mVisible || !wc.isVisibleRequested()) return; // Note: many more tests have already been done by caller. mContainerFreezer.freeze(wc, change.mAbsoluteBounds); } /** * @return {@code true} if `wc` is a participant or is a descendant of one. */ boolean isInTransition(WindowContainer wc) { for (WindowContainer p = wc; p != null; p = p.getParent()) { if (mParticipants.contains(p)) return true; } return false; } /** * Specifies configuration change explicitly for the window container, so it can be chosen as * transition target. This is usually used with transition mode Loading Loading @@ -531,6 +597,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe displays.add(target.getDisplayContent()); } } // Remove screenshot layers if necessary if (mContainerFreezer != null) { mContainerFreezer.cleanUp(t); } // Need to update layers on involved displays since they were all paused while // the animation played. This puts the layers back into the correct order. mController.mBuildingFinishLayers = true; Loading Loading @@ -1982,4 +2052,111 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe return sortedTargets; } } /** * Interface for freezing a container's content during sync preparation. Really just one impl * but broken into an interface for testing (since you can't take screenshots in unit tests). */ interface IContainerFreezer { /** * Makes sure a particular window is "frozen" for the remainder of a sync. * * @return whether the freeze was successful. It fails if `wc` is already in a frozen window * or is not visible/ready. */ boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds); /** Populates `t` with operations that clean-up any state created to set-up the freeze. */ void cleanUp(SurfaceControl.Transaction t); } /** * Freezes container content by taking a screenshot. Because screenshots are heavy, usage of * any container "freeze" is currently explicit. WM code needs to be prudent about which * containers to freeze. */ @VisibleForTesting private class ScreenshotFreezer implements IContainerFreezer { /** Values are the screenshot "surfaces" or null if it was frozen via BLAST override. */ private final ArrayMap<WindowContainer, SurfaceControl> mSnapshots = new ArrayMap<>(); /** Takes a screenshot and puts it at the top of the container's surface. */ @Override public boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds) { if (!wc.isVisibleRequested()) return false; // Check if any parents have already been "frozen". If so, `wc` is already part of that // snapshot, so just skip it. for (WindowContainer p = wc; p != null; p = p.getParent()) { if (mSnapshots.containsKey(p)) return false; } if (mIsSeamlessRotation) { WindowState top = wc.getDisplayContent() == null ? null : wc.getDisplayContent().getDisplayPolicy().getTopFullscreenOpaqueWindow(); if (top != null && (top == wc || top.isDescendantOf(wc))) { // Don't use screenshots for seamless windows: these will use BLAST even if not // BLAST mode. mSnapshots.put(wc, null); return true; } } ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Screenshotting %s [%s]", wc.toString(), bounds.toString()); Rect cropBounds = new Rect(bounds); cropBounds.offsetTo(0, 0); SurfaceControl.LayerCaptureArgs captureArgs = new SurfaceControl.LayerCaptureArgs.Builder(wc.getSurfaceControl()) .setSourceCrop(cropBounds) .setCaptureSecureLayers(true) .setAllowProtected(true) .build(); SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = SurfaceControl.captureLayers(captureArgs); final HardwareBuffer buffer = screenshotBuffer == null ? null : screenshotBuffer.getHardwareBuffer(); if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) { // This can happen when display is not ready. Slog.w(TAG, "Failed to capture screenshot for " + wc); return false; } SurfaceControl snapshotSurface = wc.makeAnimationLeash() .setName("transition snapshot: " + wc.toString()) .setOpaque(true) .setParent(wc.getSurfaceControl()) .setSecure(screenshotBuffer.containsSecureLayers()) .setCallsite("Transition.ScreenshotSync") .setBLASTLayer() .build(); mSnapshots.put(wc, snapshotSurface); SurfaceControl.Transaction t = wc.mWmService.mTransactionFactory.get(); t.setBuffer(snapshotSurface, buffer); t.setDataSpace(snapshotSurface, screenshotBuffer.getColorSpace().getDataSpace()); t.show(snapshotSurface); // Place it on top of anything else in the container. t.setLayer(snapshotSurface, Integer.MAX_VALUE); t.apply(); t.close(); // Detach the screenshot on the sync transaction (the screenshot is just meant to // freeze the window until the sync transaction is applied (with all its other // corresponding changes), so this is how we unfreeze it. wc.getSyncTransaction().reparent(snapshotSurface, null /* newParent */); return true; } @Override public void cleanUp(SurfaceControl.Transaction t) { for (int i = 0; i < mSnapshots.size(); ++i) { SurfaceControl snap = mSnapshots.valueAt(i); // May be null if it was frozen via BLAST override. if (snap == null) continue; t.remove(snap); } } } }