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

Commit 5ee61a54 authored by Aurélien Pomini's avatar Aurélien Pomini Committed by Automerger Merge Worker
Browse files

Merge "Rewrite thread logic, add tests and add color extraction for...

Merge "Rewrite thread logic, add tests and add color extraction for CanvasEngine" into tm-qpr-dev am: 8fa46d14

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20053087



Change-Id: I9eb5f8dae372ec7ca0cde0def3ee16a739930fba
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents d204bb7c 8fa46d14
Loading
Loading
Loading
Loading
+297 −166
Original line number Diff line number Diff line
@@ -16,21 +16,17 @@

package com.android.systemui.wallpapers;

import static android.view.Display.DEFAULT_DISPLAY;

import static com.android.systemui.flags.Flags.USE_CANVAS_RENDERER;

import android.app.WallpaperColors;
import android.app.WallpaperManager;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
@@ -40,8 +36,6 @@ import android.util.ArraySet;
import android.util.Log;
import android.util.MathUtils;
import android.util.Size;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.WindowManager;
@@ -49,8 +43,11 @@ import android.view.WindowManager;
import androidx.annotation.NonNull;

import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.wallpapers.canvas.ImageCanvasWallpaperRenderer;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.wallpapers.canvas.WallpaperColorExtractor;
import com.android.systemui.wallpapers.gl.EglHelper;
import com.android.systemui.wallpapers.gl.ImageWallpaperRenderer;

@@ -59,6 +56,7 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;

import javax.inject.Inject;

