Loading data/etc/services.core.protolog.json +24 −0 Original line number Diff line number Diff line Loading @@ -169,6 +169,12 @@ "group": "WM_DEBUG_WINDOW_ORGANIZER", "at": "com\/android\/server\/wm\/TaskOrganizerController.java" }, "-1961637874": { "message": "DeferredDisplayUpdater: applying DisplayInfo immediately", "level": "DEBUG", "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java" }, "-1949279037": { "message": "Attempted to add input method window with bad token %s. Aborting.", "level": "WARN", Loading Loading @@ -313,6 +319,12 @@ "group": "WM_DEBUG_RESIZE", "at": "com\/android\/server\/wm\/WindowState.java" }, "-1818910559": { "message": "DeferredDisplayUpdater: applied DisplayInfo after deferring", "level": "DEBUG", "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java" }, "-1814361639": { "message": "Set IME snapshot position: (%d, %d)", "level": "INFO", Loading Loading @@ -1909,6 +1921,12 @@ "group": "WM_DEBUG_FOCUS_LIGHT", "at": "com\/android\/server\/wm\/DisplayContent.java" }, "-415346336": { "message": "DeferredDisplayUpdater: partially applying DisplayInfo immediately", "level": "DEBUG", "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java" }, "-401282500": { "message": "destroyIfPossible: r=%s destroy returned removed=%s", "level": "DEBUG", Loading Loading @@ -1957,6 +1975,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS", "at": "com\/android\/server\/wm\/AppTransitionController.java" }, "-376950429": { "message": "DeferredDisplayUpdater: deferring DisplayInfo update", "level": "DEBUG", "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java" }, "-374767836": { "message": "setAppVisibility(%s, visible=%b): %s visible=%b mVisibleRequested=%b Callers=%s", "level": "VERBOSE", Loading libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +1 −2 Original line number Diff line number Diff line Loading @@ -50,7 +50,6 @@ import android.window.WindowContainerTransaction; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.activityembedding.ActivityEmbeddingController; import com.android.wm.shell.common.split.SplitScreenUtils; import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.pip.PipTransitionController; Loading Loading @@ -298,7 +297,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, mixed.mLeftoversHandler = handler.first; mActiveTransitions.add(mixed); return handler.second; } else if (mUnfoldHandler != null && mUnfoldHandler.hasUnfold(request)) { } else if (mUnfoldHandler != null && mUnfoldHandler.shouldPlayUnfoldAnimation(request)) { final WindowContainerTransaction wct = mUnfoldHandler.handleRequest(transition, request); if (wct != null) { Loading libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java +27 −5 Original line number Diff line number Diff line Loading @@ -106,7 +106,7 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull TransitionFinishCallback finishCallback) { if (hasUnfold(info) && transition != mTransition) { if (shouldPlayUnfoldAnimation(info) && transition != mTransition) { // Take over transition that has unfold, we might receive it if no other handler // accepted request in handleRequest, e.g. for rotation + unfold or // TRANSIT_NONE + unfold transitions Loading Loading @@ -213,14 +213,36 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene } /** Whether `request` contains an unfold action. */ public boolean hasUnfold(@NonNull TransitionRequestInfo request) { public boolean shouldPlayUnfoldAnimation(@NonNull TransitionRequestInfo request) { // Unfold animation won't play when animations are disabled if (!ValueAnimator.areAnimatorsEnabled()) return false; return (request.getType() == TRANSIT_CHANGE && request.getDisplayChange() != null && request.getDisplayChange().isPhysicalDisplayChanged()); && isUnfoldDisplayChange(request.getDisplayChange())); } private boolean isUnfoldDisplayChange( @NonNull TransitionRequestInfo.DisplayChange displayChange) { if (!displayChange.isPhysicalDisplayChanged()) { return false; } if (displayChange.getStartAbsBounds() == null || displayChange.getEndAbsBounds() == null) { return false; } // Handle only unfolding, currently we don't have an animation when folding final int endArea = displayChange.getEndAbsBounds().width() * displayChange.getEndAbsBounds().height(); final int startArea = displayChange.getStartAbsBounds().width() * displayChange.getStartAbsBounds().height(); return endArea > startArea; } /** Whether `transitionInfo` contains an unfold action. */ public boolean hasUnfold(@NonNull TransitionInfo transitionInfo) { public boolean shouldPlayUnfoldAnimation(@NonNull TransitionInfo transitionInfo) { // Unfold animation won't play when animations are disabled if (!ValueAnimator.areAnimatorsEnabled()) return false; Loading Loading @@ -250,7 +272,7 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene @Override public WindowContainerTransaction handleRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request) { if (hasUnfold(request)) { if (shouldPlayUnfoldAnimation(request)) { mTransition = transition; return new WindowContainerTransaction(); } Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java +26 −3 Original line number Diff line number Diff line Loading @@ -98,10 +98,13 @@ public class UnfoldTransitionHandlerTest { } @Test public void handleRequest_physicalDisplayChange_handlesTransition() { public void handleRequest_physicalDisplayChangeUnfold_handlesTransition() { ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo(); TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange( Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(true); Display.DEFAULT_DISPLAY) .setPhysicalDisplayChanged(true) .setStartAbsBounds(new Rect(0, 0, 100, 100)) .setEndAbsBounds(new Rect(0, 0, 200, 200)); TransitionRequestInfo requestInfo = new TransitionRequestInfo(TRANSIT_CHANGE, triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */); Loading @@ -111,6 +114,23 @@ public class UnfoldTransitionHandlerTest { assertThat(result).isNotNull(); } @Test public void handleRequest_physicalDisplayChangeFold_doesNotHandleTransition() { ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo(); TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange( Display.DEFAULT_DISPLAY) .setPhysicalDisplayChanged(true) .setStartAbsBounds(new Rect(0, 0, 200, 200)) .setEndAbsBounds(new Rect(0, 0, 100, 100)); TransitionRequestInfo requestInfo = new TransitionRequestInfo(TRANSIT_CHANGE, triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */); WindowContainerTransaction result = mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo); assertThat(result).isNull(); } @Test public void handleRequest_noPhysicalDisplayChange_doesNotHandleTransition() { ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo(); Loading Loading @@ -306,7 +326,10 @@ public class UnfoldTransitionHandlerTest { private TransitionRequestInfo createUnfoldTransitionRequestInfo() { ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo(); TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange( Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(true); Display.DEFAULT_DISPLAY) .setPhysicalDisplayChanged(true) .setStartAbsBounds(new Rect(0, 0, 100, 100)) .setEndAbsBounds(new Rect(0, 0, 200, 200)); return new TransitionRequestInfo(TRANSIT_CHANGE, triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */); } Loading services/core/java/com/android/server/wm/DeferredDisplayUpdater.java 0 → 100644 +345 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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 android.view.WindowManager.TRANSIT_CHANGE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS; import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY; import static com.android.server.wm.utils.DisplayInfoOverrides.WM_OVERRIDE_FIELDS; import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Rect; import android.view.DisplayInfo; import android.window.DisplayAreaInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.protolog.common.ProtoLog; import com.android.server.wm.utils.DisplayInfoOverrides.DisplayInfoFieldsUpdater; import java.util.Arrays; import java.util.Objects; /** * A DisplayUpdater that could defer and queue display updates coming from DisplayManager to * WindowManager. It allows to defer pending display updates if WindowManager is currently not * ready to apply them. * For example, this might happen if there is a Shell transition running and physical display * changed. We can't immediately apply the display updates because we want to start a separate * display change transition. In this case, we will queue all display updates until the current * transition's collection finishes and then apply them afterwards. */ public class DeferredDisplayUpdater implements DisplayUpdater { /** * List of fields that could be deferred before applying to DisplayContent. * This should be kept in sync with {@link DeferredDisplayUpdater#calculateDisplayInfoDiff} */ @VisibleForTesting static final DisplayInfoFieldsUpdater DEFERRABLE_FIELDS = (out, override) -> { // Treat unique id and address change as WM-specific display change as we re-query display // settings and parameters based on it which could cause window changes out.uniqueId = override.uniqueId; out.address = override.address; // Also apply WM-override fields, since they might produce differences in window hierarchy WM_OVERRIDE_FIELDS.setFields(out, override); }; private final DisplayContent mDisplayContent; @NonNull private final DisplayInfo mNonOverrideDisplayInfo = new DisplayInfo(); /** * The last known display parameters from DisplayManager, some WM-specific fields in this object * might not be applied to the DisplayContent yet */ @Nullable private DisplayInfo mLastDisplayInfo; /** * The last DisplayInfo that was applied to DisplayContent, only WM-specific parameters must be * used from this object. This object is used to store old values of DisplayInfo while these * fields are pending to be applied to DisplayContent. */ @Nullable private DisplayInfo mLastWmDisplayInfo; @NonNull private final DisplayInfo mOutputDisplayInfo = new DisplayInfo(); public DeferredDisplayUpdater(@NonNull DisplayContent displayContent) { mDisplayContent = displayContent; mNonOverrideDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo()); } /** * Reads the latest display parameters from the display manager and returns them in a callback. * If there are pending display updates, it will wait for them to finish first and only then it * will call the callback with the latest display parameters. * * @param finishCallback is called when all pending display updates are finished */ @Override public void updateDisplayInfo(@NonNull Runnable finishCallback) { // Get the latest display parameters from the DisplayManager final DisplayInfo displayInfo = getCurrentDisplayInfo(); final int displayInfoDiff = calculateDisplayInfoDiff(mLastDisplayInfo, displayInfo); final boolean physicalDisplayUpdated = isPhysicalDisplayUpdated(mLastDisplayInfo, displayInfo); mLastDisplayInfo = displayInfo; // Apply whole display info immediately as is if either: // * it is the first display update // * shell transitions are disabled or temporary unavailable if (displayInfoDiff == DIFF_EVERYTHING || !mDisplayContent.mTransitionController.isShellTransitionsEnabled()) { ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS, "DeferredDisplayUpdater: applying DisplayInfo immediately"); mLastWmDisplayInfo = displayInfo; applyLatestDisplayInfo(); finishCallback.run(); return; } // If there are non WM-specific display info changes, apply only these fields immediately if ((displayInfoDiff & DIFF_NOT_WM_DEFERRABLE) > 0) { ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS, "DeferredDisplayUpdater: partially applying DisplayInfo immediately"); applyLatestDisplayInfo(); } // If there are WM-specific display info changes, apply them through a Shell transition if ((displayInfoDiff & DIFF_WM_DEFERRABLE) > 0) { ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS, "DeferredDisplayUpdater: deferring DisplayInfo update"); requestDisplayChangeTransition(physicalDisplayUpdated, () -> { // Apply deferrable fields to DisplayContent only when the transition // starts collecting, non-deferrable fields are ignored in mLastWmDisplayInfo mLastWmDisplayInfo = displayInfo; applyLatestDisplayInfo(); finishCallback.run(); }); } else { // There are no WM-specific updates, so we can immediately notify that all display // info changes are applied finishCallback.run(); } } /** * Requests a display change Shell transition * * @param physicalDisplayUpdated if true also starts remote display change * @param onStartCollect called when the Shell transition starts collecting */ private void requestDisplayChangeTransition(boolean physicalDisplayUpdated, @NonNull Runnable onStartCollect) { final Transition transition = new Transition(TRANSIT_CHANGE, /* flags= */ 0, mDisplayContent.mTransitionController, mDisplayContent.mTransitionController.mSyncEngine); mDisplayContent.mAtmService.startPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY); mDisplayContent.mTransitionController.startCollectOrQueue(transition, deferred -> { final Rect startBounds = new Rect(0, 0, mDisplayContent.mInitialDisplayWidth, mDisplayContent.mInitialDisplayHeight); final int fromRotation = mDisplayContent.getRotation(); onStartCollect.run(); ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS, "DeferredDisplayUpdater: applied DisplayInfo after deferring"); if (physicalDisplayUpdated) { onDisplayUpdated(transition, fromRotation, startBounds); } else { transition.setAllReady(); } }); } /** * Applies current DisplayInfo to DisplayContent, DisplayContent is merged from two parts: * - non-deferrable fields are set from the most recent values received from DisplayManager * (uses {@link mLastDisplayInfo} field) * - deferrable fields are set from the latest values that we could apply to WM * (uses {@link mLastWmDisplayInfo} field) */ private void applyLatestDisplayInfo() { copyDisplayInfoFields(mOutputDisplayInfo, /* base= */ mLastDisplayInfo, /* override= */ mLastWmDisplayInfo, /* fields= */ DEFERRABLE_FIELDS); mDisplayContent.onDisplayInfoUpdated(mOutputDisplayInfo); } @NonNull private DisplayInfo getCurrentDisplayInfo() { mDisplayContent.mWmService.mDisplayManagerInternal.getNonOverrideDisplayInfo( mDisplayContent.mDisplayId, mNonOverrideDisplayInfo); return new DisplayInfo(mNonOverrideDisplayInfo); } /** * Called when physical display is updated, this could happen e.g. on foldable * devices when the physical underlying display is replaced. This method should be called * when the new display info is already applied to the WM hierarchy. * * @param fromRotation rotation before the display change * @param startBounds display bounds before the display change */ private void onDisplayUpdated(@NonNull Transition transition, int fromRotation, @NonNull Rect startBounds) { final Rect endBounds = new Rect(0, 0, mDisplayContent.mInitialDisplayWidth, mDisplayContent.mInitialDisplayHeight); final int toRotation = mDisplayContent.getRotation(); final TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(mDisplayContent.getDisplayId()); displayChange.setStartAbsBounds(startBounds); displayChange.setEndAbsBounds(endBounds); displayChange.setStartRotation(fromRotation); displayChange.setEndRotation(toRotation); displayChange.setPhysicalDisplayChanged(true); mDisplayContent.mTransitionController.requestStartTransition(transition, /* startTask= */ null, /* remoteTransition= */ null, displayChange); final DisplayAreaInfo newDisplayAreaInfo = mDisplayContent.getDisplayAreaInfo(); final boolean startedRemoteChange = mDisplayContent.mRemoteDisplayChangeController .performRemoteDisplayChange(fromRotation, toRotation, newDisplayAreaInfo, transaction -> finishDisplayUpdate(transaction, transition)); if (!startedRemoteChange) { finishDisplayUpdate(/* wct= */ null, transition); } } private void finishDisplayUpdate(@Nullable WindowContainerTransaction wct, @NonNull Transition transition) { if (wct != null) { mDisplayContent.mAtmService.mWindowOrganizerController.applyTransaction( wct); } transition.setAllReady(); } private boolean isPhysicalDisplayUpdated(@Nullable DisplayInfo first, @Nullable DisplayInfo second) { if (first == null || second == null) return true; return !Objects.equals(first.uniqueId, second.uniqueId); } /** * Diff result: fields are the same */ static final int DIFF_NONE = 0; /** * Diff result: fields that could be deferred in WM are different */ static final int DIFF_WM_DEFERRABLE = 1 << 0; /** * Diff result: fields that could not be deferred in WM are different */ static final int DIFF_NOT_WM_DEFERRABLE = 1 << 1; /** * Diff result: everything is different */ static final int DIFF_EVERYTHING = 0XFFFFFFFF; @VisibleForTesting static int calculateDisplayInfoDiff(@Nullable DisplayInfo first, @Nullable DisplayInfo second) { int diff = DIFF_NONE; if (Objects.equals(first, second)) return diff; if (first == null || second == null) return DIFF_EVERYTHING; if (first.layerStack != second.layerStack || first.flags != second.flags || first.type != second.type || first.displayId != second.displayId || first.displayGroupId != second.displayGroupId || !Objects.equals(first.deviceProductInfo, second.deviceProductInfo) || first.modeId != second.modeId || first.renderFrameRate != second.renderFrameRate || first.defaultModeId != second.defaultModeId || first.userPreferredModeId != second.userPreferredModeId || !Arrays.equals(first.supportedModes, second.supportedModes) || first.colorMode != second.colorMode || !Arrays.equals(first.supportedColorModes, second.supportedColorModes) || !Objects.equals(first.hdrCapabilities, second.hdrCapabilities) || !Arrays.equals(first.userDisabledHdrTypes, second.userDisabledHdrTypes) || first.minimalPostProcessingSupported != second.minimalPostProcessingSupported || first.appVsyncOffsetNanos != second.appVsyncOffsetNanos || first.presentationDeadlineNanos != second.presentationDeadlineNanos || first.state != second.state || first.committedState != second.committedState || first.ownerUid != second.ownerUid || !Objects.equals(first.ownerPackageName, second.ownerPackageName) || first.removeMode != second.removeMode || first.getRefreshRate() != second.getRefreshRate() || first.brightnessMinimum != second.brightnessMinimum || first.brightnessMaximum != second.brightnessMaximum || first.brightnessDefault != second.brightnessDefault || first.installOrientation != second.installOrientation || !Objects.equals(first.layoutLimitedRefreshRate, second.layoutLimitedRefreshRate) || !BrightnessSynchronizer.floatEquals(first.hdrSdrRatio, second.hdrSdrRatio) || !first.thermalRefreshRateThrottling.contentEquals( second.thermalRefreshRateThrottling) || !Objects.equals(first.thermalBrightnessThrottlingDataId, second.thermalBrightnessThrottlingDataId)) { diff |= DIFF_NOT_WM_DEFERRABLE; } if (first.appWidth != second.appWidth || first.appHeight != second.appHeight || first.smallestNominalAppWidth != second.smallestNominalAppWidth || first.smallestNominalAppHeight != second.smallestNominalAppHeight || first.largestNominalAppWidth != second.largestNominalAppWidth || first.largestNominalAppHeight != second.largestNominalAppHeight || first.logicalWidth != second.logicalWidth || first.logicalHeight != second.logicalHeight || first.physicalXDpi != second.physicalXDpi || first.physicalYDpi != second.physicalYDpi || first.rotation != second.rotation || !Objects.equals(first.displayCutout, second.displayCutout) || first.logicalDensityDpi != second.logicalDensityDpi || !Objects.equals(first.roundedCorners, second.roundedCorners) || !Objects.equals(first.displayShape, second.displayShape) || !Objects.equals(first.uniqueId, second.uniqueId) || !Objects.equals(first.address, second.address) ) { diff |= DIFF_WM_DEFERRABLE; } return diff; } } Loading
data/etc/services.core.protolog.json +24 −0 Original line number Diff line number Diff line Loading @@ -169,6 +169,12 @@ "group": "WM_DEBUG_WINDOW_ORGANIZER", "at": "com\/android\/server\/wm\/TaskOrganizerController.java" }, "-1961637874": { "message": "DeferredDisplayUpdater: applying DisplayInfo immediately", "level": "DEBUG", "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java" }, "-1949279037": { "message": "Attempted to add input method window with bad token %s. Aborting.", "level": "WARN", Loading Loading @@ -313,6 +319,12 @@ "group": "WM_DEBUG_RESIZE", "at": "com\/android\/server\/wm\/WindowState.java" }, "-1818910559": { "message": "DeferredDisplayUpdater: applied DisplayInfo after deferring", "level": "DEBUG", "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java" }, "-1814361639": { "message": "Set IME snapshot position: (%d, %d)", "level": "INFO", Loading Loading @@ -1909,6 +1921,12 @@ "group": "WM_DEBUG_FOCUS_LIGHT", "at": "com\/android\/server\/wm\/DisplayContent.java" }, "-415346336": { "message": "DeferredDisplayUpdater: partially applying DisplayInfo immediately", "level": "DEBUG", "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java" }, "-401282500": { "message": "destroyIfPossible: r=%s destroy returned removed=%s", "level": "DEBUG", Loading Loading @@ -1957,6 +1975,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS", "at": "com\/android\/server\/wm\/AppTransitionController.java" }, "-376950429": { "message": "DeferredDisplayUpdater: deferring DisplayInfo update", "level": "DEBUG", "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java" }, "-374767836": { "message": "setAppVisibility(%s, visible=%b): %s visible=%b mVisibleRequested=%b Callers=%s", "level": "VERBOSE", Loading
libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +1 −2 Original line number Diff line number Diff line Loading @@ -50,7 +50,6 @@ import android.window.WindowContainerTransaction; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.activityembedding.ActivityEmbeddingController; import com.android.wm.shell.common.split.SplitScreenUtils; import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.pip.PipTransitionController; Loading Loading @@ -298,7 +297,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, mixed.mLeftoversHandler = handler.first; mActiveTransitions.add(mixed); return handler.second; } else if (mUnfoldHandler != null && mUnfoldHandler.hasUnfold(request)) { } else if (mUnfoldHandler != null && mUnfoldHandler.shouldPlayUnfoldAnimation(request)) { final WindowContainerTransaction wct = mUnfoldHandler.handleRequest(transition, request); if (wct != null) { Loading
libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java +27 −5 Original line number Diff line number Diff line Loading @@ -106,7 +106,7 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull TransitionFinishCallback finishCallback) { if (hasUnfold(info) && transition != mTransition) { if (shouldPlayUnfoldAnimation(info) && transition != mTransition) { // Take over transition that has unfold, we might receive it if no other handler // accepted request in handleRequest, e.g. for rotation + unfold or // TRANSIT_NONE + unfold transitions Loading Loading @@ -213,14 +213,36 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene } /** Whether `request` contains an unfold action. */ public boolean hasUnfold(@NonNull TransitionRequestInfo request) { public boolean shouldPlayUnfoldAnimation(@NonNull TransitionRequestInfo request) { // Unfold animation won't play when animations are disabled if (!ValueAnimator.areAnimatorsEnabled()) return false; return (request.getType() == TRANSIT_CHANGE && request.getDisplayChange() != null && request.getDisplayChange().isPhysicalDisplayChanged()); && isUnfoldDisplayChange(request.getDisplayChange())); } private boolean isUnfoldDisplayChange( @NonNull TransitionRequestInfo.DisplayChange displayChange) { if (!displayChange.isPhysicalDisplayChanged()) { return false; } if (displayChange.getStartAbsBounds() == null || displayChange.getEndAbsBounds() == null) { return false; } // Handle only unfolding, currently we don't have an animation when folding final int endArea = displayChange.getEndAbsBounds().width() * displayChange.getEndAbsBounds().height(); final int startArea = displayChange.getStartAbsBounds().width() * displayChange.getStartAbsBounds().height(); return endArea > startArea; } /** Whether `transitionInfo` contains an unfold action. */ public boolean hasUnfold(@NonNull TransitionInfo transitionInfo) { public boolean shouldPlayUnfoldAnimation(@NonNull TransitionInfo transitionInfo) { // Unfold animation won't play when animations are disabled if (!ValueAnimator.areAnimatorsEnabled()) return false; Loading Loading @@ -250,7 +272,7 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene @Override public WindowContainerTransaction handleRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request) { if (hasUnfold(request)) { if (shouldPlayUnfoldAnimation(request)) { mTransition = transition; return new WindowContainerTransaction(); } Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java +26 −3 Original line number Diff line number Diff line Loading @@ -98,10 +98,13 @@ public class UnfoldTransitionHandlerTest { } @Test public void handleRequest_physicalDisplayChange_handlesTransition() { public void handleRequest_physicalDisplayChangeUnfold_handlesTransition() { ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo(); TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange( Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(true); Display.DEFAULT_DISPLAY) .setPhysicalDisplayChanged(true) .setStartAbsBounds(new Rect(0, 0, 100, 100)) .setEndAbsBounds(new Rect(0, 0, 200, 200)); TransitionRequestInfo requestInfo = new TransitionRequestInfo(TRANSIT_CHANGE, triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */); Loading @@ -111,6 +114,23 @@ public class UnfoldTransitionHandlerTest { assertThat(result).isNotNull(); } @Test public void handleRequest_physicalDisplayChangeFold_doesNotHandleTransition() { ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo(); TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange( Display.DEFAULT_DISPLAY) .setPhysicalDisplayChanged(true) .setStartAbsBounds(new Rect(0, 0, 200, 200)) .setEndAbsBounds(new Rect(0, 0, 100, 100)); TransitionRequestInfo requestInfo = new TransitionRequestInfo(TRANSIT_CHANGE, triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */); WindowContainerTransaction result = mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo); assertThat(result).isNull(); } @Test public void handleRequest_noPhysicalDisplayChange_doesNotHandleTransition() { ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo(); Loading Loading @@ -306,7 +326,10 @@ public class UnfoldTransitionHandlerTest { private TransitionRequestInfo createUnfoldTransitionRequestInfo() { ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo(); TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange( Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(true); Display.DEFAULT_DISPLAY) .setPhysicalDisplayChanged(true) .setStartAbsBounds(new Rect(0, 0, 100, 100)) .setEndAbsBounds(new Rect(0, 0, 200, 200)); return new TransitionRequestInfo(TRANSIT_CHANGE, triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */); } Loading
services/core/java/com/android/server/wm/DeferredDisplayUpdater.java 0 → 100644 +345 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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 android.view.WindowManager.TRANSIT_CHANGE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS; import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY; import static com.android.server.wm.utils.DisplayInfoOverrides.WM_OVERRIDE_FIELDS; import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Rect; import android.view.DisplayInfo; import android.window.DisplayAreaInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.protolog.common.ProtoLog; import com.android.server.wm.utils.DisplayInfoOverrides.DisplayInfoFieldsUpdater; import java.util.Arrays; import java.util.Objects; /** * A DisplayUpdater that could defer and queue display updates coming from DisplayManager to * WindowManager. It allows to defer pending display updates if WindowManager is currently not * ready to apply them. * For example, this might happen if there is a Shell transition running and physical display * changed. We can't immediately apply the display updates because we want to start a separate * display change transition. In this case, we will queue all display updates until the current * transition's collection finishes and then apply them afterwards. */ public class DeferredDisplayUpdater implements DisplayUpdater { /** * List of fields that could be deferred before applying to DisplayContent. * This should be kept in sync with {@link DeferredDisplayUpdater#calculateDisplayInfoDiff} */ @VisibleForTesting static final DisplayInfoFieldsUpdater DEFERRABLE_FIELDS = (out, override) -> { // Treat unique id and address change as WM-specific display change as we re-query display // settings and parameters based on it which could cause window changes out.uniqueId = override.uniqueId; out.address = override.address; // Also apply WM-override fields, since they might produce differences in window hierarchy WM_OVERRIDE_FIELDS.setFields(out, override); }; private final DisplayContent mDisplayContent; @NonNull private final DisplayInfo mNonOverrideDisplayInfo = new DisplayInfo(); /** * The last known display parameters from DisplayManager, some WM-specific fields in this object * might not be applied to the DisplayContent yet */ @Nullable private DisplayInfo mLastDisplayInfo; /** * The last DisplayInfo that was applied to DisplayContent, only WM-specific parameters must be * used from this object. This object is used to store old values of DisplayInfo while these * fields are pending to be applied to DisplayContent. */ @Nullable private DisplayInfo mLastWmDisplayInfo; @NonNull private final DisplayInfo mOutputDisplayInfo = new DisplayInfo(); public DeferredDisplayUpdater(@NonNull DisplayContent displayContent) { mDisplayContent = displayContent; mNonOverrideDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo()); } /** * Reads the latest display parameters from the display manager and returns them in a callback. * If there are pending display updates, it will wait for them to finish first and only then it * will call the callback with the latest display parameters. * * @param finishCallback is called when all pending display updates are finished */ @Override public void updateDisplayInfo(@NonNull Runnable finishCallback) { // Get the latest display parameters from the DisplayManager final DisplayInfo displayInfo = getCurrentDisplayInfo(); final int displayInfoDiff = calculateDisplayInfoDiff(mLastDisplayInfo, displayInfo); final boolean physicalDisplayUpdated = isPhysicalDisplayUpdated(mLastDisplayInfo, displayInfo); mLastDisplayInfo = displayInfo; // Apply whole display info immediately as is if either: // * it is the first display update // * shell transitions are disabled or temporary unavailable if (displayInfoDiff == DIFF_EVERYTHING || !mDisplayContent.mTransitionController.isShellTransitionsEnabled()) { ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS, "DeferredDisplayUpdater: applying DisplayInfo immediately"); mLastWmDisplayInfo = displayInfo; applyLatestDisplayInfo(); finishCallback.run(); return; } // If there are non WM-specific display info changes, apply only these fields immediately if ((displayInfoDiff & DIFF_NOT_WM_DEFERRABLE) > 0) { ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS, "DeferredDisplayUpdater: partially applying DisplayInfo immediately"); applyLatestDisplayInfo(); } // If there are WM-specific display info changes, apply them through a Shell transition if ((displayInfoDiff & DIFF_WM_DEFERRABLE) > 0) { ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS, "DeferredDisplayUpdater: deferring DisplayInfo update"); requestDisplayChangeTransition(physicalDisplayUpdated, () -> { // Apply deferrable fields to DisplayContent only when the transition // starts collecting, non-deferrable fields are ignored in mLastWmDisplayInfo mLastWmDisplayInfo = displayInfo; applyLatestDisplayInfo(); finishCallback.run(); }); } else { // There are no WM-specific updates, so we can immediately notify that all display // info changes are applied finishCallback.run(); } } /** * Requests a display change Shell transition * * @param physicalDisplayUpdated if true also starts remote display change * @param onStartCollect called when the Shell transition starts collecting */ private void requestDisplayChangeTransition(boolean physicalDisplayUpdated, @NonNull Runnable onStartCollect) { final Transition transition = new Transition(TRANSIT_CHANGE, /* flags= */ 0, mDisplayContent.mTransitionController, mDisplayContent.mTransitionController.mSyncEngine); mDisplayContent.mAtmService.startPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY); mDisplayContent.mTransitionController.startCollectOrQueue(transition, deferred -> { final Rect startBounds = new Rect(0, 0, mDisplayContent.mInitialDisplayWidth, mDisplayContent.mInitialDisplayHeight); final int fromRotation = mDisplayContent.getRotation(); onStartCollect.run(); ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS, "DeferredDisplayUpdater: applied DisplayInfo after deferring"); if (physicalDisplayUpdated) { onDisplayUpdated(transition, fromRotation, startBounds); } else { transition.setAllReady(); } }); } /** * Applies current DisplayInfo to DisplayContent, DisplayContent is merged from two parts: * - non-deferrable fields are set from the most recent values received from DisplayManager * (uses {@link mLastDisplayInfo} field) * - deferrable fields are set from the latest values that we could apply to WM * (uses {@link mLastWmDisplayInfo} field) */ private void applyLatestDisplayInfo() { copyDisplayInfoFields(mOutputDisplayInfo, /* base= */ mLastDisplayInfo, /* override= */ mLastWmDisplayInfo, /* fields= */ DEFERRABLE_FIELDS); mDisplayContent.onDisplayInfoUpdated(mOutputDisplayInfo); } @NonNull private DisplayInfo getCurrentDisplayInfo() { mDisplayContent.mWmService.mDisplayManagerInternal.getNonOverrideDisplayInfo( mDisplayContent.mDisplayId, mNonOverrideDisplayInfo); return new DisplayInfo(mNonOverrideDisplayInfo); } /** * Called when physical display is updated, this could happen e.g. on foldable * devices when the physical underlying display is replaced. This method should be called * when the new display info is already applied to the WM hierarchy. * * @param fromRotation rotation before the display change * @param startBounds display bounds before the display change */ private void onDisplayUpdated(@NonNull Transition transition, int fromRotation, @NonNull Rect startBounds) { final Rect endBounds = new Rect(0, 0, mDisplayContent.mInitialDisplayWidth, mDisplayContent.mInitialDisplayHeight); final int toRotation = mDisplayContent.getRotation(); final TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(mDisplayContent.getDisplayId()); displayChange.setStartAbsBounds(startBounds); displayChange.setEndAbsBounds(endBounds); displayChange.setStartRotation(fromRotation); displayChange.setEndRotation(toRotation); displayChange.setPhysicalDisplayChanged(true); mDisplayContent.mTransitionController.requestStartTransition(transition, /* startTask= */ null, /* remoteTransition= */ null, displayChange); final DisplayAreaInfo newDisplayAreaInfo = mDisplayContent.getDisplayAreaInfo(); final boolean startedRemoteChange = mDisplayContent.mRemoteDisplayChangeController .performRemoteDisplayChange(fromRotation, toRotation, newDisplayAreaInfo, transaction -> finishDisplayUpdate(transaction, transition)); if (!startedRemoteChange) { finishDisplayUpdate(/* wct= */ null, transition); } } private void finishDisplayUpdate(@Nullable WindowContainerTransaction wct, @NonNull Transition transition) { if (wct != null) { mDisplayContent.mAtmService.mWindowOrganizerController.applyTransaction( wct); } transition.setAllReady(); } private boolean isPhysicalDisplayUpdated(@Nullable DisplayInfo first, @Nullable DisplayInfo second) { if (first == null || second == null) return true; return !Objects.equals(first.uniqueId, second.uniqueId); } /** * Diff result: fields are the same */ static final int DIFF_NONE = 0; /** * Diff result: fields that could be deferred in WM are different */ static final int DIFF_WM_DEFERRABLE = 1 << 0; /** * Diff result: fields that could not be deferred in WM are different */ static final int DIFF_NOT_WM_DEFERRABLE = 1 << 1; /** * Diff result: everything is different */ static final int DIFF_EVERYTHING = 0XFFFFFFFF; @VisibleForTesting static int calculateDisplayInfoDiff(@Nullable DisplayInfo first, @Nullable DisplayInfo second) { int diff = DIFF_NONE; if (Objects.equals(first, second)) return diff; if (first == null || second == null) return DIFF_EVERYTHING; if (first.layerStack != second.layerStack || first.flags != second.flags || first.type != second.type || first.displayId != second.displayId || first.displayGroupId != second.displayGroupId || !Objects.equals(first.deviceProductInfo, second.deviceProductInfo) || first.modeId != second.modeId || first.renderFrameRate != second.renderFrameRate || first.defaultModeId != second.defaultModeId || first.userPreferredModeId != second.userPreferredModeId || !Arrays.equals(first.supportedModes, second.supportedModes) || first.colorMode != second.colorMode || !Arrays.equals(first.supportedColorModes, second.supportedColorModes) || !Objects.equals(first.hdrCapabilities, second.hdrCapabilities) || !Arrays.equals(first.userDisabledHdrTypes, second.userDisabledHdrTypes) || first.minimalPostProcessingSupported != second.minimalPostProcessingSupported || first.appVsyncOffsetNanos != second.appVsyncOffsetNanos || first.presentationDeadlineNanos != second.presentationDeadlineNanos || first.state != second.state || first.committedState != second.committedState || first.ownerUid != second.ownerUid || !Objects.equals(first.ownerPackageName, second.ownerPackageName) || first.removeMode != second.removeMode || first.getRefreshRate() != second.getRefreshRate() || first.brightnessMinimum != second.brightnessMinimum || first.brightnessMaximum != second.brightnessMaximum || first.brightnessDefault != second.brightnessDefault || first.installOrientation != second.installOrientation || !Objects.equals(first.layoutLimitedRefreshRate, second.layoutLimitedRefreshRate) || !BrightnessSynchronizer.floatEquals(first.hdrSdrRatio, second.hdrSdrRatio) || !first.thermalRefreshRateThrottling.contentEquals( second.thermalRefreshRateThrottling) || !Objects.equals(first.thermalBrightnessThrottlingDataId, second.thermalBrightnessThrottlingDataId)) { diff |= DIFF_NOT_WM_DEFERRABLE; } if (first.appWidth != second.appWidth || first.appHeight != second.appHeight || first.smallestNominalAppWidth != second.smallestNominalAppWidth || first.smallestNominalAppHeight != second.smallestNominalAppHeight || first.largestNominalAppWidth != second.largestNominalAppWidth || first.largestNominalAppHeight != second.largestNominalAppHeight || first.logicalWidth != second.logicalWidth || first.logicalHeight != second.logicalHeight || first.physicalXDpi != second.physicalXDpi || first.physicalYDpi != second.physicalYDpi || first.rotation != second.rotation || !Objects.equals(first.displayCutout, second.displayCutout) || first.logicalDensityDpi != second.logicalDensityDpi || !Objects.equals(first.roundedCorners, second.roundedCorners) || !Objects.equals(first.displayShape, second.displayShape) || !Objects.equals(first.uniqueId, second.uniqueId) || !Objects.equals(first.address, second.address) ) { diff |= DIFF_WM_DEFERRABLE; } return diff; } }