Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a8291245 authored by Mariia Sandrikova's avatar Mariia Sandrikova
Browse files

Freeze live wallpaper for letterbox background.

With this change, WallpaperController sends "freeze" / "unfreeze" commands when the app window is letterboxed / not letterboxed indicating that:
- "freeze": a screenshot of the wallpaper is taken by WallpaperServices and put on top of the main surface while the wallpaper host is notified through onVisibilityChanged(false) callback that it's no longer visible.
- "unfreeze": a screenshot of the wallpaper is removed and the wallpaper host is notified through onVisibilityChanged(true) callback if the wallpaper is visible again (e.g., if transitioned from an app to the Home screen).

Logic isn't applied to static wallpapers.

When wallpaper size changes while being frozen, the screenshot is scaled and cropped to adjust to the new size.

Fix: 177884751
Test: Manual, atest WallpaperServiceTest, WallpaperServiceTests
Change-Id: If0300478cdccc07d226117e01ce3d43aa0b432a0
parent a8813c12
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -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
+179 −1
Original line number Diff line number Diff line
@@ -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;
@@ -199,6 +204,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() {
            {
@@ -1344,11 +1357,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;
@@ -1365,6 +1382,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();
                    }
                }
            }
        }
@@ -1825,6 +1846,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 {
@@ -1839,6 +1863,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;
@@ -2161,6 +2338,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?
+32 −9
Original line number Diff line number Diff line
@@ -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,6 +429,14 @@ class WallpaperController {
    Bundle sendWindowWallpaperCommand(
            WindowState window, String action, int x, int y, int z, Bundle extras, boolean sync) {
        if (window == mWallpaperTarget || window == mPrevWallpaperTarget) {
            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);
@@ -435,9 +448,6 @@ class WallpaperController {
        }
    }

        return null;
    }

    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;
        }
    }
}
+5 −2
Original line number Diff line number Diff line
@@ -6045,8 +6045,11 @@ class WindowState extends WindowContainer<WindowState> 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();
    }

    /**