@@ -78,15 +76,28 @@ public class ImageWallpaper extends WallpaperService {
    private final ArrayList<RectF> mLocalColorsToAdd = new ArrayList<>();
    private final ArraySet<RectF> mColorAreas = new ArraySet<>();
    private volatile int mPages = 1;
    private boolean mPagesComputed = false;
    private HandlerThread mWorker;
    // scaled down version
    private Bitmap mMiniBitmap;
    private final FeatureFlags mFeatureFlags;

    // used in canvasEngine to load/unload the bitmap and extract the colors
    @Background
    private final DelayableExecutor mBackgroundExecutor;
    private static final int DELAY_UNLOAD_BITMAP = 2000;

    @Main
    private final Executor mMainExecutor;

    @Inject
    public ImageWallpaper(FeatureFlags featureFlags) {
    public ImageWallpaper(FeatureFlags featureFlags,
            @Background DelayableExecutor backgroundExecutor,
            @Main Executor mainExecutor) {
        super();
        mFeatureFlags = featureFlags;
        mBackgroundExecutor = backgroundExecutor;
        mMainExecutor = mainExecutor;
    }

    @Override
@@ -339,7 +350,6 @@ public class ImageWallpaper extends WallpaperService {
                imgArea.left = 0;
                imgArea.right = 1;
            }

            return imgArea;
        }

@@ -510,69 +520,84 @@ public class ImageWallpaper extends WallpaperService {


    class CanvasEngine extends WallpaperService.Engine implements DisplayListener {

        // time [ms] before unloading the wallpaper after it is loaded
        private static final int DELAY_FORGET_WALLPAPER = 5000;

        private final Runnable mUnloadWallpaperCallback = this::unloadWallpaper;

        private WallpaperManager mWallpaperManager;
        private ImageCanvasWallpaperRenderer mImageCanvasWallpaperRenderer;
        private final WallpaperColorExtractor mWallpaperColorExtractor;
        private SurfaceHolder mSurfaceHolder;
        @VisibleForTesting
        static final int MIN_SURFACE_WIDTH = 128;
        @VisibleForTesting
        static final int MIN_SURFACE_HEIGHT = 128;
        private Bitmap mBitmap;

        private Display mDisplay;
        private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();

        private AsyncTask<Void, Void, Bitmap> mLoader;
        private boolean mNeedsDrawAfterLoadingWallpaper = false;
        /*
         * Counter to unload the bitmap as soon as possible.
         * Before any bitmap operation, this is incremented.
         * After an operation completion, this is decremented (synchronously),
         * and if the count is 0, unload the bitmap
         */
        private int mBitmapUsages = 0;
        private final Object mLock = new Object();

        CanvasEngine() {
            super();
            setFixedSizeAllowed(true);
            setShowForAllUsers(true);
            mWallpaperColorExtractor = new WallpaperColorExtractor(
                    mBackgroundExecutor,
                    new WallpaperColorExtractor.WallpaperColorExtractorCallback() {
                        @Override
                        public void onColorsProcessed(List<RectF> regions,
                                List<WallpaperColors> colors) {
                            CanvasEngine.this.onColorsProcessed(regions, colors);
                        }

        void trimMemory(int level) {
            if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW
                    && level <= ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL
                    && isBitmapLoaded()) {
                if (DEBUG) {
                    Log.d(TAG, "trimMemory");
                        @Override
                        public void onMiniBitmapUpdated() {
                            CanvasEngine.this.onMiniBitmapUpdated();
                        }
                unloadWallpaper();

                        @Override
                        public void onActivated() {
                            setOffsetNotificationsEnabled(true);
                        }

                        @Override
                        public void onDeactivated() {
                            setOffsetNotificationsEnabled(false);
                        }
                    });

            // if the number of pages is already computed, transmit it to the color extractor
            if (mPagesComputed) {
                mWallpaperColorExtractor.onPageChanged(mPages);
            }
        }

        @Override
        public void onCreate(SurfaceHolder surfaceHolder) {
            Trace.beginSection("ImageWallpaper.CanvasEngine#onCreate");
            if (DEBUG) {
                Log.d(TAG, "onCreate");
            }
            mWallpaperManager = getDisplayContext().getSystemService(WallpaperManager.class);
            mSurfaceHolder = surfaceHolder;
            Rect dimensions = mWallpaperManager.peekBitmapDimensions();
            int width = Math.max(MIN_SURFACE_WIDTH, dimensions.width());
            int height = Math.max(MIN_SURFACE_HEIGHT, dimensions.height());
            mSurfaceHolder.setFixedSize(width, height);

            mWallpaperManager = getSystemService(WallpaperManager.class);
            super.onCreate(surfaceHolder);

            final Context displayContext = getDisplayContext();
            final int displayId = displayContext == null ? DEFAULT_DISPLAY :
                    displayContext.getDisplayId();
            DisplayManager dm = getSystemService(DisplayManager.class);
            if (dm != null) {
                mDisplay = dm.getDisplay(displayId);
                if (mDisplay == null) {
                    Log.e(TAG, "Cannot find display! Fallback to default.");
                    mDisplay = dm.getDisplay(DEFAULT_DISPLAY);
                }
            }
            setOffsetNotificationsEnabled(false);

            mImageCanvasWallpaperRenderer = new ImageCanvasWallpaperRenderer(surfaceHolder);
            loadWallpaper(false);
            getDisplayContext().getSystemService(DisplayManager.class)
                    .registerDisplayListener(this, null);
            getDisplaySizeAndUpdateColorExtractor();
            Trace.endSection();
        }

        @Override
        public void onDestroy() {
            super.onDestroy();
            unloadWallpaper();
            getDisplayContext().getSystemService(DisplayManager.class)
                    .unregisterDisplayListener(this);
            mWallpaperColorExtractor.cleanUp();
            unloadBitmap();
        }

        @Override
@@ -580,32 +605,31 @@ public class ImageWallpaper extends WallpaperService {
            return true;
        }

        @Override
        public boolean shouldWaitForEngineShown() {
            return true;
        }

        @Override
        public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            if (DEBUG) {
                Log.d(TAG, "onSurfaceChanged: width=" + width + ", height=" + height);
            }
            super.onSurfaceChanged(holder, format, width, height);
            mImageCanvasWallpaperRenderer.setSurfaceHolder(holder);
            drawFrame(false);
        }

        @Override
        public void onSurfaceDestroyed(SurfaceHolder holder) {
            super.onSurfaceDestroyed(holder);
            if (DEBUG) {
                Log.i(TAG, "onSurfaceDestroyed");
            }
            mImageCanvasWallpaperRenderer.setSurfaceHolder(null);
            mSurfaceHolder = null;
        }

        @Override
        public void onSurfaceCreated(SurfaceHolder holder) {
            super.onSurfaceCreated(holder);
            if (DEBUG) {
                Log.i(TAG, "onSurfaceCreated");
            }
            mImageCanvasWallpaperRenderer.setSurfaceHolder(holder);
        }

        @Override
@@ -613,149 +637,223 @@ public class ImageWallpaper extends WallpaperService {
            if (DEBUG) {
                Log.d(TAG, "onSurfaceRedrawNeeded");
            }
            super.onSurfaceRedrawNeeded(holder);
            // At the end of this method we should have drawn into the surface.
            // This means that the bitmap should be loaded synchronously if
            // it was already unloaded.
            if (!isBitmapLoaded()) {
                setBitmap(mWallpaperManager.getBitmap(true /* hardware */));
            drawFrame();
        }
            drawFrame(true);

        private void drawFrame() {
            mBackgroundExecutor.execute(this::drawFrameSynchronized);
        }

        private DisplayInfo getDisplayInfo() {
            mDisplay.getDisplayInfo(mTmpDisplayInfo);
            return mTmpDisplayInfo;
        private void drawFrameSynchronized() {
            synchronized (mLock) {
                drawFrameInternal();
            }
        }

        private void drawFrame(boolean forceRedraw) {
            if (!mImageCanvasWallpaperRenderer.isSurfaceHolderLoaded()) {
        private void drawFrameInternal() {
            if (mSurfaceHolder == null) {
                Log.e(TAG, "attempt to draw a frame without a valid surface");
                return;
            }

            // load the wallpaper if not already done
            if (!isBitmapLoaded()) {
                // ensure that we load the wallpaper.
                // if the wallpaper is currently loading, this call will have no effect.
                loadWallpaper(true);
                return;
                loadWallpaperAndDrawFrameInternal();
            } else {
                mBitmapUsages++;

                // drawing is done on the main thread
                mMainExecutor.execute(() -> {
                    drawFrameOnCanvas(mBitmap);
                    reportEngineShown(false);
                    unloadBitmapIfNotUsed();
                });
            }
            mImageCanvasWallpaperRenderer.drawFrame(mBitmap, forceRedraw);
        }

        private void setBitmap(Bitmap bitmap) {
            if (bitmap == null) {
                Log.e(TAG, "Attempt to set a null bitmap");
            } else if (mBitmap == bitmap) {
                Log.e(TAG, "The value of bitmap is the same");
            } else if (bitmap.getWidth() < 1 || bitmap.getHeight() < 1) {
                Log.e(TAG, "Attempt to set an invalid wallpaper of length "
                        + bitmap.getWidth() + "x" + bitmap.getHeight());
            } else {
                if (mBitmap != null) {
                    mBitmap.recycle();
        @VisibleForTesting
        void drawFrameOnCanvas(Bitmap bitmap) {
            Trace.beginSection("ImageWallpaper.CanvasEngine#drawFrame");
            // TODO change SurfaceHolder API to add wcg support
            Canvas c = mSurfaceHolder.lockHardwareCanvas();
            if (c != null) {
                Rect dest = mSurfaceHolder.getSurfaceFrame();
                try {
                    c.drawBitmap(bitmap, null, dest, null);
                } finally {
                    mSurfaceHolder.unlockCanvasAndPost(c);
                }
                mBitmap = bitmap;
            }
            Trace.endSection();
        }

        private boolean isBitmapLoaded() {
        @VisibleForTesting
        boolean isBitmapLoaded() {
            return mBitmap != null && !mBitmap.isRecycled();
        }

        /**
         * Loads the wallpaper on background thread and schedules updating the surface frame,
         * and if {@code needsDraw} is set also draws a frame.
         *
         * If loading is already in-flight, subsequent loads are ignored (but needDraw is or-ed to
         * the active request).
         *
         */
        private void loadWallpaper(boolean needsDraw) {
            mNeedsDrawAfterLoadingWallpaper |= needsDraw;
            if (mLoader != null) {
                if (DEBUG) {
                    Log.d(TAG, "Skipping loadWallpaper, already in flight ");
        private void unloadBitmapIfNotUsed() {
            mBackgroundExecutor.execute(this::unloadBitmapIfNotUsedSynchronized);
        }
                return;

        private void unloadBitmapIfNotUsedSynchronized() {
            synchronized (mLock) {
                mBitmapUsages -= 1;
                if (mBitmapUsages <= 0) {
                    mBitmapUsages = 0;
                    unloadBitmapInternal();
                }
            mLoader = new AsyncTask<Void, Void, Bitmap>() {
                @Override
                protected Bitmap doInBackground(Void... params) {
                    Throwable exception;
                    try {
                        Bitmap wallpaper = mWallpaperManager.getBitmap(true /* hardware */);
                        if (wallpaper != null
                                && wallpaper.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) {
                            throw new RuntimeException("Wallpaper is too large to draw!");
            }
                        return wallpaper;
                    } catch (RuntimeException | OutOfMemoryError e) {
                        exception = e;
        }

                    if (isCancelled()) {
                        return null;
        private void unloadBitmap() {
            mBackgroundExecutor.execute(this::unloadBitmapSynchronized);
        }

        private void unloadBitmapSynchronized() {
            synchronized (mLock) {
                mBitmapUsages = 0;
                unloadBitmapInternal();
            }
        }

        private void unloadBitmapInternal() {
            Trace.beginSection("ImageWallpaper.CanvasEngine#unloadBitmap");
            if (mBitmap != null) {
                mBitmap.recycle();
            }
            mBitmap = null;

            final Surface surface = getSurfaceHolder().getSurface();
            surface.hwuiDestroy();
            mWallpaperManager.forgetLoadedWallpaper();
            Trace.endSection();
        }

        private void loadWallpaperAndDrawFrameInternal() {
            Trace.beginSection("ImageWallpaper.CanvasEngine#loadWallpaper");
            boolean loadSuccess = false;
            Bitmap bitmap;
            try {
                bitmap = mWallpaperManager.getBitmap(false);
                if (bitmap != null
                        && bitmap.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) {
                    throw new RuntimeException("Wallpaper is too large to draw!");
                }
            } catch (RuntimeException | OutOfMemoryError exception) {

                // Note that if we do fail at this, and the default wallpaper can't
                // be loaded, we will go into a cycle. Don't do a build where the
                // default wallpaper can't be loaded.
                Log.w(TAG, "Unable to load wallpaper!", exception);
                try {
                        mWallpaperManager.clear();
                    mWallpaperManager.clear(WallpaperManager.FLAG_SYSTEM);
                } catch (IOException ex) {
                    // now we're really screwed.
                    Log.w(TAG, "Unable reset to default wallpaper!", ex);
                }

                    if (isCancelled()) {
                        return null;
                    }

                try {
                        return mWallpaperManager.getBitmap(true /* hardware */);
                    bitmap = mWallpaperManager.getBitmap(false);
                } catch (RuntimeException | OutOfMemoryError e) {
                    Log.w(TAG, "Unable to load default wallpaper!", e);
                    bitmap = null;
                }
                    return null;
            }

                @Override
                protected void onPostExecute(Bitmap bitmap) {
                    setBitmap(bitmap);
            if (bitmap == null) {
                Log.w(TAG, "Could not load bitmap");
            } else if (bitmap.isRecycled()) {
                Log.e(TAG, "Attempt to load a recycled bitmap");
            } else if (mBitmap == bitmap) {
                Log.e(TAG, "Loaded a bitmap that was already loaded");
            } else if (bitmap.getWidth() < 1 || bitmap.getHeight() < 1) {
                Log.e(TAG, "Attempt to load an invalid wallpaper of length "
                        + bitmap.getWidth() + "x" + bitmap.getHeight());
            } else {
                // at this point, loading is done correctly.
                loadSuccess = true;
                // recycle the previously loaded bitmap
                if (mBitmap != null) {
                    mBitmap.recycle();
                }
                mBitmap = bitmap;

                // +2 usages for the color extraction and the delayed unload.
                mBitmapUsages += 2;
                recomputeColorExtractorMiniBitmap();
                drawFrameInternal();

                    if (mNeedsDrawAfterLoadingWallpaper) {
                        drawFrame(true);
                /*
                 * after loading, the bitmap will be unloaded after all these conditions:
                 *   - the frame is redrawn
                 *   - the mini bitmap from color extractor is recomputed
                 *   - the DELAY_UNLOAD_BITMAP has passed
                 */
                mBackgroundExecutor.executeDelayed(
                        this::unloadBitmapIfNotUsedSynchronized, DELAY_UNLOAD_BITMAP);
            }
            // even if the bitmap cannot be loaded, call reportEngineShown
            if (!loadSuccess) reportEngineShown(false);
            Trace.endSection();
        }

                    mLoader = null;
                    mNeedsDrawAfterLoadingWallpaper = false;
                    scheduleUnloadWallpaper();
        private void onColorsProcessed(List<RectF> regions, List<WallpaperColors> colors) {
            try {
                notifyLocalColorsChanged(regions, colors);
            } catch (RuntimeException e) {
                Log.e(TAG, e.getMessage(), e);
            }
            }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        }

        private void unloadWallpaper() {
            if (mLoader != null) {
                mLoader.cancel(false);
                mLoader = null;
        @VisibleForTesting
        void recomputeColorExtractorMiniBitmap() {
            mWallpaperColorExtractor.onBitmapChanged(mBitmap);
        }

            if (mBitmap != null) {
                mBitmap.recycle();
        @VisibleForTesting
        void onMiniBitmapUpdated() {
            unloadBitmapIfNotUsed();
        }
            mBitmap = null;

            final Surface surface = getSurfaceHolder().getSurface();
            surface.hwuiDestroy();
            mWallpaperManager.forgetLoadedWallpaper();
        @Override
        public boolean supportsLocalColorExtraction() {
            return true;
        }

        @Override
        public void addLocalColorsAreas(@NonNull List<RectF> regions) {
            // this call will activate the offset notifications
            // if no colors were being processed before
            mWallpaperColorExtractor.addLocalColorsAreas(regions);
        }

        private void scheduleUnloadWallpaper() {
            Handler handler = getMainThreadHandler();
            handler.removeCallbacks(mUnloadWallpaperCallback);
            handler.postDelayed(mUnloadWallpaperCallback, DELAY_FORGET_WALLPAPER);
        @Override
        public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
            // this call will deactivate the offset notifications
            // if we are no longer processing colors
            mWallpaperColorExtractor.removeLocalColorAreas(regions);
        }

        @Override
        public void onOffsetsChanged(float xOffset, float yOffset,
                float xOffsetStep, float yOffsetStep,
                int xPixelOffset, int yPixelOffset) {
            /*
             * TODO check this formula. mPages is always >= 4, even when launcher is single-paged
             * this formula is also used in the GL engine
             */
            final int pages;
            if (xOffsetStep > 0 && xOffsetStep <= 1) {
                pages = Math.round(1 / xOffsetStep) + 1;
            } else {
                pages = 1;
            }
            if (pages != mPages || !mPagesComputed) {
                mPages = pages;
                mPagesComputed = true;
                mWallpaperColorExtractor.onPageChanged(mPages);
            }
        }

        @Override
@@ -763,14 +861,47 @@ public class ImageWallpaper extends WallpaperService {

        }

        @Override
        public void onDisplayRemoved(int displayId) {

        }

        @Override
        public void onDisplayChanged(int displayId) {
            // changes the display in the color extractor
            // the new display dimensions will be used in the next color computation
            if (displayId == getDisplayContext().getDisplayId()) {
                getDisplaySizeAndUpdateColorExtractor();
            }
        }

        private void getDisplaySizeAndUpdateColorExtractor() {
            Rect window = getDisplayContext()
                    .getSystemService(WindowManager.class)
                    .getCurrentWindowMetrics()
                    .getBounds();
            mWallpaperColorExtractor.setDisplayDimensions(window.width(), window.height());
        }


        @Override
        public void onDisplayRemoved(int displayId) {
        protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
            super.dump(prefix, fd, out, args);
            out.print(prefix); out.print("Engine="); out.println(this);
            out.print(prefix); out.print("valid surface=");
            out.println(getSurfaceHolder() != null && getSurfaceHolder().getSurface() != null
                    ? getSurfaceHolder().getSurface().isValid()
                    : "null");

            out.print(prefix); out.print("surface frame=");
            out.println(getSurfaceHolder() != null ? getSurfaceHolder().getSurfaceFrame() : "null");

            out.print(prefix); out.print("bitmap=");
            out.println(mBitmap == null ? "null"
                    : mBitmap.isRecycled() ? "recycled"
                    : mBitmap.getWidth() + "x" + mBitmap.getHeight());

            mWallpaperColorExtractor.dump(prefix, fd, out, args);
        }
    }
}
+0 −145

File deleted.

Preview size limit exceeded, changes collapsed.

+400 −0

File added.

Preview size limit exceeded, changes collapsed.

+186 −14

File changed.

Preview size limit exceeded, changes collapsed.

+0 −133

File deleted.

Preview size limit exceeded, changes collapsed.

Loading