Wallpaper Local color sampling

Test: Integration tests will be in a different CL
Fixes: 173046221
Change-Id: I5755525f90f4c7445cbfd337057a34cfb1f3c4fe
This commit is contained in:
Jay Aliomer
2020-12-15 18:57:14 -05:00
parent 2fd099f165
commit 9c12bcc5d8
11 changed files with 963 additions and 8 deletions

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2021 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 android.app;
import android.app.WallpaperColors;
import android.graphics.RectF;
/**
* @hide
*/
oneway interface ILocalWallpaperColorConsumer {
void onColorsChanged(in RectF area, in WallpaperColors colors);
}

View File

@@ -17,9 +17,11 @@
package android.app;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.app.IWallpaperManagerCallback;
import android.app.ILocalWallpaperColorConsumer;
import android.app.WallpaperInfo;
import android.content.ComponentName;
import android.app.WallpaperColors;
@@ -161,6 +163,18 @@ interface IWallpaperManager {
*/
WallpaperColors getWallpaperColors(int which, int userId, int displayId);
/**
* @hide
*/
void removeOnLocalColorsChangedListener(
in ILocalWallpaperColorConsumer callback, int which, int userId, int displayId);
/**
* @hide
*/
void addOnLocalColorsChangedListener(in ILocalWallpaperColorConsumer callback,
in List<RectF> regions, int which, int userId, int displayId);
/**
* Register a callback to receive color updates from a display
*/

View File

@@ -66,6 +66,7 @@ import android.os.RemoteException;
import android.os.StrictMode;
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
import android.view.Display;
@@ -107,6 +108,8 @@ public class WallpaperManager {
private static boolean DEBUG = false;
private float mWallpaperXStep = -1;
private float mWallpaperYStep = -1;
private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
new RectF(0, 0, 1, 1);
/** {@hide} */
private static final String PROP_WALLPAPER = "ro.config.wallpaper";
@@ -309,6 +312,8 @@ public class WallpaperManager {
private int mCachedWallpaperUserId;
private Bitmap mDefaultWallpaper;
private Handler mMainLooperHandler;
private ArrayMap<LocalWallpaperColorConsumer, ILocalWallpaperColorConsumer>
mLocalColorCallbacks = new ArrayMap<>();
Globals(IWallpaperManager service, Looper looper) {
mService = service;
@@ -350,6 +355,40 @@ public class WallpaperManager {
}
}
private ILocalWallpaperColorConsumer wrap(LocalWallpaperColorConsumer callback) {
ILocalWallpaperColorConsumer callback2 = new ILocalWallpaperColorConsumer.Stub() {
@Override
public void onColorsChanged(RectF area, WallpaperColors colors) {
callback.onColorsChanged(area, colors);
}
};
mLocalColorCallbacks.put(callback, callback2);
return callback2;
}
public void addOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback,
@NonNull List<RectF> regions, int which, int userId, int displayId) {
try {
mService.addOnLocalColorsChangedListener(wrap(callback) , regions, which,
userId, displayId);
} catch (RemoteException e) {
// Can't get colors, connection lost.
}
}
public void removeOnColorsChangedListener(
@NonNull LocalWallpaperColorConsumer callback, int which, int userId,
int displayId) {
ILocalWallpaperColorConsumer callback2 = mLocalColorCallbacks.remove(callback);
if (callback2 == null) return;
try {
mService.removeOnLocalColorsChangedListener(
callback2, which, userId, displayId);
} catch (RemoteException e) {
// Can't get colors, connection lost.
}
}
/**
* Stop listening to wallpaper color events.
*
@@ -1056,6 +1095,29 @@ public class WallpaperManager {
return sGlobals.getWallpaperColors(which, userId, mContext.getDisplayId());
}
/**
* @hide
*/
public void addOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback,
List<RectF> regions) throws IllegalArgumentException {
for (RectF region : regions) {
if (!LOCAL_COLOR_BOUNDS.contains(region)) {
throw new IllegalArgumentException("Regions must be within bounds "
+ LOCAL_COLOR_BOUNDS);
}
}
sGlobals.addOnColorsChangedListener(callback, regions, FLAG_SYSTEM,
mContext.getUserId(), mContext.getDisplayId());
}
/**
* @hide
*/
public void removeOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback) {
sGlobals.removeOnColorsChangedListener(callback, FLAG_SYSTEM, mContext.getUserId(),
mContext.getDisplayId());
}
/**
* Version of {@link #getWallpaperFile(int)} that can access the wallpaper data
* for a given user. The caller must hold the INTERACT_ACROSS_USERS_FULL
@@ -2202,4 +2264,18 @@ public class WallpaperManager {
onColorsChanged(colors, which);
}
}
/**
* Callback to update a consumer with a local color change
* @hide
*/
public interface LocalWallpaperColorConsumer {
/**
* Gets called when a color of an area gets updated
* @param area
* @param colors
*/
void onColorsChanged(RectF area, WallpaperColors colors);
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright (C) 2020 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 android.service.wallpaper;
import android.app.WallpaperColors;
import android.graphics.Bitmap;
import android.graphics.RectF;
import android.util.ArrayMap;
import android.util.ArraySet;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
/**
* This class represents a page of a launcher page used by the wallpaper
* @hide
*/
public class EngineWindowPage {
private Bitmap mScreenShot;
private volatile long mLastUpdateTime;
private Set<RectF> mCallbackAreas = new ArraySet<>();
private Map<RectF, WallpaperColors> mRectFColors = new ArrayMap<>();
/** should be locked extrnally */
public void addArea(RectF area) {
mCallbackAreas.add(area);
}
/** should be locked extrnally */
public void addWallpaperColors(RectF area, WallpaperColors colors) {
mCallbackAreas.add(area);
mRectFColors.put(area, colors);
}
/** get screenshot bitmap */
public Bitmap getBitmap() {
if (mScreenShot == null || mScreenShot.isRecycled()) return null;
return mScreenShot;
}
/** remove callbacks for an area */
public void removeArea(RectF area) {
mCallbackAreas.remove(area);
mRectFColors.remove(area);
}
/** set the last time the screenshot was updated */
public void setLastUpdateTime(long lastUpdateTime) {
mLastUpdateTime = lastUpdateTime;
}
/** get last screenshot time */
public long getLastUpdateTime() {
return mLastUpdateTime;
}
/** get colors for an area */
public WallpaperColors getColors(RectF rect) {
return mRectFColors.get(rect);
}
/** set the new bitmap version */
public void setBitmap(Bitmap screenShot) {
mScreenShot = screenShot;
}
/** get areas of interest */
public Set<RectF> getAreas() {
return mCallbackAreas;
}
/** run operations on this page */
public synchronized void execSync(Consumer<EngineWindowPage> run) {
run.accept(this);
}
/** nullify the area color */
public void removeColor(RectF colorArea) {
mRectFColors.remove(colorArea);
}
}

View File

@@ -16,6 +16,7 @@
package android.service.wallpaper;
import android.graphics.RectF;
import android.os.ParcelFileDescriptor;
import android.service.wallpaper.IWallpaperEngine;
import android.app.WallpaperColors;
@@ -28,4 +29,5 @@ interface IWallpaperConnection {
void engineShown(IWallpaperEngine engine);
ParcelFileDescriptor setWallpaper(String name);
void onWallpaperColorsChanged(in WallpaperColors colors, int displayId);
void onLocalWallpaperColorsChanged(in RectF area, in WallpaperColors colors, int displayId);
}

View File

@@ -16,7 +16,10 @@
package android.service.wallpaper;
import android.app.ILocalWallpaperColorConsumer;
import android.app.WallpaperColors;
import android.graphics.Rect;
import android.graphics.RectF;
import android.view.MotionEvent;
import android.os.Bundle;
@@ -39,4 +42,6 @@ oneway interface IWallpaperEngine {
void destroy();
void setZoomOut(float scale);
void scalePreview(in Rect positionInWindow);
void removeLocalColorsAreas(in List<RectF> regions);
void addLocalColorsAreas(in List<RectF> regions);
}

View File

@@ -23,6 +23,7 @@ import static android.graphics.Matrix.MSKEW_Y;
import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
@@ -42,6 +43,7 @@ import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
@@ -53,6 +55,8 @@ import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
import android.util.ArraySet;
import android.util.Log;
import android.util.MergedConfiguration;
import android.view.Display;
@@ -66,6 +70,8 @@ import android.view.InputEventReceiver;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.MotionEvent;
import android.view.PixelCopy;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceHolder;
import android.view.View;
@@ -83,7 +89,10 @@ import com.android.internal.view.BaseSurfaceHolder;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier;
/**
@@ -115,7 +124,12 @@ public abstract class WallpaperService extends Service {
public static final String SERVICE_META_DATA = "android.service.wallpaper";
static final String TAG = "WallpaperService";
static final boolean DEBUG = false;
static final boolean DEBUG = true;
static final float MIN_PAGE_ALLOWED_MARGIN = .05f;
private static final int MIN_BITMAP_SCREENSHOT_WIDTH = 64;
private static final long DEFAULT_UPDATE_SCREENSHOT_DURATION = 60 * 1000; //Once per minute
private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
new RectF(0, 0, 1, 1);
private static final int DO_ATTACH = 10;
private static final int DO_DETACH = 20;
@@ -134,6 +148,8 @@ public abstract class WallpaperService extends Service {
private static final int MSG_REQUEST_WALLPAPER_COLORS = 10050;
private static final int MSG_ZOOM = 10100;
private static final int MSG_SCALE_PREVIEW = 10110;
private static final List<Float> PROHIBITED_STEPS = Arrays.asList(0f, Float.POSITIVE_INFINITY,
Float.NEGATIVE_INFINITY);
private static final int NOTIFY_COLORS_RATE_LIMIT_MS = 1000;
@@ -158,6 +174,14 @@ public abstract class WallpaperService extends Service {
*/
public class Engine {
IWallpaperEngineWrapper mIWallpaperEngine;
final ArraySet<RectF> mLocalColorAreas = new ArraySet<>(4);
final ArraySet<RectF> mLocalColorsToAdd = new ArraySet<>(4);
// 2D matrix [x][y] to represent a page of a portion of a window
EngineWindowPage[] mWindowPages = new EngineWindowPage[1];
Bitmap mLastScreenshot;
int mLastWindowPage = -1;
float mLastPageOffset = 0;
// Copies from mIWallpaperEngine.
HandlerCaller mCaller;
@@ -409,8 +433,8 @@ public abstract class WallpaperService extends Service {
*/
@VisibleForTesting
public Engine(Supplier<Long> clockFunction, Handler handler) {
mClockFunction = clockFunction;
mHandler = handler;
mClockFunction = clockFunction;
mHandler = handler;
}
/**
@@ -447,6 +471,19 @@ public abstract class WallpaperService extends Service {
return mReportedVisible;
}
/**
* Return whether the wallpaper is capable of extracting local colors in a rectangle area,
* Must implement without calling super:
* {@link #addLocalColorsAreas(List)}
* {@link #removeLocalColorsAreas(List)}
* When local colors change, call {@link #notifyLocalColorsChanged(List, List)}
* See {@link com.android.systemui.ImageWallpaper} for an example
* @hide
*/
public boolean supportsLocalColorExtraction() {
return false;
}
/**
* Returns true if this engine is running in preview mode -- that is,
* it is being shown to the user before they select it as the actual
@@ -691,6 +728,8 @@ public abstract class WallpaperService extends Service {
Log.w(TAG, "Can't notify system because wallpaper connection "
+ "was not established.");
}
resetWindowPages();
processLocalColors(mPendingXOffset, mPendingXOffsetStep);
} catch (RemoteException e) {
Log.w(TAG, "Can't notify system because wallpaper connection was lost.", e);
}
@@ -713,6 +752,28 @@ public abstract class WallpaperService extends Service {
return null;
}
/**
* Send the changed local color areas for the connection
* @param regions
* @param colors
* @hide
*/
public void notifyLocalColorsChanged(@NonNull List<RectF> regions,
@NonNull List<WallpaperColors> colors)
throws RuntimeException {
for (int i = 0; i < regions.size() && i < colors.size() && colors.get(i) != null; i++) {
try {
mConnection.onLocalWallpaperColorsChanged(
regions.get(i),
colors.get(i),
mDisplayContext.getDisplayId()
);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
}
/**
* Sets internal engine state. Only for testing.
* @param created {@code true} or {@code false}.
@@ -1068,7 +1129,9 @@ public abstract class WallpaperService extends Service {
mIsCreating = false;
mSurfaceCreated = true;
if (redrawNeeded) {
resetWindowPages();
mSession.finishDrawing(mWindow, null /* postDrawTransaction */);
processLocalColors(mPendingXOffset, mPendingXOffsetStep);
}
reposition();
mIWallpaperEngine.reportShown();
@@ -1209,6 +1272,7 @@ public abstract class WallpaperService extends Service {
if (!mDestroyed) {
mVisible = visible;
reportVisibility();
if (visible) processLocalColors(mPendingXOffset, mPendingXOffsetStep);
}
}
@@ -1278,6 +1342,405 @@ public abstract class WallpaperService extends Service {
} catch (RemoteException e) {
}
}
// setup local color extraction data
processLocalColors(xOffset, xOffsetStep);
}
private void processLocalColors(float xOffset, float xOffsetStep) {
// implemented by the wallpaper
if (supportsLocalColorExtraction()) return;
if (DEBUG) {
Log.d(TAG, "processLocalColors " + xOffset + " of step "
+ xOffsetStep);
}
//below is the default implementation
if (!validStep(xOffsetStep)) {
if (DEBUG) {
Log.w(TAG, "invalid offset step " + xOffsetStep);
}
return;
}
int xPage = Math.round(xOffset / xOffsetStep);
if (!shouldProcessPage(xPage, xOffset, xOffsetStep)) return;
mLastWindowPage = xPage;
mLastPageOffset = xOffset;
int xPages = Math.round(1 / xOffsetStep);
if (DEBUG) {
Log.d(TAG, "xPages " + xPages + " xPage " + xPage);
Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
}
EngineWindowPage current;
synchronized (mLock) {
if (mWindowPages.length == 0 || (mWindowPages.length != xPages)) {
mWindowPages = new EngineWindowPage[xPages];
initWindowPages(mWindowPages, xOffsetStep);
}
if (mLocalColorsToAdd.size() != 0) {
for (RectF colorArea : mLocalColorsToAdd) {
if (!isValid(colorArea)) continue;
mLocalColorAreas.add(colorArea);
int colorPage = getRectFPage(colorArea, xOffsetStep);
EngineWindowPage currentPage = mWindowPages[colorPage];
if (currentPage == null) {
currentPage = new EngineWindowPage();
currentPage.addArea(colorArea);
mWindowPages[colorPage] = currentPage;
} else {
currentPage.setLastUpdateTime(0);
currentPage.removeColor(colorArea);
}
}
mLocalColorsToAdd.clear();
}
if (xPage >= mWindowPages.length) {
if (DEBUG) {
Log.e(TAG, "error xPage >= mWindowPages.length page: " + xPage);
Log.e(TAG, "error on page " + xPage + " out of " + xPages);
Log.e(TAG,
"error on xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
}
xPage = mWindowPages.length - 1;
}
current = mWindowPages[xPage];
if (current == null) {
if (DEBUG) Log.d(TAG, "making page " + xPage + " out of " + xPages);
if (DEBUG) {
Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
}
current = new EngineWindowPage();
mWindowPages[xPage] = current;
}
}
updatePage(current, xPage, xPages, xOffsetStep);
}
private boolean shouldProcessPage(int xPage, float xOffset, float xOffsetStep) {
float pageCenter = xOffsetStep * xPage + xOffsetStep / 2;
return (pageCenter > xOffset && pageCenter < mLastPageOffset)
|| (pageCenter < xOffset && pageCenter > mLastPageOffset);
}
private void initWindowPages(EngineWindowPage[] windowPages, float step) {
for (int i = 0; i < windowPages.length; i++) {
windowPages[i] = new EngineWindowPage();
}
mLocalColorAreas.addAll(mLocalColorsToAdd);
mLocalColorsToAdd.clear();
for (RectF area: mLocalColorAreas) {
if (!isValid(area)) {
mLocalColorAreas.remove(area);
continue;
}
int pageNum = getRectFPage(area, step);
windowPages[pageNum].addArea(area);
}
}
void updatePage(EngineWindowPage currentPage, int pageIndx, int numPages,
float xOffsetStep) {
// to save creating a runnable, check twice
long current = System.nanoTime() / 1_000_000;
AtomicLong lapsed = new AtomicLong(current - currentPage.getLastUpdateTime());
if (lapsed.get() < DEFAULT_UPDATE_SCREENSHOT_DURATION) return;
currentPage.execSync((page) -> {
// just in case of race double check
long currentInner = System.nanoTime() / 1_000_000;
lapsed.set(currentInner - page.getLastUpdateTime());
page.setLastUpdateTime(currentInner);
});
long lastUpdate = currentPage.getLastUpdateTime();
if (lapsed.get() < DEFAULT_UPDATE_SCREENSHOT_DURATION) return;
Surface surface = mSurfaceHolder.getSurface();
boolean widthIsLarger =
mSurfaceControl.getWidth() > mSurfaceControl.getHeight();
int smaller = widthIsLarger ? mSurfaceControl.getWidth()
: mSurfaceControl.getHeight();
float ratio = (float) MIN_BITMAP_SCREENSHOT_WIDTH / (float) smaller;
int width = (int) (ratio * mSurfaceControl.getWidth());
int height = (int) (ratio * mSurfaceControl.getHeight());
if (width <= 0 || height <= 0) {
return;
}
Bitmap screenShot = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
final Bitmap finalScreenShot = screenShot;
Trace.beginSection("WallpaperService#pixelCopy");
PixelCopy.request(surface, screenShot, (res) -> {
Trace.endSection();
if (DEBUG) Log.d(TAG, "result of pixel copy is " + res);
if (res != PixelCopy.SUCCESS) {
currentPage.execSync((p) -> {
// reset the time
p.setLastUpdateTime(lastUpdate);
// assign the last bitmap taken for now
p.setBitmap(mLastScreenshot);
});
return;
}
mLastScreenshot = finalScreenShot;
// going to hold this lock for a while
currentPage.execSync((p) -> {
p.setBitmap(finalScreenShot);
});
updatePageColors(currentPage, pageIndx, numPages, xOffsetStep);
}, mHandler);
}
// locked by the passed page
private void updatePageColors(EngineWindowPage page, int pageIndx, int numPages,
float xOffsetStep) {
if (page.getBitmap() == null) return;
if (DEBUG) {
Log.d(TAG, "updatePageColorsLocked for page " + pageIndx + " with areas "
+ page.getAreas().size() + " and bitmap size of "
+ page.getBitmap().getWidth() + " x " + page.getBitmap().getHeight());
}
for (RectF area: page.getAreas()) {
RectF subArea = generateSubRect(area, pageIndx, numPages);
Bitmap b = page.getBitmap();
int x = Math.round(b.getWidth() * subArea.left);
int y = Math.round(b.getHeight() * subArea.top);
int width = Math.round(b.getWidth() * subArea.width());
int height = Math.round(b.getHeight() * subArea.height());
Bitmap target;
try {
target = Bitmap.createBitmap(page.getBitmap(), x, y, width, height);
} catch (Exception e) {
Log.e(TAG, "Error creating page local color bitmap", e);
continue;
}
WallpaperColors color = WallpaperColors.fromBitmap(target);
target.recycle();
WallpaperColors currentColor = page.getColors(area);
if (DEBUG) {
Log.d(TAG, "getting local bitmap area x " + x + " y " + y
+ " width " + width + " height " + height + " for sub area " + subArea
+ " and with page " + pageIndx + " of " + numPages);
}
if (currentColor == null || !color.equals(currentColor)) {
page.addWallpaperColors(area, color);
if (DEBUG) {
Log.d(TAG, "onLocalWallpaperColorsChanged"
+ " local color callback for area" + area + " for page " + pageIndx
+ " of " + numPages);
}
try {
mConnection.onLocalWallpaperColorsChanged(area, color,
mDisplayContext.getDisplayId());
} catch (RemoteException e) {
Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e);
}
}
}
}
private RectF generateSubRect(RectF in, int pageInx, int numPages) {
float minLeft = (float) (pageInx) / (float) (numPages);
float maxRight = (float) (pageInx + 1) / (float) (numPages);
float left = in.left;
float right = in.right;
// bound rect
if (left < minLeft) left = minLeft;
if (right > maxRight) right = maxRight;
// scale up the sub area then trim
left = (left * (float) numPages) % 1f;
right = (right * (float) numPages) % 1f;
if (right == 0f) {
right = 1f;
}
return new RectF(left, in.top, right, in.bottom);
}
private void resetWindowPages() {
if (supportsLocalColorExtraction()) return;
mLastWindowPage = -1;
synchronized (mLock) {
for (int i = 0; i < mWindowPages.length; i++) {
EngineWindowPage page = mWindowPages[i];
if (page != null) {
page.execSync((p) -> {
p.setLastUpdateTime(0L);
});
}
}
}
}
private int getRectFPage(RectF area, float step) {
if (!isValid(area)) return 0;
if (!validStep(step)) return 0;
int pages = Math.round(1 / step);
int page = Math.round(area.centerX() * pages);
if (page == pages) return pages - 1;
if (page == mWindowPages.length) page = mWindowPages.length - 1;
return page;
}
/**
* Add local colors areas of interest
* @param regions list of areas
* @hide
*/
public void addLocalColorsAreas(@NonNull List<RectF> regions) {
if (supportsLocalColorExtraction()) return;
if (DEBUG) {
Log.d(TAG, "addLocalColorsAreas adding local color areas " + regions);
}
float step = mPendingXOffsetStep;
List<WallpaperColors> colors = getLocalWallpaperColors(regions);
synchronized (mLock) {
if (!validStep(step)) {
mLocalColorsToAdd.addAll(regions);
return;
}
for (int i = 0; i < regions.size(); i++) {
RectF area = regions.get(i);
if (!isValid(area)) continue;
int pageInx = getRectFPage(area, step);
// no page should be null
EngineWindowPage page = mWindowPages[pageInx];
if (page != null) {
mLocalColorAreas.add(area);
page.addArea(area);
WallpaperColors color = colors.get(i);
if (color != null && !color.equals(page.getColors(area))) {
page.execSync(p -> {
p.addWallpaperColors(area, color);
});
}
} else {
mLocalColorsToAdd.add(area);
}
}
}
for (int i = 0; i < colors.size() && colors.get(i) != null; i++) {
try {
mConnection.onLocalWallpaperColorsChanged(regions.get(i), colors.get(i),
mDisplayContext.getDisplayId());
} catch (RemoteException e) {
Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e);
return;
}
}
}
/**
* Remove local colors areas of interest if they exist
* @param regions list of areas
* @hide
*/
public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
if (supportsLocalColorExtraction()) return;
synchronized (mLock) {
float step = mPendingXOffsetStep;
mLocalColorsToAdd.removeAll(regions);
mLocalColorAreas.removeAll(regions);
if (!validStep(step)) {
return;
}
for (int i = 0; i < regions.size(); i++) {
RectF area = regions.get(i);
if (!isValid(area)) continue;
int pageInx = getRectFPage(area, step);
// no page should be null
EngineWindowPage page = mWindowPages[pageInx];
if (page != null) {
page.execSync(p -> {
p.removeArea(area);
});
}
}
}
}
private @NonNull List<WallpaperColors> getLocalWallpaperColors(@NonNull List<RectF> areas) {
ArrayList<WallpaperColors> colors = new ArrayList<>(areas.size());
float step = mPendingXOffsetStep;
if (!validStep(step)) {
if (DEBUG) Log.d(TAG, "invalid step size " + step);
step = 1.0f;
}
for (int i = 0; i < areas.size(); i++) {
RectF currentArea = areas.get(i);
EngineWindowPage page;
RectF area;
int pageIndx;
synchronized (mLock) {
pageIndx = getRectFPage(currentArea, step);
if (mWindowPages.length == 0 || pageIndx < 0
|| pageIndx > mWindowPages.length || !isValid(currentArea)) {
colors.add(null);
continue;
}
area = generateSubRect(currentArea, pageIndx, mWindowPages.length);
page = mWindowPages[pageIndx];
}
if (page == null) {
colors.add(null);
continue;
}
float finalStep = step;
int finalPageIndx = pageIndx;
Bitmap screenShot = page.getBitmap();
if (screenShot == null || screenShot.isRecycled()) {
if (DEBUG) {
Log.d(TAG, "invalid bitmap " + screenShot
+ " for page " + finalPageIndx);
}
page.setLastUpdateTime(0);
colors.add(null);
continue;
}
Bitmap b = screenShot;
Rect subImage = new Rect(
Math.round(area.left * b.getWidth() / finalStep),
Math.round(area.top * b.getHeight()),
Math.round(area.right * b.getWidth() / finalStep),
Math.round(area.bottom * b.getHeight())
);
subImage = fixRect(b, subImage);
if (DEBUG) {
Log.d(TAG, "getting subbitmap of " + subImage.toString()
+ " for RectF " + area.toString()
+ " screenshot width " + screenShot.getWidth() + " height "
+ screenShot.getHeight());
}
Bitmap colorImg = Bitmap.createBitmap(screenShot,
subImage.left, subImage.top, subImage.width(), subImage.height());
if (DEBUG) {
Log.d(TAG, "created bitmap " + colorImg.getWidth() + ", "
+ colorImg.getHeight());
}
WallpaperColors color = WallpaperColors.fromBitmap(colorImg);
colors.add(color);
}
return colors;
}
// fix the rect to be included within the bounds of the bitmap
private Rect fixRect(Bitmap b, Rect r) {
r.left = r.left >= r.right || r.left >= b.getWidth() || r.left > 0
? 0
: r.left;
r.right = r.left >= r.right || r.right > b.getWidth()
? b.getWidth()
: r.right;
return r;
}
private boolean validStep(float step) {
return !PROHIBITED_STEPS.contains(step) && step > 0. && step <= 1.;
}
void doCommand(WallpaperCommand cmd) {
@@ -1371,6 +1834,16 @@ public abstract class WallpaperService extends Service {
};
}
private boolean isValid(RectF area) {
boolean valid = area.bottom > area.top && area.left < area.right
&& LOCAL_COLOR_BOUNDS.contains(area);
return valid;
}
private boolean inRectFRange(float number) {
return number >= 0f && number <= 1f;
}
class IWallpaperEngineWrapper extends IWallpaperEngine.Stub
implements HandlerCaller.Callback {
private final HandlerCaller mCaller;
@@ -1477,6 +1950,14 @@ public abstract class WallpaperService extends Service {
mCaller.sendMessage(msg);
}
public void addLocalColorsAreas(List<RectF> regions) {
mEngine.addLocalColorsAreas(regions);
}
public void removeLocalColorsAreas(List<RectF> regions) {
mEngine.removeLocalColorsAreas(regions);
}
public void destroy() {
Message msg = mCaller.obtainMessage(DO_DETACH);
mCaller.sendMessage(msg);
@@ -1516,14 +1997,15 @@ public abstract class WallpaperService extends Service {
}
switch (message.what) {
case DO_ATTACH: {
Engine engine = onCreateEngine();
mEngine = engine;
try {
mConnection.attachEngine(this, mDisplayId);
} catch (RemoteException e) {
engine.detach();
Log.w(TAG, "Wallpaper host disappeared", e);
return;
}
Engine engine = onCreateEngine();
mEngine = engine;
mActiveEngines.add(engine);
engine.attach(this);
return;

View File

@@ -16,7 +16,10 @@
package com.android.systemui;
import android.app.WallpaperColors;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
@@ -26,13 +29,16 @@ import android.util.Log;
import android.util.Size;
import android.view.SurfaceHolder;
import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.glwallpaper.EglHelper;
import com.android.systemui.glwallpaper.GLWallpaperRenderer;
import com.android.systemui.glwallpaper.ImageWallpaperRenderer;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
@@ -45,8 +51,13 @@ public class ImageWallpaper extends WallpaperService {
// We delayed destroy render context that subsequent render requests have chance to cancel it.
// This is to avoid destroying then recreating render context in a very short time.
private static final int DELAY_FINISH_RENDERING = 1000;
private static final @android.annotation.NonNull RectF LOCAL_COLOR_BOUNDS =
new RectF(0, 0, 1, 1);
private static final boolean DEBUG = false;
private ArrayList<RectF> mLocalColorsToAdd = new ArrayList<>();
private HandlerThread mWorker;
// scaled down version
private Bitmap mMiniBitmap;
@Inject
public ImageWallpaper() {
@@ -70,6 +81,7 @@ public class ImageWallpaper extends WallpaperService {
super.onDestroy();
mWorker.quitSafely();
mWorker = null;
mMiniBitmap = null;
}
class GLEngine extends Engine {
@@ -80,7 +92,7 @@ public class ImageWallpaper extends WallpaperService {
@VisibleForTesting
static final int MIN_SURFACE_HEIGHT = 64;
private GLWallpaperRenderer mRenderer;
private ImageWallpaperRenderer mRenderer;
private EglHelper mEglHelper;
private final Runnable mFinishRenderingTask = this::finishRendering;
private boolean mNeedRedraw;
@@ -101,6 +113,12 @@ public class ImageWallpaper extends WallpaperService {
setFixedSizeAllowed(true);
setOffsetNotificationsEnabled(false);
updateSurfaceSize();
mMiniBitmap = null;
if (mWorker == null || mWorker.getThreadHandler() == null) {
updateMiniBitmap();
} else {
mWorker.getThreadHandler().post(this::updateMiniBitmap);
}
}
EglHelper getEglHelperInstance() {
@@ -111,6 +129,20 @@ public class ImageWallpaper extends WallpaperService {
return new ImageWallpaperRenderer(getDisplayContext());
}
private void updateMiniBitmap() {
mRenderer.useBitmap(b -> {
int size = Math.min(b.getWidth(), b.getHeight());
float scale = 1.0f;
if (size > MIN_SURFACE_WIDTH) {
scale = (float) MIN_SURFACE_WIDTH / (float) size;
}
mMiniBitmap = Bitmap.createScaledBitmap(b, Math.round(scale * b.getWidth()),
Math.round(scale * b.getHeight()), false);
computeAndNotifyLocalColors(mLocalColorsToAdd, mMiniBitmap);
mLocalColorsToAdd.clear();
});
}
private void updateSurfaceSize() {
SurfaceHolder holder = getSurfaceHolder();
Size frameSize = mRenderer.reportSurfaceSize();
@@ -126,6 +158,7 @@ public class ImageWallpaper extends WallpaperService {
@Override
public void onDestroy() {
mMiniBitmap = null;
mWorker.getThreadHandler().post(() -> {
mRenderer.finish();
mRenderer = null;
@@ -134,6 +167,61 @@ public class ImageWallpaper extends WallpaperService {
});
}
@Override
public boolean supportsLocalColorExtraction() {
return true;
}
@Override
public void addLocalColorsAreas(@NonNull List<RectF> regions) {
mWorker.getThreadHandler().post(() -> {
Bitmap bitmap = mMiniBitmap;
if (bitmap == null) {
mLocalColorsToAdd.addAll(regions);
} else {
computeAndNotifyLocalColors(regions, bitmap);
}
});
}
private void computeAndNotifyLocalColors(@NonNull List<RectF> regions, Bitmap b) {
List<WallpaperColors> colors = getLocalWallpaperColors(regions, b);
try {
notifyLocalColorsChanged(regions, colors);
} catch (RuntimeException e) {
Log.e(TAG, e.getMessage(), e);
}
}
@Override
public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
// No-OP
}
private List<WallpaperColors> getLocalWallpaperColors(@NonNull List<RectF> areas,
Bitmap b) {
List<WallpaperColors> colors = new ArrayList<>(areas.size());
for (int i = 0; i < areas.size(); i++) {
RectF area = areas.get(i);
if (area == null || !LOCAL_COLOR_BOUNDS.contains(area)) {
colors.add(null);
continue;
}
Rect subImage = new Rect(
Math.round(area.left * b.getWidth()),
Math.round(area.top * b.getHeight()),
Math.round(area.right * b.getWidth()),
Math.round(area.bottom * b.getHeight()));
Bitmap colorImg = Bitmap.createBitmap(b,
subImage.left, subImage.top, subImage.width(), subImage.height());
WallpaperColors color = WallpaperColors.fromBitmap(colorImg);
colors.add(color);
}
return colors;
}
@Override
public void onSurfaceCreated(SurfaceHolder holder) {
if (mWorker == null) return;

View File

@@ -58,6 +58,14 @@ public class ImageWallpaperRenderer implements GLWallpaperRenderer {
mWallpaper = new ImageGLWallpaper(mProgram);
}
/**
* @hide
* @return
*/
public void useBitmap(Consumer<Bitmap> c) {
mTexture.use(c);
}
@Override
public boolean isWcgContent() {
return mTexture.isWcgContent();

View File

@@ -100,6 +100,7 @@ public class ImageWallpaperTest extends SysuiTestCase {
return new ImageWallpaper() {
@Override
public Engine onCreateEngine() {
onCreate();
return new GLEngine(mHandler) {
@Override
public Context getDisplayContext() {

View File

@@ -31,6 +31,7 @@ import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.ILocalWallpaperColorConsumer;
import android.app.IWallpaperManager;
import android.app.IWallpaperManagerCallback;
import android.app.PendingIntent;
@@ -59,6 +60,7 @@ import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.RectF;
import android.hardware.display.DisplayManager;
import android.os.Binder;
import android.os.Bundle;
@@ -85,7 +87,10 @@ import android.service.wallpaper.IWallpaperService;
import android.service.wallpaper.WallpaperService;
import android.system.ErrnoException;
import android.system.Os;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -105,7 +110,6 @@ import com.android.server.EventLogTags;
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.SystemService.TargetUser;
import com.android.server.pm.UserManagerInternal;
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.wm.WindowManagerInternal;
@@ -136,6 +140,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
private static final String TAG = "WallpaperManagerService";
private static final boolean DEBUG = false;
private static final boolean DEBUG_LIVE = true;
private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
new RectF(0, 0, 1, 1);
public static class Lifecycle extends SystemService {
private IWallpaperManagerService mService;
@@ -866,6 +872,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
private final SparseBooleanArray mUserRestorecon = new SparseBooleanArray();
private int mCurrentUserId = UserHandle.USER_NULL;
private boolean mInAmbientMode;
private ArrayMap<IBinder, ArraySet<RectF>> mLocalColorCallbackAreas =
new ArrayMap<>();
private ArrayMap<RectF, RemoteCallbackList<ILocalWallpaperColorConsumer>>
mLocalColorAreaCallbacks = new ArrayMap<>();
private ArrayMap<Integer, ArraySet<RectF>> mLocalColorDisplayIdAreas = new ArrayMap<>();
private ArrayMap<IBinder, Integer> mLocalColorCallbackDisplayId = new ArrayMap<>();
static class WallpaperData {
@@ -1275,6 +1287,32 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
@Override
public void onLocalWallpaperColorsChanged(RectF area, WallpaperColors colors,
int displayId) {
forEachDisplayConnector(displayConnector -> {
if (displayConnector.mDisplayId == displayId) {
RemoteCallbackList<ILocalWallpaperColorConsumer> callbacks;
ArrayMap<IBinder, Integer> callbackDisplayIds;
synchronized (mLock) {
callbacks = mLocalColorAreaCallbacks.get(area);
callbackDisplayIds = new ArrayMap<>(mLocalColorCallbackDisplayId);
}
if (callbacks == null) return;
callbacks.broadcast(c -> {
try {
int targetDisplayId =
callbackDisplayIds.get(c.asBinder());
if (targetDisplayId == displayId) c.onColorsChanged(area, colors);
} catch (RemoteException e) {
e.printStackTrace();
}
});
}
});
}
@Override
public void onServiceDisconnected(ComponentName name) {
synchronized (mLock) {
@@ -1437,6 +1475,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
} catch (RemoteException e) {
Slog.w(TAG, "Failed to request wallpaper colors", e);
}
ArraySet<RectF> areas = mLocalColorDisplayIdAreas.get(displayId);
if (areas != null && areas.size() != 0) {
try {
connector.mEngine.addLocalColorsAreas(new ArrayList<>(areas));
} catch (RemoteException e) {
Slog.w(TAG, "Failed to register local colors areas", e);
}
}
}
}
@@ -2340,6 +2387,115 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
return true;
}
private IWallpaperEngine getEngine(int which, int userId, int displayId) {
WallpaperData wallpaperData = findWallpaperAtDisplay(userId, displayId);
if (wallpaperData == null) return null;
IWallpaperEngine engine = null;
synchronized (mLock) {
for (int i = 0; i < wallpaperData.connection.mDisplayConnector.size(); i++) {
int id = wallpaperData.connection.mDisplayConnector.get(i).mDisplayId;
int currentWhich = wallpaperData.connection.mDisplayConnector.get(i).mDisplayId;
if (id != displayId && currentWhich != which) continue;
engine = wallpaperData.connection.mDisplayConnector.get(i).mEngine;
break;
}
}
return engine;
}
@Override
public void addOnLocalColorsChangedListener(@NonNull ILocalWallpaperColorConsumer callback,
@NonNull List<RectF> regions, int which, int userId, int displayId)
throws RemoteException {
if (which != FLAG_LOCK && which != FLAG_SYSTEM) {
throw new IllegalArgumentException("which should be either FLAG_LOCK or FLAG_SYSTEM");
}
IWallpaperEngine engine = getEngine(which, userId, displayId);
if (engine == null) return;
ArrayList<RectF> validAreas = new ArrayList<>(regions.size());
synchronized (mLock) {
ArraySet<RectF> areas = mLocalColorCallbackAreas.get(callback);
if (areas == null) areas = new ArraySet<>(regions.size());
areas.addAll(regions);
mLocalColorCallbackAreas.put(callback.asBinder(), areas);
}
for (int i = 0; i < regions.size(); i++) {
if (!LOCAL_COLOR_BOUNDS.contains(regions.get(i))) {
continue;
}
RemoteCallbackList callbacks;
synchronized (mLock) {
callbacks = mLocalColorAreaCallbacks.get(
regions.get(i));
if (callbacks == null) {
callbacks = new RemoteCallbackList();
mLocalColorAreaCallbacks.put(regions.get(i), callbacks);
}
mLocalColorCallbackDisplayId.put(callback.asBinder(), displayId);
ArraySet<RectF> displayAreas = mLocalColorDisplayIdAreas.get(displayId);
if (displayAreas == null) {
displayAreas = new ArraySet<>(1);
mLocalColorDisplayIdAreas.put(displayId, displayAreas);
}
displayAreas.add(regions.get(i));
}
validAreas.add(regions.get(i));
callbacks.register(callback);
}
engine.addLocalColorsAreas(validAreas);
}
@Override
public void removeOnLocalColorsChangedListener(
@NonNull ILocalWallpaperColorConsumer callback, int which, int userId,
int displayId) throws RemoteException {
if (which != FLAG_LOCK && which != FLAG_SYSTEM) {
throw new IllegalArgumentException("which should be either FLAG_LOCK or FLAG_SYSTEM");
}
final UserHandle callingUser = Binder.getCallingUserHandle();
if (callingUser.getIdentifier() != userId) {
throw new SecurityException("calling user id does not match");
}
final long identity = Binder.clearCallingIdentity();
ArrayList<RectF> removeAreas = new ArrayList<>();
ArrayList<Pair<RemoteCallbackList, ILocalWallpaperColorConsumer>>
callbacksToRemove = new ArrayList<>();
try {
synchronized (mLock) {
ArraySet<RectF> areas = mLocalColorCallbackAreas.remove(callback.asBinder());
mLocalColorCallbackDisplayId.remove(callback.asBinder());
if (areas == null) areas = new ArraySet<>();
for (RectF area : areas) {
RemoteCallbackList callbacks = mLocalColorAreaCallbacks.get(area);
if (callbacks == null) continue;
callbacksToRemove.add(new Pair<>(callbacks, callback));
if (callbacks.getRegisteredCallbackCount() == 0) {
mLocalColorAreaCallbacks.remove(area);
removeAreas.add(area);
}
ArraySet<RectF> displayAreas = mLocalColorDisplayIdAreas.get(displayId);
if (displayAreas != null) {
displayAreas.remove(area);
}
}
}
for (int i = 0; i < callbacksToRemove.size(); i++) {
Pair<RemoteCallbackList, ILocalWallpaperColorConsumer>
pair = callbacksToRemove.get(i);
pair.first.unregister(pair.second);
}
} catch (Exception e) {
// ignore any exception
} finally {
Binder.restoreCallingIdentity(identity);
}
if (removeAreas.size() == 0) return;
IWallpaperEngine engine = getEngine(which, userId, displayId);
if (engine == null) return;
engine.removeLocalColorsAreas(removeAreas);
}
@Override
public WallpaperColors getWallpaperColors(int which, int userId, int displayId)
throws RemoteException {