summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java463
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRenderer.java145
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java400
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java200
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRendererTest.java133
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java350
6 files changed, 1233 insertions, 458 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 0f7e14374e60..6b3beeb54c76 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -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");
- }
- unloadWallpaper();
+ @Override
+ public void onMiniBitmapUpdated() {
+ CanvasEngine.this.onMiniBitmapUpdated();
+ }
+
+ @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
@@ -581,31 +606,30 @@ public class ImageWallpaper extends WallpaperService {
}
@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,135 +637,88 @@ 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(true);
+ drawFrame();
}
- private DisplayInfo getDisplayInfo() {
- mDisplay.getDisplayInfo(mTmpDisplayInfo);
- return mTmpDisplayInfo;
+ private void drawFrame() {
+ mBackgroundExecutor.execute(this::drawFrameSynchronized);
}
- private void drawFrame(boolean forceRedraw) {
- if (!mImageCanvasWallpaperRenderer.isSurfaceHolderLoaded()) {
+ private void drawFrameSynchronized() {
+ synchronized (mLock) {
+ drawFrameInternal();
+ }
+ }
+
+ 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);
+ }
+
+ private void unloadBitmapIfNotUsedSynchronized() {
+ synchronized (mLock) {
+ mBitmapUsages -= 1;
+ if (mBitmapUsages <= 0) {
+ mBitmapUsages = 0;
+ unloadBitmapInternal();
}
- return;
}
- 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;
- }
-
- // 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();
- } 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 */);
- } catch (RuntimeException | OutOfMemoryError e) {
- Log.w(TAG, "Unable to load default wallpaper!", e);
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- setBitmap(bitmap);
-
- if (mNeedsDrawAfterLoadingWallpaper) {
- drawFrame(true);
- }
+ }
- mLoader = null;
- mNeedsDrawAfterLoadingWallpaper = false;
- scheduleUnloadWallpaper();
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ private void unloadBitmap() {
+ mBackgroundExecutor.execute(this::unloadBitmapSynchronized);
}
- private void unloadWallpaper() {
- if (mLoader != null) {
- mLoader.cancel(false);
- mLoader = null;
+ private void unloadBitmapSynchronized() {
+ synchronized (mLock) {
+ mBitmapUsages = 0;
+ unloadBitmapInternal();
}
+ }
+ private void unloadBitmapInternal() {
+ Trace.beginSection("ImageWallpaper.CanvasEngine#unloadBitmap");
if (mBitmap != null) {
mBitmap.recycle();
}
@@ -750,12 +727,133 @@ public class ImageWallpaper extends WallpaperService {
final Surface surface = getSurfaceHolder().getSurface();
surface.hwuiDestroy();
mWallpaperManager.forgetLoadedWallpaper();
+ Trace.endSection();
}
- private void scheduleUnloadWallpaper() {
- Handler handler = getMainThreadHandler();
- handler.removeCallbacks(mUnloadWallpaperCallback);
- handler.postDelayed(mUnloadWallpaperCallback, DELAY_FORGET_WALLPAPER);
+ 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(WallpaperManager.FLAG_SYSTEM);
+ } catch (IOException ex) {
+ // now we're really screwed.
+ Log.w(TAG, "Unable reset to default wallpaper!", ex);
+ }
+
+ try {
+ bitmap = mWallpaperManager.getBitmap(false);
+ } catch (RuntimeException | OutOfMemoryError e) {
+ Log.w(TAG, "Unable to load default wallpaper!", e);
+ bitmap = null;
+ }
+ }
+
+ 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();
+
+ /*
+ * 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();
+ }
+
+ private void onColorsProcessed(List<RectF> regions, List<WallpaperColors> colors) {
+ try {
+ notifyLocalColorsChanged(regions, colors);
+ } catch (RuntimeException e) {
+ Log.e(TAG, e.getMessage(), e);
+ }
+ }
+
+ @VisibleForTesting
+ void recomputeColorExtractorMiniBitmap() {
+ mWallpaperColorExtractor.onBitmapChanged(mBitmap);
+ }
+
+ @VisibleForTesting
+ void onMiniBitmapUpdated() {
+ unloadBitmapIfNotUsed();
+ }
+
+ @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);
+ }
+
+ @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
@@ -764,13 +862,46 @@ 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);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRenderer.java
deleted file mode 100644
index fdba16ed2059..000000000000
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRenderer.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2022 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.systemui.wallpapers.canvas;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.util.Log;
-import android.view.SurfaceHolder;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-/**
- * Helper to draw a wallpaper on a surface.
- * It handles the geometry regarding the dimensions of the display and the wallpaper,
- * and rescales the surface and the wallpaper accordingly.
- */
-public class ImageCanvasWallpaperRenderer {
-
- private static final String TAG = ImageCanvasWallpaperRenderer.class.getSimpleName();
- private static final boolean DEBUG = false;
-
- private SurfaceHolder mSurfaceHolder;
- //private Bitmap mBitmap = null;
-
- @VisibleForTesting
- static final int MIN_SURFACE_WIDTH = 128;
- @VisibleForTesting
- static final int MIN_SURFACE_HEIGHT = 128;
-
- private boolean mSurfaceRedrawNeeded;
-
- private int mLastSurfaceWidth = -1;
- private int mLastSurfaceHeight = -1;
-
- public ImageCanvasWallpaperRenderer(SurfaceHolder surfaceHolder) {
- mSurfaceHolder = surfaceHolder;
- }
-
- /**
- * Set the surface holder on which to draw.
- * Should be called when the surface holder is created or changed
- * @param surfaceHolder the surface on which to draw the wallpaper
- */
- public void setSurfaceHolder(SurfaceHolder surfaceHolder) {
- mSurfaceHolder = surfaceHolder;
- }
-
- /**
- * Check if a surface holder is loaded
- * @return true if a valid surfaceHolder has been set.
- */
- public boolean isSurfaceHolderLoaded() {
- return mSurfaceHolder != null;
- }
-
- /**
- * Computes and set the surface dimensions, by using the play and the bitmap dimensions.
- * The Bitmap must be loaded before any call to this function
- */
- private boolean updateSurfaceSize(Bitmap bitmap) {
- int surfaceWidth = Math.max(MIN_SURFACE_WIDTH, bitmap.getWidth());
- int surfaceHeight = Math.max(MIN_SURFACE_HEIGHT, bitmap.getHeight());
- boolean surfaceChanged =
- surfaceWidth != mLastSurfaceWidth || surfaceHeight != mLastSurfaceHeight;
- if (surfaceChanged) {
- /*
- Used a fixed size surface, because we are special. We can do
- this because we know the current design of window animations doesn't
- cause this to break.
- */
- mSurfaceHolder.setFixedSize(surfaceWidth, surfaceHeight);
- mLastSurfaceWidth = surfaceWidth;
- mLastSurfaceHeight = surfaceHeight;
- }
- return surfaceChanged;
- }
-
- /**
- * Draw a the wallpaper on the surface.
- * The bitmap and the surface must be loaded before calling
- * this function.
- * @param forceRedraw redraw the wallpaper even if no changes are detected
- */
- public void drawFrame(Bitmap bitmap, boolean forceRedraw) {
-
- if (bitmap == null || bitmap.isRecycled()) {
- Log.e(TAG, "Attempt to draw frame before background is loaded:");
- return;
- }
-
- if (bitmap.getWidth() < 1 || bitmap.getHeight() < 1) {
- Log.e(TAG, "Attempt to set an invalid wallpaper of length "
- + bitmap.getWidth() + "x" + bitmap.getHeight());
- return;
- }
-
- mSurfaceRedrawNeeded |= forceRedraw;
- boolean surfaceChanged = updateSurfaceSize(bitmap);
-
- boolean redrawNeeded = surfaceChanged || mSurfaceRedrawNeeded;
- mSurfaceRedrawNeeded = false;
-
- if (!redrawNeeded) {
- if (DEBUG) {
- Log.d(TAG, "Suppressed drawFrame since redraw is not needed ");
- }
- return;
- }
-
- if (DEBUG) {
- Log.d(TAG, "Redrawing wallpaper");
- }
- drawWallpaperWithCanvas(bitmap);
- }
-
- @VisibleForTesting
- void drawWallpaperWithCanvas(Bitmap bitmap) {
- Canvas c = mSurfaceHolder.lockHardwareCanvas();
- if (c != null) {
- Rect dest = mSurfaceHolder.getSurfaceFrame();
- Log.i(TAG, "Redrawing in rect: " + dest + " with surface size: "
- + mLastSurfaceWidth + "x" + mLastSurfaceHeight);
- try {
- c.drawBitmap(bitmap, null, dest, null);
- } finally {
- mSurfaceHolder.unlockCanvasAndPost(c);
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java b/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java
new file mode 100644
index 000000000000..e2e4555bb965
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright (C) 2022 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.systemui.wallpapers.canvas;
+
+import android.app.WallpaperColors;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Trace;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.MathUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.util.Assert;
+import com.android.systemui.wallpapers.ImageWallpaper;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/**
+ * This class is used by the {@link ImageWallpaper} to extract colors from areas of a wallpaper.
+ * It uses a background executor, and uses callbacks to inform that the work is done.
+ * It uses a downscaled version of the wallpaper to extract the colors.
+ */
+public class WallpaperColorExtractor {
+
+ private Bitmap mMiniBitmap;
+
+ @VisibleForTesting
+ static final int SMALL_SIDE = 128;
+
+ private static final String TAG = WallpaperColorExtractor.class.getSimpleName();
+ private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
+ new RectF(0, 0, 1, 1);
+
+ private int mDisplayWidth = -1;
+ private int mDisplayHeight = -1;
+ private int mPages = -1;
+ private int mBitmapWidth = -1;
+ private int mBitmapHeight = -1;
+
+ private final Object mLock = new Object();
+
+ private final List<RectF> mPendingRegions = new ArrayList<>();
+ private final Set<RectF> mProcessedRegions = new ArraySet<>();
+
+ @Background
+ private final Executor mBackgroundExecutor;
+
+ private final WallpaperColorExtractorCallback mWallpaperColorExtractorCallback;
+
+ /**
+ * Interface to handle the callbacks after the different steps of the color extraction
+ */
+ public interface WallpaperColorExtractorCallback {
+ /**
+ * Callback after the colors of new regions have been extracted
+ * @param regions the list of new regions that have been processed
+ * @param colors the resulting colors for these regions, in the same order as the regions
+ */
+ void onColorsProcessed(List<RectF> regions, List<WallpaperColors> colors);
+
+ /**
+ * Callback after the mini bitmap is computed, to indicate that the wallpaper bitmap is
+ * no longer used by the color extractor and can be safely recycled
+ */
+ void onMiniBitmapUpdated();
+
+ /**
+ * Callback to inform that the extractor has started processing colors
+ */
+ void onActivated();
+
+ /**
+ * Callback to inform that no more colors are being processed
+ */
+ void onDeactivated();
+ }
+
+ /**
+ * Creates a new color extractor.
+ * @param backgroundExecutor the executor on which the color extraction will be performed
+ * @param wallpaperColorExtractorCallback an interface to handle the callbacks from
+ * the color extractor.
+ */
+ public WallpaperColorExtractor(@Background Executor backgroundExecutor,
+ WallpaperColorExtractorCallback wallpaperColorExtractorCallback) {
+ mBackgroundExecutor = backgroundExecutor;
+ mWallpaperColorExtractorCallback = wallpaperColorExtractorCallback;
+ }
+
+ /**
+ * Used by the outside to inform that the display size has changed.
+ * The new display size will be used in the next computations, but the current colors are
+ * not recomputed.
+ */
+ public void setDisplayDimensions(int displayWidth, int displayHeight) {
+ mBackgroundExecutor.execute(() ->
+ setDisplayDimensionsSynchronized(displayWidth, displayHeight));
+ }
+
+ private void setDisplayDimensionsSynchronized(int displayWidth, int displayHeight) {
+ synchronized (mLock) {
+ if (displayWidth == mDisplayWidth && displayHeight == mDisplayHeight) return;
+ mDisplayWidth = displayWidth;
+ mDisplayHeight = displayHeight;
+ processColorsInternal();
+ }
+ }
+
+ /**
+ * @return whether color extraction is currently in use
+ */
+ private boolean isActive() {
+ return mPendingRegions.size() + mProcessedRegions.size() > 0;
+ }
+
+ /**
+ * Should be called when the wallpaper is changed.
+ * This will recompute the mini bitmap
+ * and restart the extraction of all areas
+ * @param bitmap the new wallpaper
+ */
+ public void onBitmapChanged(@NonNull Bitmap bitmap) {
+ mBackgroundExecutor.execute(() -> onBitmapChangedSynchronized(bitmap));
+ }
+
+ private void onBitmapChangedSynchronized(@NonNull Bitmap bitmap) {
+ synchronized (mLock) {
+ if (bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
+ Log.e(TAG, "Attempt to extract colors from an invalid bitmap");
+ return;
+ }
+ mBitmapWidth = bitmap.getWidth();
+ mBitmapHeight = bitmap.getHeight();
+ mMiniBitmap = createMiniBitmap(bitmap);
+ mWallpaperColorExtractorCallback.onMiniBitmapUpdated();
+ recomputeColors();
+ }
+ }
+
+ /**
+ * Should be called when the number of pages is changed
+ * This will restart the extraction of all areas
+ * @param pages the total number of pages of the launcher
+ */
+ public void onPageChanged(int pages) {
+ mBackgroundExecutor.execute(() -> onPageChangedSynchronized(pages));
+ }
+
+ private void onPageChangedSynchronized(int pages) {
+ synchronized (mLock) {
+ if (mPages == pages) return;
+ mPages = pages;
+ if (mMiniBitmap != null && !mMiniBitmap.isRecycled()) {
+ recomputeColors();
+ }
+ }
+ }
+
+ // helper to recompute colors, to be called in synchronized methods
+ private void recomputeColors() {
+ mPendingRegions.addAll(mProcessedRegions);
+ mProcessedRegions.clear();
+ processColorsInternal();
+ }
+
+ /**
+ * Add new regions to extract
+ * This will trigger the color extraction and call the callback only for these new regions
+ * @param regions The areas of interest in our wallpaper (in screen pixel coordinates)
+ */
+ public void addLocalColorsAreas(@NonNull List<RectF> regions) {
+ if (regions.size() > 0) {
+ mBackgroundExecutor.execute(() -> addLocalColorsAreasSynchronized(regions));
+ } else {
+ Log.w(TAG, "Attempt to add colors with an empty list");
+ }
+ }
+
+ private void addLocalColorsAreasSynchronized(@NonNull List<RectF> regions) {
+ synchronized (mLock) {
+ boolean wasActive = isActive();
+ mPendingRegions.addAll(regions);
+ if (!wasActive && isActive()) {
+ mWallpaperColorExtractorCallback.onActivated();
+ }
+ processColorsInternal();
+ }
+ }
+
+ /**
+ * Remove regions to extract. If a color extraction is ongoing does not stop it.
+ * But if there are subsequent changes that restart the extraction, the removed regions
+ * will not be recomputed.
+ * @param regions The areas of interest in our wallpaper (in screen pixel coordinates)
+ */
+ public void removeLocalColorAreas(@NonNull List<RectF> regions) {
+ mBackgroundExecutor.execute(() -> removeLocalColorAreasSynchronized(regions));
+ }
+
+ private void removeLocalColorAreasSynchronized(@NonNull List<RectF> regions) {
+ synchronized (mLock) {
+ boolean wasActive = isActive();
+ mPendingRegions.removeAll(regions);
+ regions.forEach(mProcessedRegions::remove);
+ if (wasActive && !isActive()) {
+ mWallpaperColorExtractorCallback.onDeactivated();
+ }
+ }
+ }
+
+ /**
+ * Clean up the memory (in particular, the mini bitmap) used by this class.
+ */
+ public void cleanUp() {
+ mBackgroundExecutor.execute(this::cleanUpSynchronized);
+ }
+
+ private void cleanUpSynchronized() {
+ synchronized (mLock) {
+ if (mMiniBitmap != null) {
+ mMiniBitmap.recycle();
+ mMiniBitmap = null;
+ }
+ mProcessedRegions.clear();
+ mPendingRegions.clear();
+ }
+ }
+
+ private Bitmap createMiniBitmap(@NonNull Bitmap bitmap) {
+ Trace.beginSection("WallpaperColorExtractor#createMiniBitmap");
+ // if both sides of the image are larger than SMALL_SIDE, downscale the bitmap.
+ int smallestSide = Math.min(bitmap.getWidth(), bitmap.getHeight());
+ float scale = Math.min(1.0f, (float) SMALL_SIDE / smallestSide);
+ Bitmap result = createMiniBitmap(bitmap,
+ (int) (scale * bitmap.getWidth()),
+ (int) (scale * bitmap.getHeight()));
+ Trace.endSection();
+ return result;
+ }
+
+ @VisibleForTesting
+ Bitmap createMiniBitmap(@NonNull Bitmap bitmap, int width, int height) {
+ return Bitmap.createScaledBitmap(bitmap, width, height, false);
+ }
+
+ private WallpaperColors getLocalWallpaperColors(@NonNull RectF area) {
+ RectF imageArea = pageToImgRect(area);
+ if (imageArea == null || !LOCAL_COLOR_BOUNDS.contains(imageArea)) {
+ return null;
+ }
+ Rect subImage = new Rect(
+ (int) Math.floor(imageArea.left * mMiniBitmap.getWidth()),
+ (int) Math.floor(imageArea.top * mMiniBitmap.getHeight()),
+ (int) Math.ceil(imageArea.right * mMiniBitmap.getWidth()),
+ (int) Math.ceil(imageArea.bottom * mMiniBitmap.getHeight()));
+ if (subImage.isEmpty()) {
+ // Do not notify client. treat it as too small to sample
+ return null;
+ }
+ return getLocalWallpaperColors(subImage);
+ }
+
+ @VisibleForTesting
+ WallpaperColors getLocalWallpaperColors(@NonNull Rect subImage) {
+ Assert.isNotMainThread();
+ Bitmap colorImg = Bitmap.createBitmap(mMiniBitmap,
+ subImage.left, subImage.top, subImage.width(), subImage.height());
+ return WallpaperColors.fromBitmap(colorImg);
+ }
+
+ /**
+ * Transform the logical coordinates into wallpaper coordinates.
+ *
+ * Logical coordinates are organised such that the various pages are non-overlapping. So,
+ * if there are n pages, the first page will have its X coordinate on the range [0-1/n].
+ *
+ * The real pages are overlapping. If the Wallpaper are a width Ww and the screen a width
+ * Ws, the relative width of a page Wr is Ws/Ww. This does not change if the number of
+ * pages increase.
+ * If there are n pages, the page k starts at the offset k * (1 - Wr) / (n - 1), as the
+ * last page is at position (1-Wr) and the others are regularly spread on the range [0-
+ * (1-Wr)].
+ */
+ private RectF pageToImgRect(RectF area) {
+ // Width of a page for the caller of this API.
+ float virtualPageWidth = 1f / (float) mPages;
+ float leftPosOnPage = (area.left % virtualPageWidth) / virtualPageWidth;
+ float rightPosOnPage = (area.right % virtualPageWidth) / virtualPageWidth;
+ int currentPage = (int) Math.floor(area.centerX() / virtualPageWidth);
+
+ if (mDisplayWidth <= 0 || mDisplayHeight <= 0) {
+ Log.e(TAG, "Trying to extract colors with invalid display dimensions");
+ return null;
+ }
+
+ RectF imgArea = new RectF();
+ imgArea.bottom = area.bottom;
+ imgArea.top = area.top;
+
+ float imageScale = Math.min(((float) mBitmapHeight) / mDisplayHeight, 1);
+ float mappedScreenWidth = mDisplayWidth * imageScale;
+ float pageWidth = Math.min(1.0f,
+ mBitmapWidth > 0 ? mappedScreenWidth / (float) mBitmapWidth : 1.f);
+ float pageOffset = (1 - pageWidth) / (float) (mPages - 1);
+
+ imgArea.left = MathUtils.constrain(
+ leftPosOnPage * pageWidth + currentPage * pageOffset, 0, 1);
+ imgArea.right = MathUtils.constrain(
+ rightPosOnPage * pageWidth + currentPage * pageOffset, 0, 1);
+ if (imgArea.left > imgArea.right) {
+ // take full page
+ imgArea.left = 0;
+ imgArea.right = 1;
+ }
+ return imgArea;
+ }
+
+ /**
+ * Extract the colors from the pending regions,
+ * then notify the callback with the resulting colors for these regions
+ * This method should only be called synchronously
+ */
+ private void processColorsInternal() {
+ /*
+ * if the miniBitmap is not yet loaded, that means the onBitmapChanged has not yet been
+ * called, and thus the wallpaper is not yet loaded. In that case, exit, the function
+ * will be called again when the bitmap is loaded and the miniBitmap is computed.
+ */
+ if (mMiniBitmap == null || mMiniBitmap.isRecycled()) return;
+
+ /*
+ * if the screen size or number of pages is not yet known, exit
+ * the function will be called again once the screen size and page are known
+ */
+ if (mDisplayWidth < 0 || mDisplayHeight < 0 || mPages < 0) return;
+
+ Trace.beginSection("WallpaperColorExtractor#processColorsInternal");
+ List<WallpaperColors> processedColors = new ArrayList<>();
+ for (int i = 0; i < mPendingRegions.size(); i++) {
+ RectF nextArea = mPendingRegions.get(i);
+ WallpaperColors colors = getLocalWallpaperColors(nextArea);
+
+ mProcessedRegions.add(nextArea);
+ processedColors.add(colors);
+ }
+ List<RectF> processedRegions = new ArrayList<>(mPendingRegions);
+ mPendingRegions.clear();
+ Trace.endSection();
+
+ mWallpaperColorExtractorCallback.onColorsProcessed(processedRegions, processedColors);
+ }
+
+ /**
+ * Called to dump current state.
+ * @param prefix prefix.
+ * @param fd fd.
+ * @param out out.
+ * @param args args.
+ */
+ public void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
+ out.print(prefix); out.print("display="); out.println(mDisplayWidth + "x" + mDisplayHeight);
+ out.print(prefix); out.print("mPages="); out.println(mPages);
+
+ out.print(prefix); out.print("bitmap dimensions=");
+ out.println(mBitmapWidth + "x" + mBitmapHeight);
+
+ out.print(prefix); out.print("bitmap=");
+ out.println(mMiniBitmap == null ? "null"
+ : mMiniBitmap.isRecycled() ? "recycled"
+ : mMiniBitmap.getWidth() + "x" + mMiniBitmap.getHeight());
+
+ out.print(prefix); out.print("PendingRegions size="); out.print(mPendingRegions.size());
+ out.print(prefix); out.print("ProcessedRegions size="); out.print(mProcessedRegions.size());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
index 343437634b29..c2543589bfb8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
@@ -16,14 +16,23 @@
package com.android.systemui.wallpapers;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.mockito.hamcrest.MockitoHamcrest.intThat;
import android.app.WallpaperManager;
import android.content.Context;
@@ -31,17 +40,25 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.ColorSpace;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Handler;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.Display;
import android.view.DisplayInfo;
+import android.view.Surface;
import android.view.SurfaceHolder;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.wallpapers.gl.ImageWallpaperRenderer;
import org.junit.Before;
@@ -56,7 +73,6 @@ import java.util.concurrent.CountDownLatch;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-@Ignore
public class ImageWallpaperTest extends SysuiTestCase {
private static final int LOW_BMP_WIDTH = 128;
private static final int LOW_BMP_HEIGHT = 128;
@@ -66,44 +82,86 @@ public class ImageWallpaperTest extends SysuiTestCase {
private static final int DISPLAY_HEIGHT = 1080;
@Mock
+ private WindowManager mWindowManager;
+ @Mock
+ private WindowMetrics mWindowMetrics;
+ @Mock
+ private DisplayManager mDisplayManager;
+ @Mock
+ private WallpaperManager mWallpaperManager;
+ @Mock
private SurfaceHolder mSurfaceHolder;
@Mock
+ private Surface mSurface;
+ @Mock
private Context mMockContext;
+
@Mock
private Bitmap mWallpaperBitmap;
+ private int mBitmapWidth = 1;
+ private int mBitmapHeight = 1;
+
@Mock
private Handler mHandler;
@Mock
private FeatureFlags mFeatureFlags;
+ FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+ FakeExecutor mFakeMainExecutor = new FakeExecutor(mFakeSystemClock);
+ FakeExecutor mFakeBackgroundExecutor = new FakeExecutor(mFakeSystemClock);
+
private CountDownLatch mEventCountdown;
@Before
public void setUp() throws Exception {
allowTestableLooperAsMainThread();
MockitoAnnotations.initMocks(this);
- mEventCountdown = new CountDownLatch(1);
+ //mEventCountdown = new CountDownLatch(1);
- WallpaperManager wallpaperManager = mock(WallpaperManager.class);
- Resources resources = mock(Resources.class);
+ // set up window manager
+ when(mWindowMetrics.getBounds()).thenReturn(
+ new Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT));
+ when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+ when(mMockContext.getSystemService(WindowManager.class)).thenReturn(mWindowManager);
- when(mMockContext.getSystemService(WallpaperManager.class)).thenReturn(wallpaperManager);
- when(mMockContext.getResources()).thenReturn(resources);
- when(resources.getConfiguration()).thenReturn(mock(Configuration.class));
+ // set up display manager
+ doNothing().when(mDisplayManager).registerDisplayListener(any(), any());
+ when(mMockContext.getSystemService(DisplayManager.class)).thenReturn(mDisplayManager);
+ // set up bitmap
+ when(mWallpaperBitmap.getColorSpace()).thenReturn(ColorSpace.get(ColorSpace.Named.SRGB));
+ when(mWallpaperBitmap.getConfig()).thenReturn(Bitmap.Config.ARGB_8888);
+ when(mWallpaperBitmap.getWidth()).thenReturn(mBitmapWidth);
+ when(mWallpaperBitmap.getHeight()).thenReturn(mBitmapHeight);
+
+ // set up wallpaper manager
+ when(mWallpaperManager.peekBitmapDimensions()).thenReturn(
+ new Rect(0, 0, mBitmapWidth, mBitmapHeight));
+ when(mWallpaperManager.getBitmap(false)).thenReturn(mWallpaperBitmap);
+ when(mMockContext.getSystemService(WallpaperManager.class)).thenReturn(mWallpaperManager);
+
+ // set up surface
+ when(mSurfaceHolder.getSurface()).thenReturn(mSurface);
+ doNothing().when(mSurface).hwuiDestroy();
+
+ // TODO remove code below. Outdated, used in only in old GL tests (that are ignored)
+ Resources resources = mock(Resources.class);
+ when(resources.getConfiguration()).thenReturn(mock(Configuration.class));
+ when(mMockContext.getResources()).thenReturn(resources);
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = DISPLAY_WIDTH;
displayInfo.logicalHeight = DISPLAY_HEIGHT;
when(mMockContext.getDisplay()).thenReturn(
new Display(mock(DisplayManagerGlobal.class), 0, displayInfo, (Resources) null));
+ }
- when(wallpaperManager.getBitmap(false)).thenReturn(mWallpaperBitmap);
- when(mWallpaperBitmap.getColorSpace()).thenReturn(ColorSpace.get(ColorSpace.Named.SRGB));
- when(mWallpaperBitmap.getConfig()).thenReturn(Bitmap.Config.ARGB_8888);
+ private void setBitmapDimensions(int bitmapWidth, int bitmapHeight) {
+ mBitmapWidth = bitmapWidth;
+ mBitmapHeight = bitmapHeight;
}
private ImageWallpaper createImageWallpaper() {
- return new ImageWallpaper(mFeatureFlags) {
+ return new ImageWallpaper(mFeatureFlags, mFakeBackgroundExecutor, mFakeMainExecutor) {
@Override
public Engine onCreateEngine() {
return new GLEngine(mHandler) {
@@ -130,6 +188,7 @@ public class ImageWallpaperTest extends SysuiTestCase {
}
@Test
+ @Ignore
public void testBitmapWallpaper_normal() {
// Will use a image wallpaper with dimensions DISPLAY_WIDTH x DISPLAY_WIDTH.
// Then we expect the surface size will be also DISPLAY_WIDTH x DISPLAY_WIDTH.
@@ -140,6 +199,7 @@ public class ImageWallpaperTest extends SysuiTestCase {
}
@Test
+ @Ignore
public void testBitmapWallpaper_low_resolution() {
// Will use a image wallpaper with dimensions BMP_WIDTH x BMP_HEIGHT.
// Then we expect the surface size will be also BMP_WIDTH x BMP_HEIGHT.
@@ -150,6 +210,7 @@ public class ImageWallpaperTest extends SysuiTestCase {
}
@Test
+ @Ignore
public void testBitmapWallpaper_too_small() {
// Will use a image wallpaper with dimensions INVALID_BMP_WIDTH x INVALID_BMP_HEIGHT.
// Then we expect the surface size will be also MIN_SURFACE_WIDTH x MIN_SURFACE_HEIGHT.
@@ -166,8 +227,7 @@ public class ImageWallpaperTest extends SysuiTestCase {
ImageWallpaper.GLEngine engineSpy = spy(wallpaperEngine);
- when(mWallpaperBitmap.getWidth()).thenReturn(bmpWidth);
- when(mWallpaperBitmap.getHeight()).thenReturn(bmpHeight);
+ setBitmapDimensions(bmpWidth, bmpHeight);
ImageWallpaperRenderer renderer = new ImageWallpaperRenderer(mMockContext);
doReturn(renderer).when(engineSpy).getRendererInstance();
@@ -177,4 +237,116 @@ public class ImageWallpaperTest extends SysuiTestCase {
assertWithMessage("setFixedSizeAllowed should have been called.").that(
mEventCountdown.getCount()).isEqualTo(0);
}
+
+
+ private ImageWallpaper createImageWallpaperCanvas() {
+ return new ImageWallpaper(mFeatureFlags, mFakeBackgroundExecutor, mFakeMainExecutor) {
+ @Override
+ public Engine onCreateEngine() {
+ return new CanvasEngine() {
+ @Override
+ public Context getDisplayContext() {
+ return mMockContext;
+ }
+
+ @Override
+ public SurfaceHolder getSurfaceHolder() {
+ return mSurfaceHolder;
+ }
+
+ @Override
+ public void setFixedSizeAllowed(boolean allowed) {
+ super.setFixedSizeAllowed(allowed);
+ assertWithMessage("mFixedSizeAllowed should be true").that(
+ allowed).isTrue();
+ }
+ };
+ }
+ };
+ }
+
+ private ImageWallpaper.CanvasEngine getSpyEngine() {
+ ImageWallpaper imageWallpaper = createImageWallpaperCanvas();
+ ImageWallpaper.CanvasEngine engine =
+ (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
+ ImageWallpaper.CanvasEngine spyEngine = spy(engine);
+ doNothing().when(spyEngine).drawFrameOnCanvas(any(Bitmap.class));
+ doNothing().when(spyEngine).reportEngineShown(anyBoolean());
+ doAnswer(invocation -> {
+ ((ImageWallpaper.CanvasEngine) invocation.getMock()).onMiniBitmapUpdated();
+ return null;
+ }).when(spyEngine).recomputeColorExtractorMiniBitmap();
+ return spyEngine;
+ }
+
+ @Test
+ public void testMinSurface() {
+
+ // test that the surface is always at least MIN_SURFACE_WIDTH x MIN_SURFACE_HEIGHT
+ testMinSurfaceHelper(8, 8);
+ testMinSurfaceHelper(100, 2000);
+ testMinSurfaceHelper(200, 1);
+ testMinSurfaceHelper(0, 1);
+ testMinSurfaceHelper(1, 0);
+ testMinSurfaceHelper(0, 0);
+ }
+
+ private void testMinSurfaceHelper(int bitmapWidth, int bitmapHeight) {
+
+ clearInvocations(mSurfaceHolder);
+ setBitmapDimensions(bitmapWidth, bitmapHeight);
+
+ ImageWallpaper imageWallpaper = createImageWallpaperCanvas();
+ ImageWallpaper.CanvasEngine engine =
+ (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
+ engine.onCreate(mSurfaceHolder);
+
+ verify(mSurfaceHolder, times(1)).setFixedSize(
+ intThat(greaterThanOrEqualTo(ImageWallpaper.CanvasEngine.MIN_SURFACE_WIDTH)),
+ intThat(greaterThanOrEqualTo(ImageWallpaper.CanvasEngine.MIN_SURFACE_HEIGHT)));
+ }
+
+ @Test
+ public void testZeroBitmap() {
+ // test that a frame is never drawn with a 0 bitmap
+ testZeroBitmapHelper(0, 1);
+ testZeroBitmapHelper(1, 0);
+ testZeroBitmapHelper(0, 0);
+ }
+
+ private void testZeroBitmapHelper(int bitmapWidth, int bitmapHeight) {
+
+ clearInvocations(mSurfaceHolder);
+ setBitmapDimensions(bitmapWidth, bitmapHeight);
+
+ ImageWallpaper imageWallpaper = createImageWallpaperCanvas();
+ ImageWallpaper.CanvasEngine engine =
+ (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
+ ImageWallpaper.CanvasEngine spyEngine = spy(engine);
+ spyEngine.onCreate(mSurfaceHolder);
+ spyEngine.onSurfaceRedrawNeeded(mSurfaceHolder);
+ verify(spyEngine, never()).drawFrameOnCanvas(any());
+ }
+
+ @Test
+ public void testLoadDrawAndUnloadBitmap() {
+ setBitmapDimensions(LOW_BMP_WIDTH, LOW_BMP_HEIGHT);
+
+ ImageWallpaper.CanvasEngine spyEngine = getSpyEngine();
+ spyEngine.onCreate(mSurfaceHolder);
+ spyEngine.onSurfaceRedrawNeeded(mSurfaceHolder);
+ assertThat(mFakeBackgroundExecutor.numPending()).isAtLeast(1);
+
+ int n = 0;
+ while (mFakeBackgroundExecutor.numPending() + mFakeMainExecutor.numPending() >= 1) {
+ n++;
+ assertThat(n).isAtMost(10);
+ mFakeBackgroundExecutor.runNextReady();
+ mFakeMainExecutor.runNextReady();
+ mFakeSystemClock.advanceTime(1000);
+ }
+
+ verify(spyEngine, times(1)).drawFrameOnCanvas(mWallpaperBitmap);
+ assertThat(spyEngine.isBitmapLoaded()).isFalse();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRendererTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRendererTest.java
deleted file mode 100644
index 93f4f8223955..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRendererTest.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2022 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.systemui.wallpapers.canvas;
-
-import static org.hamcrest.Matchers.greaterThanOrEqualTo;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.mockito.hamcrest.MockitoHamcrest.intThat;
-
-import android.graphics.Bitmap;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.DisplayInfo;
-import android.view.SurfaceHolder;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class ImageCanvasWallpaperRendererTest extends SysuiTestCase {
-
- private static final int MOBILE_DISPLAY_WIDTH = 720;
- private static final int MOBILE_DISPLAY_HEIGHT = 1600;
-
- @Mock
- private SurfaceHolder mMockSurfaceHolder;
-
- @Mock
- private DisplayInfo mMockDisplayInfo;
-
- @Mock
- private Bitmap mMockBitmap;
-
- @Before
- public void setUp() throws Exception {
- allowTestableLooperAsMainThread();
- MockitoAnnotations.initMocks(this);
- }
-
- private void setDimensions(
- int bitmapWidth, int bitmapHeight,
- int displayWidth, int displayHeight) {
- when(mMockBitmap.getWidth()).thenReturn(bitmapWidth);
- when(mMockBitmap.getHeight()).thenReturn(bitmapHeight);
- mMockDisplayInfo.logicalWidth = displayWidth;
- mMockDisplayInfo.logicalHeight = displayHeight;
- }
-
- private void testMinDimensions(
- int bitmapWidth, int bitmapHeight) {
-
- clearInvocations(mMockSurfaceHolder);
- setDimensions(bitmapWidth, bitmapHeight,
- ImageCanvasWallpaperRendererTest.MOBILE_DISPLAY_WIDTH,
- ImageCanvasWallpaperRendererTest.MOBILE_DISPLAY_HEIGHT);
-
- ImageCanvasWallpaperRenderer renderer =
- new ImageCanvasWallpaperRenderer(mMockSurfaceHolder);
- renderer.drawFrame(mMockBitmap, true);
-
- verify(mMockSurfaceHolder, times(1)).setFixedSize(
- intThat(greaterThanOrEqualTo(ImageCanvasWallpaperRenderer.MIN_SURFACE_WIDTH)),
- intThat(greaterThanOrEqualTo(ImageCanvasWallpaperRenderer.MIN_SURFACE_HEIGHT)));
- }
-
- @Test
- public void testMinSurface() {
- // test that the surface is always at least MIN_SURFACE_WIDTH x MIN_SURFACE_HEIGHT
- testMinDimensions(8, 8);
-
- testMinDimensions(100, 2000);
-
- testMinDimensions(200, 1);
- }
-
- private void testZeroDimensions(int bitmapWidth, int bitmapHeight) {
-
- clearInvocations(mMockSurfaceHolder);
- setDimensions(bitmapWidth, bitmapHeight,
- ImageCanvasWallpaperRendererTest.MOBILE_DISPLAY_WIDTH,
- ImageCanvasWallpaperRendererTest.MOBILE_DISPLAY_HEIGHT);
-
- ImageCanvasWallpaperRenderer renderer =
- new ImageCanvasWallpaperRenderer(mMockSurfaceHolder);
- ImageCanvasWallpaperRenderer spyRenderer = spy(renderer);
- spyRenderer.drawFrame(mMockBitmap, true);
-
- verify(mMockSurfaceHolder, never()).setFixedSize(anyInt(), anyInt());
- verify(spyRenderer, never()).drawWallpaperWithCanvas(any());
- }
-
- @Test
- public void testZeroBitmap() {
- // test that updateSurfaceSize is not called with a bitmap of width 0 or height 0
- testZeroDimensions(
- 0, 1
- );
-
- testZeroDimensions(1, 0
- );
-
- testZeroDimensions(0, 0
- );
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java
new file mode 100644
index 000000000000..76bff1d72141
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2022 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.systemui.wallpapers.canvas;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.WallpaperColors;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class WallpaperColorExtractorTest extends SysuiTestCase {
+ private static final int LOW_BMP_WIDTH = 128;
+ private static final int LOW_BMP_HEIGHT = 128;
+ private static final int HIGH_BMP_WIDTH = 3000;
+ private static final int HIGH_BMP_HEIGHT = 4000;
+ private static final int VERY_LOW_BMP_WIDTH = 1;
+ private static final int VERY_LOW_BMP_HEIGHT = 1;
+ private static final int DISPLAY_WIDTH = 1920;
+ private static final int DISPLAY_HEIGHT = 1080;
+
+ private static final int PAGES_LOW = 4;
+ private static final int PAGES_HIGH = 7;
+
+ private static final int MIN_AREAS = 4;
+ private static final int MAX_AREAS = 10;
+
+ private int mMiniBitmapWidth;
+ private int mMiniBitmapHeight;
+
+ @Mock
+ private Executor mBackgroundExecutor;
+
+ private int mColorsProcessed;
+ private int mMiniBitmapUpdatedCount;
+ private int mActivatedCount;
+ private int mDeactivatedCount;
+
+ @Before
+ public void setUp() throws Exception {
+ allowTestableLooperAsMainThread();
+ MockitoAnnotations.initMocks(this);
+ doAnswer(invocation -> {
+ ((Runnable) invocation.getArgument(0)).run();
+ return null;
+ }).when(mBackgroundExecutor).execute(any(Runnable.class));
+ }
+
+ private void resetCounters() {
+ mColorsProcessed = 0;
+ mMiniBitmapUpdatedCount = 0;
+ mActivatedCount = 0;
+ mDeactivatedCount = 0;
+ }
+
+ private Bitmap getMockBitmap(int width, int height) {
+ Bitmap bitmap = mock(Bitmap.class);
+ when(bitmap.getWidth()).thenReturn(width);
+ when(bitmap.getHeight()).thenReturn(height);
+ return bitmap;
+ }
+
+ private WallpaperColorExtractor getSpyWallpaperColorExtractor() {
+
+ WallpaperColorExtractor wallpaperColorExtractor = new WallpaperColorExtractor(
+ mBackgroundExecutor,
+ new WallpaperColorExtractor.WallpaperColorExtractorCallback() {
+ @Override
+ public void onColorsProcessed(List<RectF> regions,
+ List<WallpaperColors> colors) {
+ assertThat(regions.size()).isEqualTo(colors.size());
+ mColorsProcessed += regions.size();
+ }
+
+ @Override
+ public void onMiniBitmapUpdated() {
+ mMiniBitmapUpdatedCount++;
+ }
+
+ @Override
+ public void onActivated() {
+ mActivatedCount++;
+ }
+
+ @Override
+ public void onDeactivated() {
+ mDeactivatedCount++;
+ }
+ });
+ WallpaperColorExtractor spyWallpaperColorExtractor = spy(wallpaperColorExtractor);
+
+ doAnswer(invocation -> {
+ mMiniBitmapWidth = invocation.getArgument(1);
+ mMiniBitmapHeight = invocation.getArgument(2);
+ return getMockBitmap(mMiniBitmapWidth, mMiniBitmapHeight);
+ }).when(spyWallpaperColorExtractor).createMiniBitmap(any(Bitmap.class), anyInt(), anyInt());
+
+
+ doAnswer(invocation -> getMockBitmap(
+ invocation.getArgument(1),
+ invocation.getArgument(2)))
+ .when(spyWallpaperColorExtractor)
+ .createMiniBitmap(any(Bitmap.class), anyInt(), anyInt());
+
+ doReturn(new WallpaperColors(Color.valueOf(0), Color.valueOf(0), Color.valueOf(0)))
+ .when(spyWallpaperColorExtractor).getLocalWallpaperColors(any(Rect.class));
+
+ return spyWallpaperColorExtractor;
+ }
+
+ private RectF randomArea() {
+ float width = (float) Math.random();
+ float startX = (float) (Math.random() * (1 - width));
+ float height = (float) Math.random();
+ float startY = (float) (Math.random() * (1 - height));
+ return new RectF(startX, startY, startX + width, startY + height);
+ }
+
+ private List<RectF> listOfRandomAreas(int min, int max) {
+ int nAreas = randomBetween(min, max);
+ List<RectF> result = new ArrayList<>();
+ for (int i = 0; i < nAreas; i++) {
+ result.add(randomArea());
+ }
+ return result;
+ }
+
+ private int randomBetween(int minIncluded, int maxIncluded) {
+ return (int) (Math.random() * ((maxIncluded - minIncluded) + 1)) + minIncluded;
+ }
+
+ /**
+ * Test that for bitmaps of random dimensions, the mini bitmap is always created
+ * with either a width <= SMALL_SIDE or a height <= SMALL_SIDE
+ */
+ @Test
+ public void testMiniBitmapCreation() {
+ WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ int nSimulations = 10;
+ for (int i = 0; i < nSimulations; i++) {
+ resetCounters();
+ int width = randomBetween(LOW_BMP_WIDTH, HIGH_BMP_WIDTH);
+ int height = randomBetween(LOW_BMP_HEIGHT, HIGH_BMP_HEIGHT);
+ Bitmap bitmap = getMockBitmap(width, height);
+ spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+
+ assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+ assertThat(Math.min(mMiniBitmapWidth, mMiniBitmapHeight))
+ .isAtMost(WallpaperColorExtractor.SMALL_SIDE);
+ }
+ }
+
+ /**
+ * Test that for bitmaps with both width and height <= SMALL_SIDE,
+ * the mini bitmap is always created with both width and height <= SMALL_SIDE
+ */
+ @Test
+ public void testSmallMiniBitmapCreation() {
+ WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ int nSimulations = 10;
+ for (int i = 0; i < nSimulations; i++) {
+ resetCounters();
+ int width = randomBetween(VERY_LOW_BMP_WIDTH, LOW_BMP_WIDTH);
+ int height = randomBetween(VERY_LOW_BMP_HEIGHT, LOW_BMP_HEIGHT);
+ Bitmap bitmap = getMockBitmap(width, height);
+ spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+
+ assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+ assertThat(Math.max(mMiniBitmapWidth, mMiniBitmapHeight))
+ .isAtMost(WallpaperColorExtractor.SMALL_SIDE);
+ }
+ }
+
+ /**
+ * Test that for a new color extractor with information
+ * (number of pages, display dimensions, wallpaper bitmap) given in random order,
+ * the colors are processed and all the callbacks are properly executed.
+ */
+ @Test
+ public void testNewColorExtraction() {
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+
+ int nSimulations = 10;
+ for (int i = 0; i < nSimulations; i++) {
+ resetCounters();
+ WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ List<RectF> regions = listOfRandomAreas(MIN_AREAS, MAX_AREAS);
+ int nPages = randomBetween(PAGES_LOW, PAGES_HIGH);
+ List<Runnable> tasks = Arrays.asList(
+ () -> spyWallpaperColorExtractor.onPageChanged(nPages),
+ () -> spyWallpaperColorExtractor.onBitmapChanged(bitmap),
+ () -> spyWallpaperColorExtractor.setDisplayDimensions(
+ DISPLAY_WIDTH, DISPLAY_HEIGHT),
+ () -> spyWallpaperColorExtractor.addLocalColorsAreas(
+ regions));
+ Collections.shuffle(tasks);
+ tasks.forEach(Runnable::run);
+
+ assertThat(mActivatedCount).isEqualTo(1);
+ assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+ assertThat(mColorsProcessed).isEqualTo(regions.size());
+
+ spyWallpaperColorExtractor.removeLocalColorAreas(regions);
+ assertThat(mDeactivatedCount).isEqualTo(1);
+ }
+ }
+
+ /**
+ * Test that the method removeLocalColorAreas behaves properly and does not call
+ * the onDeactivated callback unless all color areas are removed.
+ */
+ @Test
+ public void testRemoveColors() {
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ int nSimulations = 10;
+ for (int i = 0; i < nSimulations; i++) {
+ resetCounters();
+ WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ List<RectF> regions1 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
+ List<RectF> regions2 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
+ List<RectF> regions = new ArrayList<>();
+ regions.addAll(regions1);
+ regions.addAll(regions2);
+ int nPages = randomBetween(PAGES_LOW, PAGES_HIGH);
+ List<Runnable> tasks = Arrays.asList(
+ () -> spyWallpaperColorExtractor.onPageChanged(nPages),
+ () -> spyWallpaperColorExtractor.onBitmapChanged(bitmap),
+ () -> spyWallpaperColorExtractor.setDisplayDimensions(
+ DISPLAY_WIDTH, DISPLAY_HEIGHT),
+ () -> spyWallpaperColorExtractor.removeLocalColorAreas(regions1));
+
+ spyWallpaperColorExtractor.addLocalColorsAreas(regions);
+ assertThat(mActivatedCount).isEqualTo(1);
+ Collections.shuffle(tasks);
+ tasks.forEach(Runnable::run);
+
+ assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+ assertThat(mDeactivatedCount).isEqualTo(0);
+ spyWallpaperColorExtractor.removeLocalColorAreas(regions2);
+ assertThat(mDeactivatedCount).isEqualTo(1);
+ }
+ }
+
+ /**
+ * Test that if we change some information (wallpaper bitmap, number of pages),
+ * the colors are correctly recomputed.
+ * Test that if we remove some color areas in the middle of the process,
+ * only the remaining areas are recomputed.
+ */
+ @Test
+ public void testRecomputeColorExtraction() {
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ List<RectF> regions1 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
+ List<RectF> regions2 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
+ List<RectF> regions = new ArrayList<>();
+ regions.addAll(regions1);
+ regions.addAll(regions2);
+ spyWallpaperColorExtractor.addLocalColorsAreas(regions);
+ assertThat(mActivatedCount).isEqualTo(1);
+ int nPages = PAGES_LOW;
+ spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+ spyWallpaperColorExtractor.onPageChanged(nPages);
+ spyWallpaperColorExtractor.setDisplayDimensions(DISPLAY_WIDTH, DISPLAY_HEIGHT);
+
+ int nSimulations = 20;
+ for (int i = 0; i < nSimulations; i++) {
+ resetCounters();
+
+ // verify that if we remove some regions, they are not recomputed after other changes
+ if (i == nSimulations / 2) {
+ regions.removeAll(regions2);
+ spyWallpaperColorExtractor.removeLocalColorAreas(regions2);
+ }
+
+ if (Math.random() >= 0.5) {
+ int nPagesNew = randomBetween(PAGES_LOW, PAGES_HIGH);
+ if (nPagesNew == nPages) continue;
+ nPages = nPagesNew;
+ spyWallpaperColorExtractor.onPageChanged(nPagesNew);
+ } else {
+ Bitmap newBitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ spyWallpaperColorExtractor.onBitmapChanged(newBitmap);
+ assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+ }
+ assertThat(mColorsProcessed).isEqualTo(regions.size());
+ }
+ spyWallpaperColorExtractor.removeLocalColorAreas(regions);
+ assertThat(mDeactivatedCount).isEqualTo(1);
+ }
+
+ @Test
+ public void testCleanUp() {
+ resetCounters();
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ doNothing().when(bitmap).recycle();
+ WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ spyWallpaperColorExtractor.onPageChanged(PAGES_LOW);
+ spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+ assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+ spyWallpaperColorExtractor.cleanUp();
+ spyWallpaperColorExtractor.addLocalColorsAreas(listOfRandomAreas(MIN_AREAS, MAX_AREAS));
+ assertThat(mColorsProcessed).isEqualTo(0);
+ }
+}