From 7ea6094f3b057fb1243608746c0abf561f5dd1be Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Fri, 8 Jul 2022 19:06:17 +0800 Subject: [PATCH] Fix flickering when rapidly switch resolutions - The screen decor layers are moved out from display due to a memory regression issue(b/235601833). So, the SC.captureLayers will no longer be able to include the screen decor layers. - We now use SC.captureDisplay instead for resolution chagne case. - Previously, we hide the screen decor layers while animation is running and show them back when the animation ends, but it would cause flicker if user rapidly switch resolution. Because the previous ScreenRotationAnimation.kill() can be called after creating a new ScreenRotatioAnimation, then the screen decor layers hidden by the current animation are shown unexpectedly. Example: 1. Anim1 start (hide decor layers) 2. Anim2 start (hide decor layers) 3. Anim1 end (show decor layers) -> flicker 4. Anim2 end (show decor layers) - Only re-show the screen decor layers when current animation is the last one. Bug: 237354783 Test: 1. Go Settings > Display > Screen resolution 2. Rapidly switch resolutions Change-Id: Ia42eb8538a07e49c0ddcd6d48470a1afc8caae5a --- .../com/android/server/wm/DisplayContent.java | 7 +- .../server/wm/ScreenRotationAnimation.java | 69 ++++++++++++++++--- 2 files changed, 62 insertions(+), 14 deletions(-) diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 72b996c4662d1..79aef00750a12 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -3233,10 +3233,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } public void setRotationAnimation(ScreenRotationAnimation screenRotationAnimation) { - if (mScreenRotationAnimation != null) { - mScreenRotationAnimation.kill(); - } + final ScreenRotationAnimation prev = mScreenRotationAnimation; mScreenRotationAnimation = screenRotationAnimation; + if (prev != null) { + prev.kill(); + } // Hide the windows which are not significant in rotation animation. So that the windows // don't need to block the unfreeze time. diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java index bee44def1512d..f31a3cc9bcb3e 100644 --- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java @@ -40,9 +40,11 @@ import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; import android.hardware.HardwareBuffer; +import android.os.IBinder; import android.os.Trace; import android.util.Slog; import android.util.proto.ProtoOutputStream; +import android.view.DisplayAddress; import android.view.DisplayInfo; import android.view.Surface; import android.view.Surface.OutOfResourcesException; @@ -165,18 +167,43 @@ class ScreenRotationAnimation { final SurfaceControl.Transaction t = mService.mTransactionFactory.get(); try { - SurfaceControl.LayerCaptureArgs.Builder builder = - new SurfaceControl.LayerCaptureArgs.Builder(displayContent.getSurfaceControl()) - .setCaptureSecureLayers(true) - .setAllowProtected(true) - .setSourceCrop(new Rect(0, 0, width, height)); - + final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer; if (isSizeChanged) { + final DisplayAddress address = displayInfo.address; + if (!(address instanceof DisplayAddress.Physical)) { + Slog.e(TAG, "Display does not have a physical address: " + displayId); + return; + } + final DisplayAddress.Physical physicalAddress = + (DisplayAddress.Physical) address; + final IBinder displayToken = SurfaceControl.getPhysicalDisplayToken( + physicalAddress.getPhysicalDisplayId()); + if (displayToken == null) { + Slog.e(TAG, "Display token is null."); + return; + } + // Temporarily not skip screenshot for the rounded corner overlays and screenshot + // the whole display to include the rounded corner overlays. + setSkipScreenshotForRoundedCornerOverlays(false, t); mRoundedCornerOverlay = displayContent.findRoundedCornerOverlays(); + final SurfaceControl.DisplayCaptureArgs captureArgs = + new SurfaceControl.DisplayCaptureArgs.Builder(displayToken) + .setSourceCrop(new Rect(0, 0, width, height)) + .setAllowProtected(true) + .setCaptureSecureLayers(true) + .build(); + screenshotBuffer = SurfaceControl.captureDisplay(captureArgs); + } else { + SurfaceControl.LayerCaptureArgs captureArgs = + new SurfaceControl.LayerCaptureArgs.Builder( + displayContent.getSurfaceControl()) + .setCaptureSecureLayers(true) + .setAllowProtected(true) + .setSourceCrop(new Rect(0, 0, width, height)) + .build(); + screenshotBuffer = SurfaceControl.captureLayers(captureArgs); } - SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = - SurfaceControl.captureLayers(builder.build()); if (screenshotBuffer == null) { Slog.w(TAG, "Unable to take screenshot of display " + displayId); return; @@ -275,6 +302,21 @@ class ScreenRotationAnimation { t.apply(); } + void setSkipScreenshotForRoundedCornerOverlays( + boolean skipScreenshot, SurfaceControl.Transaction t) { + mDisplayContent.forAllWindows(w -> { + if (!w.mToken.mRoundedCornerOverlay || !w.isVisible() || !w.mWinAnimator.hasSurface()) { + return; + } + t.setSkipScreenshot(w.mWinAnimator.mSurfaceController.mSurfaceControl, skipScreenshot); + }, false); + if (!skipScreenshot) { + // Use sync apply to apply the change immediately, so that the next + // SC.captureDisplay can capture the screen decor layers. + t.apply(true /* sync */); + } + } + public void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); proto.write(STARTED, mStarted); @@ -483,10 +525,15 @@ class ScreenRotationAnimation { } mBackColorSurface = null; } + if (mRoundedCornerOverlay != null) { - for (SurfaceControl sc : mRoundedCornerOverlay) { - if (sc.isValid()) { - t.show(sc); + if (mDisplayContent.getRotationAnimation() == null + || mDisplayContent.getRotationAnimation() == this) { + setSkipScreenshotForRoundedCornerOverlays(true, t); + for (SurfaceControl sc : mRoundedCornerOverlay) { + if (sc.isValid()) { + t.show(sc); + } } } mRoundedCornerOverlay = null;