Merge "Rewrite thread logic, add tests and add color extraction for CanvasEngine" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
8fa46d1410
@@ -16,21 +16,17 @@
|
|||||||
|
|
||||||
package com.android.systemui.wallpapers;
|
package com.android.systemui.wallpapers;
|
||||||
|
|
||||||
import static android.view.Display.DEFAULT_DISPLAY;
|
|
||||||
|
|
||||||
import static com.android.systemui.flags.Flags.USE_CANVAS_RENDERER;
|
import static com.android.systemui.flags.Flags.USE_CANVAS_RENDERER;
|
||||||
|
|
||||||
import android.app.WallpaperColors;
|
import android.app.WallpaperColors;
|
||||||
import android.app.WallpaperManager;
|
import android.app.WallpaperManager;
|
||||||
import android.content.ComponentCallbacks2;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
import android.graphics.RecordingCanvas;
|
import android.graphics.RecordingCanvas;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.RectF;
|
import android.graphics.RectF;
|
||||||
import android.hardware.display.DisplayManager;
|
import android.hardware.display.DisplayManager;
|
||||||
import android.hardware.display.DisplayManager.DisplayListener;
|
import android.hardware.display.DisplayManager.DisplayListener;
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
@@ -40,8 +36,6 @@ import android.util.ArraySet;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.MathUtils;
|
import android.util.MathUtils;
|
||||||
import android.util.Size;
|
import android.util.Size;
|
||||||
import android.view.Display;
|
|
||||||
import android.view.DisplayInfo;
|
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import android.view.SurfaceHolder;
|
import android.view.SurfaceHolder;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
@@ -49,8 +43,11 @@ import android.view.WindowManager;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
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.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.EglHelper;
|
||||||
import com.android.systemui.wallpapers.gl.ImageWallpaperRenderer;
|
import com.android.systemui.wallpapers.gl.ImageWallpaperRenderer;
|
||||||
|
|
||||||
@@ -59,6 +56,7 @@ import java.io.IOException;
|
|||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
@@ -78,15 +76,28 @@ public class ImageWallpaper extends WallpaperService {
|
|||||||
private final ArrayList<RectF> mLocalColorsToAdd = new ArrayList<>();
|
private final ArrayList<RectF> mLocalColorsToAdd = new ArrayList<>();
|
||||||
private final ArraySet<RectF> mColorAreas = new ArraySet<>();
|
private final ArraySet<RectF> mColorAreas = new ArraySet<>();
|
||||||
private volatile int mPages = 1;
|
private volatile int mPages = 1;
|
||||||
|
private boolean mPagesComputed = false;
|
||||||
private HandlerThread mWorker;
|
private HandlerThread mWorker;
|
||||||
// scaled down version
|
// scaled down version
|
||||||
private Bitmap mMiniBitmap;
|
private Bitmap mMiniBitmap;
|
||||||
private final FeatureFlags mFeatureFlags;
|
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
|
@Inject
|
||||||
public ImageWallpaper(FeatureFlags featureFlags) {
|
public ImageWallpaper(FeatureFlags featureFlags,
|
||||||
|
@Background DelayableExecutor backgroundExecutor,
|
||||||
|
@Main Executor mainExecutor) {
|
||||||
super();
|
super();
|
||||||
mFeatureFlags = featureFlags;
|
mFeatureFlags = featureFlags;
|
||||||
|
mBackgroundExecutor = backgroundExecutor;
|
||||||
|
mMainExecutor = mainExecutor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -339,7 +350,6 @@ public class ImageWallpaper extends WallpaperService {
|
|||||||
imgArea.left = 0;
|
imgArea.left = 0;
|
||||||
imgArea.right = 1;
|
imgArea.right = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return imgArea;
|
return imgArea;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -510,69 +520,84 @@ public class ImageWallpaper extends WallpaperService {
|
|||||||
|
|
||||||
|
|
||||||
class CanvasEngine extends WallpaperService.Engine implements DisplayListener {
|
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 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 Bitmap mBitmap;
|
||||||
|
|
||||||
private Display mDisplay;
|
/*
|
||||||
private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
|
* Counter to unload the bitmap as soon as possible.
|
||||||
|
* Before any bitmap operation, this is incremented.
|
||||||
private AsyncTask<Void, Void, Bitmap> mLoader;
|
* After an operation completion, this is decremented (synchronously),
|
||||||
private boolean mNeedsDrawAfterLoadingWallpaper = false;
|
* and if the count is 0, unload the bitmap
|
||||||
|
*/
|
||||||
|
private int mBitmapUsages = 0;
|
||||||
|
private final Object mLock = new Object();
|
||||||
|
|
||||||
CanvasEngine() {
|
CanvasEngine() {
|
||||||
super();
|
super();
|
||||||
setFixedSizeAllowed(true);
|
setFixedSizeAllowed(true);
|
||||||
setShowForAllUsers(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) {
|
@Override
|
||||||
if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW
|
public void onMiniBitmapUpdated() {
|
||||||
&& level <= ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL
|
CanvasEngine.this.onMiniBitmapUpdated();
|
||||||
&& isBitmapLoaded()) {
|
}
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "trimMemory");
|
@Override
|
||||||
}
|
public void onActivated() {
|
||||||
unloadWallpaper();
|
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
|
@Override
|
||||||
public void onCreate(SurfaceHolder surfaceHolder) {
|
public void onCreate(SurfaceHolder surfaceHolder) {
|
||||||
|
Trace.beginSection("ImageWallpaper.CanvasEngine#onCreate");
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onCreate");
|
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);
|
getDisplayContext().getSystemService(DisplayManager.class)
|
||||||
super.onCreate(surfaceHolder);
|
.registerDisplayListener(this, null);
|
||||||
|
getDisplaySizeAndUpdateColorExtractor();
|
||||||
final Context displayContext = getDisplayContext();
|
Trace.endSection();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
getDisplayContext().getSystemService(DisplayManager.class)
|
||||||
unloadWallpaper();
|
.unregisterDisplayListener(this);
|
||||||
|
mWallpaperColorExtractor.cleanUp();
|
||||||
|
unloadBitmap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -580,32 +605,31 @@ public class ImageWallpaper extends WallpaperService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldWaitForEngineShown() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onSurfaceChanged: width=" + width + ", height=" + height);
|
Log.d(TAG, "onSurfaceChanged: width=" + width + ", height=" + height);
|
||||||
}
|
}
|
||||||
super.onSurfaceChanged(holder, format, width, height);
|
|
||||||
mImageCanvasWallpaperRenderer.setSurfaceHolder(holder);
|
|
||||||
drawFrame(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSurfaceDestroyed(SurfaceHolder holder) {
|
public void onSurfaceDestroyed(SurfaceHolder holder) {
|
||||||
super.onSurfaceDestroyed(holder);
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.i(TAG, "onSurfaceDestroyed");
|
Log.i(TAG, "onSurfaceDestroyed");
|
||||||
}
|
}
|
||||||
mImageCanvasWallpaperRenderer.setSurfaceHolder(null);
|
mSurfaceHolder = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSurfaceCreated(SurfaceHolder holder) {
|
public void onSurfaceCreated(SurfaceHolder holder) {
|
||||||
super.onSurfaceCreated(holder);
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.i(TAG, "onSurfaceCreated");
|
Log.i(TAG, "onSurfaceCreated");
|
||||||
}
|
}
|
||||||
mImageCanvasWallpaperRenderer.setSurfaceHolder(holder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -613,135 +637,88 @@ public class ImageWallpaper extends WallpaperService {
|
|||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onSurfaceRedrawNeeded");
|
Log.d(TAG, "onSurfaceRedrawNeeded");
|
||||||
}
|
}
|
||||||
super.onSurfaceRedrawNeeded(holder);
|
drawFrame();
|
||||||
// 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.
|
private void drawFrame() {
|
||||||
if (!isBitmapLoaded()) {
|
mBackgroundExecutor.execute(this::drawFrameSynchronized);
|
||||||
setBitmap(mWallpaperManager.getBitmap(true /* hardware */));
|
}
|
||||||
|
|
||||||
|
private void drawFrameSynchronized() {
|
||||||
|
synchronized (mLock) {
|
||||||
|
drawFrameInternal();
|
||||||
}
|
}
|
||||||
drawFrame(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private DisplayInfo getDisplayInfo() {
|
private void drawFrameInternal() {
|
||||||
mDisplay.getDisplayInfo(mTmpDisplayInfo);
|
if (mSurfaceHolder == null) {
|
||||||
return mTmpDisplayInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drawFrame(boolean forceRedraw) {
|
|
||||||
if (!mImageCanvasWallpaperRenderer.isSurfaceHolderLoaded()) {
|
|
||||||
Log.e(TAG, "attempt to draw a frame without a valid surface");
|
Log.e(TAG, "attempt to draw a frame without a valid surface");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// load the wallpaper if not already done
|
||||||
if (!isBitmapLoaded()) {
|
if (!isBitmapLoaded()) {
|
||||||
// ensure that we load the wallpaper.
|
loadWallpaperAndDrawFrameInternal();
|
||||||
// if the wallpaper is currently loading, this call will have no effect.
|
|
||||||
loadWallpaper(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
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 {
|
} else {
|
||||||
if (mBitmap != null) {
|
mBitmapUsages++;
|
||||||
mBitmap.recycle();
|
|
||||||
}
|
// drawing is done on the main thread
|
||||||
mBitmap = bitmap;
|
mMainExecutor.execute(() -> {
|
||||||
|
drawFrameOnCanvas(mBitmap);
|
||||||
|
reportEngineShown(false);
|
||||||
|
unloadBitmapIfNotUsed();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isBitmapLoaded() {
|
@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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Trace.endSection();
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
boolean isBitmapLoaded() {
|
||||||
return mBitmap != null && !mBitmap.isRecycled();
|
return mBitmap != null && !mBitmap.isRecycled();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void unloadBitmapIfNotUsed() {
|
||||||
* Loads the wallpaper on background thread and schedules updating the surface frame,
|
mBackgroundExecutor.execute(this::unloadBitmapIfNotUsedSynchronized);
|
||||||
* 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 ");
|
|
||||||
}
|
|
||||||
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 unloadWallpaper() {
|
private void unloadBitmapIfNotUsedSynchronized() {
|
||||||
if (mLoader != null) {
|
synchronized (mLock) {
|
||||||
mLoader.cancel(false);
|
mBitmapUsages -= 1;
|
||||||
mLoader = null;
|
if (mBitmapUsages <= 0) {
|
||||||
|
mBitmapUsages = 0;
|
||||||
|
unloadBitmapInternal();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
if (mBitmap != null) {
|
||||||
mBitmap.recycle();
|
mBitmap.recycle();
|
||||||
}
|
}
|
||||||
@@ -750,12 +727,133 @@ public class ImageWallpaper extends WallpaperService {
|
|||||||
final Surface surface = getSurfaceHolder().getSurface();
|
final Surface surface = getSurfaceHolder().getSurface();
|
||||||
surface.hwuiDestroy();
|
surface.hwuiDestroy();
|
||||||
mWallpaperManager.forgetLoadedWallpaper();
|
mWallpaperManager.forgetLoadedWallpaper();
|
||||||
|
Trace.endSection();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scheduleUnloadWallpaper() {
|
private void loadWallpaperAndDrawFrameInternal() {
|
||||||
Handler handler = getMainThreadHandler();
|
Trace.beginSection("ImageWallpaper.CanvasEngine#loadWallpaper");
|
||||||
handler.removeCallbacks(mUnloadWallpaperCallback);
|
boolean loadSuccess = false;
|
||||||
handler.postDelayed(mUnloadWallpaperCallback, DELAY_FORGET_WALLPAPER);
|
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
|
@Override
|
||||||
@@ -764,13 +862,46 @@ public class ImageWallpaper extends WallpaperService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisplayChanged(int displayId) {
|
public void onDisplayRemoved(int displayId) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisplayRemoved(int displayId) {
|
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
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,14 +16,23 @@
|
|||||||
|
|
||||||
package com.android.systemui.wallpapers;
|
package com.android.systemui.wallpapers;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static com.google.common.truth.Truth.assertWithMessage;
|
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.doReturn;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.spy;
|
import static org.mockito.Mockito.spy;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.mockito.hamcrest.MockitoHamcrest.intThat;
|
||||||
|
|
||||||
import android.app.WallpaperManager;
|
import android.app.WallpaperManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -31,17 +40,25 @@ import android.content.res.Configuration;
|
|||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.ColorSpace;
|
import android.graphics.ColorSpace;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.hardware.display.DisplayManager;
|
||||||
import android.hardware.display.DisplayManagerGlobal;
|
import android.hardware.display.DisplayManagerGlobal;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.test.suitebuilder.annotation.SmallTest;
|
|
||||||
import android.testing.AndroidTestingRunner;
|
import android.testing.AndroidTestingRunner;
|
||||||
import android.testing.TestableLooper;
|
import android.testing.TestableLooper;
|
||||||
import android.view.Display;
|
import android.view.Display;
|
||||||
import android.view.DisplayInfo;
|
import android.view.DisplayInfo;
|
||||||
|
import android.view.Surface;
|
||||||
import android.view.SurfaceHolder;
|
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.SysuiTestCase;
|
||||||
import com.android.systemui.flags.FeatureFlags;
|
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 com.android.systemui.wallpapers.gl.ImageWallpaperRenderer;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
@@ -56,7 +73,6 @@ import java.util.concurrent.CountDownLatch;
|
|||||||
@SmallTest
|
@SmallTest
|
||||||
@RunWith(AndroidTestingRunner.class)
|
@RunWith(AndroidTestingRunner.class)
|
||||||
@TestableLooper.RunWithLooper
|
@TestableLooper.RunWithLooper
|
||||||
@Ignore
|
|
||||||
public class ImageWallpaperTest extends SysuiTestCase {
|
public class ImageWallpaperTest extends SysuiTestCase {
|
||||||
private static final int LOW_BMP_WIDTH = 128;
|
private static final int LOW_BMP_WIDTH = 128;
|
||||||
private static final int LOW_BMP_HEIGHT = 128;
|
private static final int LOW_BMP_HEIGHT = 128;
|
||||||
@@ -65,45 +81,87 @@ public class ImageWallpaperTest extends SysuiTestCase {
|
|||||||
private static final int DISPLAY_WIDTH = 1920;
|
private static final int DISPLAY_WIDTH = 1920;
|
||||||
private static final int DISPLAY_HEIGHT = 1080;
|
private static final int DISPLAY_HEIGHT = 1080;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private WindowManager mWindowManager;
|
||||||
|
@Mock
|
||||||
|
private WindowMetrics mWindowMetrics;
|
||||||
|
@Mock
|
||||||
|
private DisplayManager mDisplayManager;
|
||||||
|
@Mock
|
||||||
|
private WallpaperManager mWallpaperManager;
|
||||||
@Mock
|
@Mock
|
||||||
private SurfaceHolder mSurfaceHolder;
|
private SurfaceHolder mSurfaceHolder;
|
||||||
@Mock
|
@Mock
|
||||||
|
private Surface mSurface;
|
||||||
|
@Mock
|
||||||
private Context mMockContext;
|
private Context mMockContext;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private Bitmap mWallpaperBitmap;
|
private Bitmap mWallpaperBitmap;
|
||||||
|
private int mBitmapWidth = 1;
|
||||||
|
private int mBitmapHeight = 1;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private Handler mHandler;
|
private Handler mHandler;
|
||||||
@Mock
|
@Mock
|
||||||
private FeatureFlags mFeatureFlags;
|
private FeatureFlags mFeatureFlags;
|
||||||
|
|
||||||
|
FakeSystemClock mFakeSystemClock = new FakeSystemClock();
|
||||||
|
FakeExecutor mFakeMainExecutor = new FakeExecutor(mFakeSystemClock);
|
||||||
|
FakeExecutor mFakeBackgroundExecutor = new FakeExecutor(mFakeSystemClock);
|
||||||
|
|
||||||
private CountDownLatch mEventCountdown;
|
private CountDownLatch mEventCountdown;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
allowTestableLooperAsMainThread();
|
allowTestableLooperAsMainThread();
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
mEventCountdown = new CountDownLatch(1);
|
//mEventCountdown = new CountDownLatch(1);
|
||||||
|
|
||||||
WallpaperManager wallpaperManager = mock(WallpaperManager.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);
|
||||||
|
|
||||||
|
// 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);
|
Resources resources = mock(Resources.class);
|
||||||
|
|
||||||
when(mMockContext.getSystemService(WallpaperManager.class)).thenReturn(wallpaperManager);
|
|
||||||
when(mMockContext.getResources()).thenReturn(resources);
|
|
||||||
when(resources.getConfiguration()).thenReturn(mock(Configuration.class));
|
when(resources.getConfiguration()).thenReturn(mock(Configuration.class));
|
||||||
|
when(mMockContext.getResources()).thenReturn(resources);
|
||||||
DisplayInfo displayInfo = new DisplayInfo();
|
DisplayInfo displayInfo = new DisplayInfo();
|
||||||
displayInfo.logicalWidth = DISPLAY_WIDTH;
|
displayInfo.logicalWidth = DISPLAY_WIDTH;
|
||||||
displayInfo.logicalHeight = DISPLAY_HEIGHT;
|
displayInfo.logicalHeight = DISPLAY_HEIGHT;
|
||||||
when(mMockContext.getDisplay()).thenReturn(
|
when(mMockContext.getDisplay()).thenReturn(
|
||||||
new Display(mock(DisplayManagerGlobal.class), 0, displayInfo, (Resources) null));
|
new Display(mock(DisplayManagerGlobal.class), 0, displayInfo, (Resources) null));
|
||||||
|
}
|
||||||
|
|
||||||
when(wallpaperManager.getBitmap(false)).thenReturn(mWallpaperBitmap);
|
private void setBitmapDimensions(int bitmapWidth, int bitmapHeight) {
|
||||||
when(mWallpaperBitmap.getColorSpace()).thenReturn(ColorSpace.get(ColorSpace.Named.SRGB));
|
mBitmapWidth = bitmapWidth;
|
||||||
when(mWallpaperBitmap.getConfig()).thenReturn(Bitmap.Config.ARGB_8888);
|
mBitmapHeight = bitmapHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImageWallpaper createImageWallpaper() {
|
private ImageWallpaper createImageWallpaper() {
|
||||||
return new ImageWallpaper(mFeatureFlags) {
|
return new ImageWallpaper(mFeatureFlags, mFakeBackgroundExecutor, mFakeMainExecutor) {
|
||||||
@Override
|
@Override
|
||||||
public Engine onCreateEngine() {
|
public Engine onCreateEngine() {
|
||||||
return new GLEngine(mHandler) {
|
return new GLEngine(mHandler) {
|
||||||
@@ -130,6 +188,7 @@ public class ImageWallpaperTest extends SysuiTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Ignore
|
||||||
public void testBitmapWallpaper_normal() {
|
public void testBitmapWallpaper_normal() {
|
||||||
// Will use a image wallpaper with dimensions DISPLAY_WIDTH x DISPLAY_WIDTH.
|
// 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.
|
// Then we expect the surface size will be also DISPLAY_WIDTH x DISPLAY_WIDTH.
|
||||||
@@ -140,6 +199,7 @@ public class ImageWallpaperTest extends SysuiTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Ignore
|
||||||
public void testBitmapWallpaper_low_resolution() {
|
public void testBitmapWallpaper_low_resolution() {
|
||||||
// Will use a image wallpaper with dimensions BMP_WIDTH x BMP_HEIGHT.
|
// 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.
|
// Then we expect the surface size will be also BMP_WIDTH x BMP_HEIGHT.
|
||||||
@@ -150,6 +210,7 @@ public class ImageWallpaperTest extends SysuiTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Ignore
|
||||||
public void testBitmapWallpaper_too_small() {
|
public void testBitmapWallpaper_too_small() {
|
||||||
// Will use a image wallpaper with dimensions INVALID_BMP_WIDTH x INVALID_BMP_HEIGHT.
|
// 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.
|
// 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);
|
ImageWallpaper.GLEngine engineSpy = spy(wallpaperEngine);
|
||||||
|
|
||||||
when(mWallpaperBitmap.getWidth()).thenReturn(bmpWidth);
|
setBitmapDimensions(bmpWidth, bmpHeight);
|
||||||
when(mWallpaperBitmap.getHeight()).thenReturn(bmpHeight);
|
|
||||||
|
|
||||||
ImageWallpaperRenderer renderer = new ImageWallpaperRenderer(mMockContext);
|
ImageWallpaperRenderer renderer = new ImageWallpaperRenderer(mMockContext);
|
||||||
doReturn(renderer).when(engineSpy).getRendererInstance();
|
doReturn(renderer).when(engineSpy).getRendererInstance();
|
||||||
@@ -177,4 +237,116 @@ public class ImageWallpaperTest extends SysuiTestCase {
|
|||||||
assertWithMessage("setFixedSizeAllowed should have been called.").that(
|
assertWithMessage("setFixedSizeAllowed should have been called.").that(
|
||||||
mEventCountdown.getCount()).isEqualTo(0);
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user