diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 8d332ab1d58b5..edfbf1a29f5af 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -220,6 +220,20 @@ public class WallpaperManager { */ public static final String COMMAND_REAPPLY = "android.wallpaper.reapply"; + /** + * Command for {@link #sendWallpaperCommand}: reported when the live wallpaper needs to be + * frozen. + * @hide + */ + public static final String COMMAND_FREEZE = "android.wallpaper.freeze"; + + /** + * Command for {@link #sendWallpaperCommand}: reported when the live wallapper doesn't need + * to be frozen anymore. + * @hide + */ + public static final String COMMAND_UNFREEZE = "android.wallpaper.unfreeze"; + /** * Extra passed back from setWallpaper() giving the new wallpaper's assigned ID. * @hide diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index a3e328da5cb90..4bf60495f3afd 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -16,6 +16,8 @@ package android.service.wallpaper; +import static android.app.WallpaperManager.COMMAND_FREEZE; +import static android.app.WallpaperManager.COMMAND_UNFREEZE; import static android.graphics.Matrix.MSCALE_X; import static android.graphics.Matrix.MSCALE_Y; import static android.graphics.Matrix.MSKEW_X; @@ -42,12 +44,14 @@ import android.content.res.TypedArray; import android.graphics.BLASTBufferQueue; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.GraphicBuffer; import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; +import android.hardware.HardwareBuffer; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.os.Build; @@ -56,6 +60,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; @@ -200,6 +205,12 @@ public abstract class WallpaperService extends Service { boolean mVisible; boolean mReportedVisible; boolean mDestroyed; + // Set to true after receiving WallpaperManager#COMMAND_FREEZE. It's reset back to false + // after receiving WallpaperManager#COMMAND_UNFREEZE. COMMAND_FREEZE is fully applied once + // mScreenshotSurfaceControl isn't null. When this happens, then Engine is notified through + // doVisibilityChanged that main wallpaper surface is no longer visible and the wallpaper + // host receives onVisibilityChanged(false) callback. + private boolean mFrozenRequested = false; // Current window state. boolean mCreated; @@ -264,6 +275,8 @@ public abstract class WallpaperService extends Service { SurfaceControl mSurfaceControl = new SurfaceControl(); SurfaceControl mBbqSurfaceControl; BLASTBufferQueue mBlastBufferQueue; + private SurfaceControl mScreenshotSurfaceControl; + private Point mScreenshotSize = new Point(); final BaseSurfaceHolder mSurfaceHolder = new BaseSurfaceHolder() { { @@ -1349,11 +1362,15 @@ public abstract class WallpaperService extends Service { if (!mDestroyed) { mVisible = visible; reportVisibility(); - if (visible) processLocalColors(mPendingXOffset, mPendingXOffsetStep); + if (mReportedVisible) processLocalColors(mPendingXOffset, mPendingXOffsetStep); } } void reportVisibility() { + if (mScreenshotSurfaceControl != null && mVisible) { + if (DEBUG) Log.v(TAG, "Frozen so don't report visibility change"); + return; + } if (!mDestroyed) { mDisplayState = mDisplay == null ? Display.STATE_UNKNOWN : mDisplay.getState(); boolean visible = mVisible && mDisplayState != Display.STATE_OFF; @@ -1370,6 +1387,10 @@ public abstract class WallpaperService extends Service { updateSurface(true, false, false); } onVisibilityChanged(visible); + if (mReportedVisible && mFrozenRequested) { + if (DEBUG) Log.v(TAG, "Freezing wallpaper after visibility update"); + freeze(); + } } } } @@ -1830,6 +1851,9 @@ public abstract class WallpaperService extends Service { void doCommand(WallpaperCommand cmd) { Bundle result; if (!mDestroyed) { + if (COMMAND_FREEZE.equals(cmd.action) || COMMAND_UNFREEZE.equals(cmd.action)) { + updateFrozenState(/* frozenRequested= */ !COMMAND_UNFREEZE.equals(cmd.action)); + } result = onCommand(cmd.action, cmd.x, cmd.y, cmd.z, cmd.extras, cmd.sync); } else { @@ -1844,6 +1868,159 @@ public abstract class WallpaperService extends Service { } } + private void updateFrozenState(boolean frozenRequested) { + if (mIWallpaperEngine.mWallpaperManager.getWallpaperInfo() == null + // Procees the unfreeze command in case the wallaper became static while + // being paused. + && frozenRequested) { + if (DEBUG) Log.v(TAG, "Ignoring the freeze command for static wallpapers"); + return; + } + mFrozenRequested = frozenRequested; + boolean isFrozen = mScreenshotSurfaceControl != null; + if (mFrozenRequested == isFrozen) { + return; + } + if (mFrozenRequested) { + freeze(); + } else { + unfreeze(); + } + } + + private void freeze() { + if (!mReportedVisible || mDestroyed) { + // Screenshot can't be taken until visibility is reported to the wallpaper host. + return; + } + if (!showScreenshotOfWallpaper()) { + return; + } + // Prevent a wallpaper host from rendering wallpaper behind a screeshot. + doVisibilityChanged(false); + // Remember that visibility is requested since it's not guaranteed that + // mWindow#dispatchAppVisibility will be called when letterboxed application with + // wallpaper background transitions to the Home screen. + mVisible = true; + } + + private void unfreeze() { + cleanUpScreenshotSurfaceControl(); + if (mVisible) { + doVisibilityChanged(true); + } + } + + private void cleanUpScreenshotSurfaceControl() { + // TODO(b/194399558): Add crossfade transition. + if (mScreenshotSurfaceControl != null) { + new SurfaceControl.Transaction() + .remove(mScreenshotSurfaceControl) + .show(mBbqSurfaceControl) + .apply(); + mScreenshotSurfaceControl = null; + } + } + + void scaleAndCropScreenshot() { + if (mScreenshotSurfaceControl == null) { + return; + } + if (mScreenshotSize.x <= 0 || mScreenshotSize.y <= 0) { + Log.w(TAG, "Unexpected screenshot size: " + mScreenshotSize); + return; + } + // Don't scale down and using the same scaling factor for both dimensions to + // avoid stretching wallpaper image. + float scaleFactor = Math.max(1, Math.max( + ((float) mSurfaceSize.x) / mScreenshotSize.x, + ((float) mSurfaceSize.y) / mScreenshotSize.y)); + int diffX = ((int) (mScreenshotSize.x * scaleFactor)) - mSurfaceSize.x; + int diffY = ((int) (mScreenshotSize.y * scaleFactor)) - mSurfaceSize.y; + if (DEBUG) { + Log.v(TAG, "Adjusting screenshot: scaleFactor=" + scaleFactor + + " diffX=" + diffX + " diffY=" + diffY + " mSurfaceSize=" + mSurfaceSize + + " mScreenshotSize=" + mScreenshotSize); + } + new SurfaceControl.Transaction() + .setMatrix( + mScreenshotSurfaceControl, + /* dsdx= */ scaleFactor, /* dtdx= */ 0, + /* dtdy= */ 0, /* dsdy= */ scaleFactor) + .setWindowCrop( + mScreenshotSurfaceControl, + new Rect( + /* left= */ diffX / 2, + /* top= */ diffY / 2, + /* right= */ diffX / 2 + mScreenshotSize.x, + /* bottom= */ diffY / 2 + mScreenshotSize.y)) + .setPosition(mScreenshotSurfaceControl, -diffX / 2, -diffY / 2) + .apply(); + } + + private boolean showScreenshotOfWallpaper() { + if (mDestroyed || mSurfaceControl == null || !mSurfaceControl.isValid()) { + if (DEBUG) Log.v(TAG, "Failed to screenshot wallpaper: surface isn't valid"); + return false; + } + + final Rect bounds = new Rect(0, 0, mSurfaceSize.x, mSurfaceSize.y); + if (bounds.isEmpty()) { + Log.w(TAG, "Failed to screenshot wallpaper: surface bounds are empty"); + return false; + } + + if (mScreenshotSurfaceControl != null) { + Log.e(TAG, "Screenshot is unexpectedly not null"); + // Destroying previous screenshot since it can have different size. + cleanUpScreenshotSurfaceControl(); + } + + SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = + SurfaceControl.captureLayers( + new SurfaceControl.LayerCaptureArgs.Builder(mSurfaceControl) + // Needed because SurfaceFlinger#validateScreenshotPermissions + // uses this parameter to check whether a caller only attempts + // to screenshot itself when call doesn't come from the system. + .setUid(Process.myUid()) + .setChildrenOnly(false) + .setSourceCrop(bounds) + .build()); + + if (screenshotBuffer == null) { + Log.w(TAG, "Failed to screenshot wallpaper: screenshotBuffer is null"); + return false; + } + + final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer(); + + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + + // TODO(b/194399558): Add crossfade transition. + mScreenshotSurfaceControl = new SurfaceControl.Builder() + .setName("Wallpaper snapshot for engine " + this) + .setFormat(hardwareBuffer.getFormat()) + .setParent(mSurfaceControl) + .setSecure(screenshotBuffer.containsSecureLayers()) + .setCallsite("WallpaperService.Engine.showScreenshotOfWallpaper") + .setBLASTLayer() + .build(); + + mScreenshotSize.set(mSurfaceSize.x, mSurfaceSize.y); + + GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(hardwareBuffer); + + t.setBuffer(mScreenshotSurfaceControl, graphicBuffer); + t.setColorSpace(mScreenshotSurfaceControl, screenshotBuffer.getColorSpace()); + // Place on top everything else. + t.setLayer(mScreenshotSurfaceControl, Integer.MAX_VALUE); + t.show(mScreenshotSurfaceControl); + t.hide(mBbqSurfaceControl); + t.apply(); + + return true; + } + void reportSurfaceDestroyed() { if (mSurfaceCreated) { mSurfaceCreated = false; @@ -2166,6 +2343,7 @@ public abstract class WallpaperService extends Service { final boolean reportDraw = message.arg1 != 0; mEngine.updateSurface(true, false, reportDraw); mEngine.doOffsetsChanged(true); + mEngine.scaleAndCropScreenshot(); } break; case MSG_WINDOW_MOVED: { // Do nothing. What does it mean for a Wallpaper to move? diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 95e5fc2e6b274..0f61f1af98bdf 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -16,6 +16,8 @@ package com.android.server.wm; +import static android.app.WallpaperManager.COMMAND_FREEZE; +import static android.app.WallpaperManager.COMMAND_UNFREEZE; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; @@ -79,6 +81,8 @@ class WallpaperController { private int mLastWallpaperDisplayOffsetX = Integer.MIN_VALUE; private int mLastWallpaperDisplayOffsetY = Integer.MIN_VALUE; private final float mMaxWallpaperScale; + // Whether COMMAND_FREEZE was dispatched. + private boolean mLastFrozen = false; // This is set when we are waiting for a wallpaper to tell us it is done // changing its scroll position. @@ -194,6 +198,7 @@ class WallpaperController { if (DEBUG_WALLPAPER) Slog.v(TAG, "Win " + w + ": token animating, looking behind."); } + mFindResults.setIsWallpaperTargetForLetterbox(w.hasWallpaperForLetterboxBackground()); // Found a target! End search. return true; } @@ -424,20 +429,25 @@ class WallpaperController { Bundle sendWindowWallpaperCommand( WindowState window, String action, int x, int y, int z, Bundle extras, boolean sync) { if (window == mWallpaperTarget || window == mPrevWallpaperTarget) { - boolean doWait = sync; - for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) { - final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx); - token.sendWindowWallpaperCommand(action, x, y, z, extras, sync); - } - - if (doWait) { - // TODO: Need to wait for result. - } + sendWindowWallpaperCommand(action, x, y, z, extras, sync); } return null; } + private void sendWindowWallpaperCommand( + String action, int x, int y, int z, Bundle extras, boolean sync) { + boolean doWait = sync; + for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) { + final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx); + token.sendWindowWallpaperCommand(action, x, y, z, extras, sync); + } + + if (doWait) { + // TODO: Need to wait for result. + } + } + private void updateWallpaperOffsetLocked(WindowState changingTarget, boolean sync) { WindowState target = mWallpaperTarget; if (target != null) { @@ -641,6 +651,13 @@ class WallpaperController { updateWallpaperTokens(visible); + if (visible && mLastFrozen != mFindResults.isWallpaperTargetForLetterbox) { + mLastFrozen = mFindResults.isWallpaperTargetForLetterbox; + sendWindowWallpaperCommand( + mFindResults.isWallpaperTargetForLetterbox ? COMMAND_FREEZE : COMMAND_UNFREEZE, + /* x= */ 0, /* y= */ 0, /* z= */ 0, /* extras= */ null, /* sync= */ false); + } + if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG, "New wallpaper: target=" + mWallpaperTarget + " prev=" + mPrevWallpaperTarget); } @@ -838,6 +855,7 @@ class WallpaperController { boolean useTopWallpaperAsTarget = false; WindowState wallpaperTarget = null; boolean resetTopWallpaper = false; + boolean isWallpaperTargetForLetterbox = false; void setTopWallpaper(WindowState win) { topWallpaper = win; @@ -851,11 +869,16 @@ class WallpaperController { useTopWallpaperAsTarget = topWallpaperAsTarget; } + void setIsWallpaperTargetForLetterbox(boolean isWallpaperTargetForLetterbox) { + this.isWallpaperTargetForLetterbox = isWallpaperTargetForLetterbox; + } + void reset() { topWallpaper = null; wallpaperTarget = null; useTopWallpaperAsTarget = false; resetTopWallpaper = false; + isWallpaperTargetForLetterbox = false; } } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index c9b15062c708f..72d1b49cd0d72 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -6043,8 +6043,11 @@ class WindowState extends WindowContainer implements WindowManagerP } boolean hasWallpaper() { - return (mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0 - || (mActivityRecord != null && mActivityRecord.hasWallpaperBackgroudForLetterbox()); + return (mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0 || hasWallpaperForLetterboxBackground(); + } + + boolean hasWallpaperForLetterboxBackground() { + return mActivityRecord != null && mActivityRecord.hasWallpaperBackgroudForLetterbox(); } /**