Merge "Extract crop hint rect from source wallpaper image"
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 7a0e7f6..fa0fbd1 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -35,10 +35,16 @@
* 'which' is some combination of:
* FLAG_SET_SYSTEM
* FLAG_SET_LOCK
+ *
+ * A 'null' cropHint rectangle is explicitly permitted as a sentinel for "whatever
+ * the source image's bounding rect is."
+ *
+ * The completion callback's "onWallpaperChanged()" method is invoked when the
+ * new wallpaper content is ready to display.
*/
ParcelFileDescriptor setWallpaper(String name, in String callingPackage,
- out Bundle extras, int which);
-
+ in Rect cropHint, out Bundle extras, int which, IWallpaperManagerCallback completion);
+
/**
* Set the live wallpaper. This only affects the system wallpaper.
*/
@@ -54,14 +60,14 @@
*/
ParcelFileDescriptor getWallpaper(IWallpaperManagerCallback cb,
out Bundle outParams);
-
+
/**
* If the current system wallpaper is a live wallpaper component, return the
* information about that wallpaper. Otherwise, if it is a static image,
* simply return null.
*/
WallpaperInfo getWallpaperInfo();
-
+
/**
* Clear the system wallpaper.
*/
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index f103576..b0ffd21 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -64,6 +64,8 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* Provides access to the system wallpaper. With WallpaperManager, you can
@@ -770,18 +772,26 @@
return 0;
}
final Bundle result = new Bundle();
+ final WallpaperSetCompletion completion = new WallpaperSetCompletion();
try {
Resources resources = mContext.getResources();
/* Set the wallpaper to the default values */
ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(
"res:" + resources.getResourceName(resid),
- mContext.getOpPackageName(), result, which);
+ mContext.getOpPackageName(), null, result, which, completion);
if (fd != null) {
FileOutputStream fos = null;
+ boolean ok = false;
try {
fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
copyStreamToWallpaperFile(resources.openRawResource(resid), fos);
+ // The 'close()' is the trigger for any server-side image manipulation,
+ // so we must do that before waiting for completion.
+ fos.close();
+ completion.waitForCompletion();
} finally {
+ // Might be redundant but completion shouldn't wait unless the write
+ // succeeded; this is a fallback if it threw past the close+wait.
IoUtils.closeQuietly(fos);
}
}
@@ -876,14 +886,17 @@
return 0;
}
final Bundle result = new Bundle();
+ final WallpaperSetCompletion completion = new WallpaperSetCompletion();
try {
ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
- mContext.getOpPackageName(), result, which);
+ mContext.getOpPackageName(), visibleCropHint, result, which, completion);
if (fd != null) {
FileOutputStream fos = null;
try {
fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
fullImage.compress(Bitmap.CompressFormat.PNG, 90, fos);
+ fos.close();
+ completion.waitForCompletion();
} finally {
IoUtils.closeQuietly(fos);
}
@@ -990,14 +1003,17 @@
return 0;
}
final Bundle result = new Bundle();
+ final WallpaperSetCompletion completion = new WallpaperSetCompletion();
try {
ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
- mContext.getOpPackageName(), result, which);
+ mContext.getOpPackageName(), visibleCropHint, result, which, completion);
if (fd != null) {
FileOutputStream fos = null;
try {
fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
copyStreamToWallpaperFile(bitmapData, fos);
+ fos.close();
+ completion.waitForCompletion();
} finally {
IoUtils.closeQuietly(fos);
}
@@ -1385,4 +1401,28 @@
return null;
}
+
+ // Private completion callback for setWallpaper() synchronization
+ private class WallpaperSetCompletion extends IWallpaperManagerCallback.Stub {
+ final CountDownLatch mLatch;
+
+ public WallpaperSetCompletion() {
+ mLatch = new CountDownLatch(1);
+ }
+
+ public void waitForCompletion() {
+ try {
+ mLatch.await(30, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ // This might be legit: the crop may take a very long time. Don't sweat
+ // it in that case; we are okay with display lagging behind in order to
+ // keep the caller from locking up indeterminately.
+ }
+ }
+
+ @Override
+ public void onWallpaperChanged() throws RemoteException {
+ mLatch.countDown();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 39983dd..c7d7096 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -42,6 +42,9 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapRegionDecoder;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Binder;
@@ -71,6 +74,7 @@
import android.view.IWindowManager;
import android.view.WindowManager;
+import java.io.BufferedOutputStream;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
@@ -96,7 +100,7 @@
public class WallpaperManagerService extends IWallpaperManager.Stub {
static final String TAG = "WallpaperManagerService";
- static final boolean DEBUG = false;
+ static final boolean DEBUG = true;
final Object mLock = new Object[0];
@@ -106,7 +110,8 @@
*/
static final long MIN_WALLPAPER_CRASH_TIME = 10000;
static final int MAX_WALLPAPER_COMPONENT_LOG_LENGTH = 128;
- static final String WALLPAPER = "wallpaper";
+ static final String WALLPAPER = "wallpaper_orig";
+ static final String WALLPAPER_CROP = "wallpaper";
static final String WALLPAPER_INFO = "wallpaper_info.xml";
/**
@@ -120,6 +125,7 @@
final WallpaperData mWallpaper;
final File mWallpaperDir;
final File mWallpaperFile;
+ final File mWallpaperCropFile;
final File mWallpaperInfoFile;
public WallpaperObserver(WallpaperData wallpaper) {
@@ -128,6 +134,7 @@
mWallpaperDir = getWallpaperDir(wallpaper.userId);
mWallpaper = wallpaper;
mWallpaperFile = new File(mWallpaperDir, WALLPAPER);
+ mWallpaperCropFile = new File(mWallpaperDir, WALLPAPER_CROP);
mWallpaperInfoFile = new File(mWallpaperDir, WALLPAPER_INFO);
}
@@ -136,8 +143,10 @@
if (path == null) {
return;
}
+ final boolean written = (event == CLOSE_WRITE || event == MOVED_TO);
+ final File changedFile = new File(mWallpaperDir, path);
+
synchronized (mLock) {
- File changedFile = new File(mWallpaperDir, path);
if (mWallpaperFile.equals(changedFile)
|| mWallpaperInfoFile.equals(changedFile)) {
// changing the wallpaper means we'll need to back up the new one
@@ -148,22 +157,111 @@
}
if (mWallpaperFile.equals(changedFile)) {
notifyCallbacksLocked(mWallpaper);
- final boolean written = (event == CLOSE_WRITE || event == MOVED_TO);
if (mWallpaper.wallpaperComponent == null
|| event != CLOSE_WRITE // includes the MOVED_TO case
|| mWallpaper.imageWallpaperPending) {
if (written) {
+ // The image source has finished writing the source image,
+ // so we now produce the crop rect (in the background), and
+ // only publish the new displayable (sub)image as a result
+ // of that work.
+ generateCrop(mWallpaper);
mWallpaper.imageWallpaperPending = false;
+ if (mWallpaper.setComplete != null) {
+ try {
+ mWallpaper.setComplete.onWallpaperChanged();
+ } catch (RemoteException e) {
+ // if this fails we don't really care; the setting app may just
+ // have crashed and that sort of thing is a fact of life.
+ }
+ }
+ bindWallpaperComponentLocked(mImageWallpaper, true,
+ false, mWallpaper, null);
+ saveSettingsLocked(mWallpaper);
}
- bindWallpaperComponentLocked(mImageWallpaper, true,
- false, mWallpaper, null);
- saveSettingsLocked(mWallpaper);
}
}
}
}
}
+ /**
+ * Once a new wallpaper has been written via setWallpaper(...), it needs to be cropped
+ * for display.
+ */
+ private void generateCrop(WallpaperData wallpaper) {
+ boolean success = false;
+ boolean needCrop = false;
+
+ // Analyse the source; needed in multiple cases
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(wallpaper.wallpaperFile.getAbsolutePath(), options);
+
+ // Legacy case uses an empty crop rect here, so we just preserve the
+ // source image verbatim
+ if (!wallpaper.cropHint.isEmpty()) {
+ // ...clamp the crop rect to the measured bounds...
+ wallpaper.cropHint.right = Math.min(wallpaper.cropHint.right, options.outWidth);
+ wallpaper.cropHint.bottom = Math.min(wallpaper.cropHint.bottom, options.outHeight);
+ // ...and don't bother cropping if what we're left with is identity
+ needCrop = (options.outHeight >= wallpaper.cropHint.height()
+ && options.outWidth >= wallpaper.cropHint.width());
+ }
+
+ if (!needCrop) {
+ // Simple case: the nominal crop is at least as big as the source image,
+ // so we take the whole thing and just copy the image file directly.
+ if (DEBUG) {
+ Slog.v(TAG, "Null crop of new wallpaper; copying");
+ }
+ success = FileUtils.copyFile(wallpaper.wallpaperFile, wallpaper.cropFile);
+ if (!success) {
+ wallpaper.cropFile.delete();
+ // TODO: fall back to default wallpaper in this case
+ }
+ } else {
+ // Fancy case: the crop is a subrect of the source
+ FileOutputStream f = null;
+ BufferedOutputStream bos = null;
+ try {
+ BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(
+ wallpaper.wallpaperFile.getAbsolutePath(), false);
+ Bitmap cropped = decoder.decodeRegion(wallpaper.cropHint, null);
+ decoder.recycle();
+
+ if (cropped == null) {
+ Slog.e(TAG, "Could not decode new wallpaper");
+ } else {
+ f = new FileOutputStream(wallpaper.cropFile);
+ bos = new BufferedOutputStream(f, 32*1024);
+ cropped.compress(Bitmap.CompressFormat.PNG, 90, bos);
+ bos.flush(); // don't rely on the implicit flush-at-close when noting success
+ success = true;
+ }
+ } catch (IOException e) {
+ if (DEBUG) {
+ Slog.e(TAG, "I/O error decoding crop: " + e.getMessage());
+ }
+ } finally {
+ IoUtils.closeQuietly(bos);
+ IoUtils.closeQuietly(f);
+ }
+ }
+
+ if (!success) {
+ Slog.e(TAG, "Unable to apply new wallpaper");
+ wallpaper.cropFile.delete();
+ }
+
+ if (wallpaper.cropFile.exists()) {
+ boolean didRestorecon = SELinux.restorecon(wallpaper.cropFile.getAbsoluteFile());
+ if (DEBUG) {
+ Slog.v(TAG, "restorecon() of crop file returned " + didRestorecon);
+ }
+ }
+ }
+
final Context mContext;
final IWindowManager mIWindowManager;
final IPackageManager mIPackageManager;
@@ -191,7 +289,8 @@
int userId;
- File wallpaperFile;
+ final File wallpaperFile;
+ final File cropFile;
/**
* Client is currently writing a new image wallpaper.
@@ -199,6 +298,11 @@
boolean imageWallpaperPending;
/**
+ * Callback once the set + crop is finished
+ */
+ IWallpaperManagerCallback setComplete;
+
+ /**
* Resource name if using a picture from the wallpaper gallery
*/
String name = "";
@@ -232,11 +336,26 @@
int width = -1;
int height = -1;
+ /**
+ * The crop hint supplied for displaying a subset of the source image
+ */
+ final Rect cropHint = new Rect(0, 0, 0, 0);
+
final Rect padding = new Rect(0, 0, 0, 0);
WallpaperData(int userId) {
this.userId = userId;
wallpaperFile = new File(getWallpaperDir(userId), WALLPAPER);
+ cropFile = new File(getWallpaperDir(userId), WALLPAPER_CROP);
+ }
+
+ // Only called in single-threaded boot sequence mode
+ boolean ensureCropExists() {
+ // if the crop file is not present, copy over the source image to use verbatim
+ if (!cropFile.exists()) {
+ return FileUtils.copyFile(wallpaperFile, cropFile);
+ }
+ return true;
}
}
@@ -524,6 +643,9 @@
public void systemRunning() {
if (DEBUG) Slog.v(TAG, "systemReady");
WallpaperData wallpaper = mWallpaperMap.get(UserHandle.USER_SYSTEM);
+ if (!wallpaper.ensureCropExists()) {
+ clearWallpaperLocked(false, UserHandle.USER_SYSTEM, null);
+ }
switchWallpaper(wallpaper, null);
wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper);
wallpaper.wallpaperObserver.startWatching();
@@ -602,6 +724,8 @@
onStoppingUser(userId);
File wallpaperFile = new File(getWallpaperDir(userId), WALLPAPER);
wallpaperFile.delete();
+ File cropFile = new File(getWallpaperDir(userId), WALLPAPER_CROP);
+ cropFile.delete();
File wallpaperInfoFile = new File(getWallpaperDir(userId), WALLPAPER_INFO);
wallpaperInfoFile.delete();
}
@@ -653,9 +777,9 @@
if (wallpaper == null) {
return;
}
- File f = new File(getWallpaperDir(userId), WALLPAPER);
- if (f.exists()) {
- f.delete();
+ if (wallpaper.wallpaperFile.exists()) {
+ wallpaper.wallpaperFile.delete();
+ wallpaper.cropFile.delete();
}
final long ident = Binder.clearCallingIdentity();
try {
@@ -844,11 +968,10 @@
outParams.putInt("height", wallpaper.height);
}
wallpaper.callbacks.register(cb);
- File f = new File(getWallpaperDir(wallpaperUserId), WALLPAPER);
- if (!f.exists()) {
+ if (!wallpaper.cropFile.exists()) {
return null;
}
- return ParcelFileDescriptor.open(f, MODE_READ_ONLY);
+ return ParcelFileDescriptor.open(wallpaper.cropFile, MODE_READ_ONLY);
} catch (FileNotFoundException e) {
/* Shouldn't happen as we check to see if the file exists */
Slog.w(TAG, "Error getting wallpaper", e);
@@ -869,8 +992,8 @@
}
@Override
- public ParcelFileDescriptor setWallpaper(String name, String callingPackage, Bundle extras,
- int which) {
+ public ParcelFileDescriptor setWallpaper(String name, String callingPackage,
+ Rect cropHint, Bundle extras, int which, IWallpaperManagerCallback completion) {
checkPermission(android.Manifest.permission.SET_WALLPAPER);
if (which == 0) {
@@ -881,6 +1004,17 @@
return null;
}
+ // "null" means the no-op crop, preserving the full input image
+ if (cropHint == null) {
+ cropHint = new Rect(0, 0, 0, 0);
+ } else {
+ if (cropHint.isEmpty()
+ || cropHint.left < 0
+ || cropHint.top < 0) {
+ return null;
+ }
+ }
+
synchronized (mLock) {
if (DEBUG) Slog.v(TAG, "setWallpaper");
int userId = UserHandle.getCallingUserId();
@@ -890,6 +1024,8 @@
ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name, wallpaper, extras);
if (pfd != null) {
wallpaper.imageWallpaperPending = true;
+ wallpaper.setComplete = completion;
+ wallpaper.cropHint.set(cropHint);
}
return pfd;
} finally {
@@ -1196,6 +1332,12 @@
out.attribute(null, "id", Integer.toString(wallpaper.wallpaperId));
out.attribute(null, "width", Integer.toString(wallpaper.width));
out.attribute(null, "height", Integer.toString(wallpaper.height));
+
+ out.attribute(null, "cropLeft", Integer.toString(wallpaper.cropHint.left));
+ out.attribute(null, "cropTop", Integer.toString(wallpaper.cropHint.top));
+ out.attribute(null, "cropRight", Integer.toString(wallpaper.cropHint.right));
+ out.attribute(null, "cropBottom", Integer.toString(wallpaper.cropHint.bottom));
+
if (wallpaper.padding.left != 0) {
out.attribute(null, "paddingLeft", Integer.toString(wallpaper.padding.left));
}
@@ -1208,6 +1350,7 @@
if (wallpaper.padding.bottom != 0) {
out.attribute(null, "paddingBottom", Integer.toString(wallpaper.padding.bottom));
}
+
out.attribute(null, "name", wallpaper.name);
if (wallpaper.wallpaperComponent != null
&& !wallpaper.wallpaperComponent.equals(mImageWallpaper)) {
@@ -1304,6 +1447,10 @@
wallpaper.width = Integer.parseInt(parser.getAttributeValue(null, "width"));
wallpaper.height = Integer.parseInt(parser
.getAttributeValue(null, "height"));
+ wallpaper.cropHint.left = getAttributeInt(parser, "cropLeft", 0);
+ wallpaper.cropHint.top = getAttributeInt(parser, "cropTop", 0);
+ wallpaper.cropHint.right = getAttributeInt(parser, "cropRight", 0);
+ wallpaper.cropHint.bottom = getAttributeInt(parser, "cropBottom", 0);
wallpaper.padding.left = getAttributeInt(parser, "paddingLeft", 0);
wallpaper.padding.top = getAttributeInt(parser, "paddingTop", 0);
wallpaper.padding.right = getAttributeInt(parser, "paddingRight", 0);
@@ -1322,6 +1469,7 @@
if (DEBUG) {
Slog.v(TAG, "mWidth:" + wallpaper.width);
Slog.v(TAG, "mHeight:" + wallpaper.height);
+ Slog.v(TAG, "cropRect:" + wallpaper.cropHint);
Slog.v(TAG, "mName:" + wallpaper.name);
Slog.v(TAG, "mNextWallpaperComponent:"
+ wallpaper.nextWallpaperComponent);
@@ -1348,6 +1496,7 @@
if (!success) {
wallpaper.width = -1;
wallpaper.height = -1;
+ wallpaper.cropHint.set(0, 0, 0, 0);
wallpaper.padding.set(0, 0, 0, 0);
wallpaper.name = "";
} else {
@@ -1368,6 +1517,11 @@
if (wallpaper.height < baseSize) {
wallpaper.height = baseSize;
}
+ // and crop, if not previously specified
+ if (wallpaper.cropHint.width() <= 0
+ || wallpaper.cropHint.height() <= 0) {
+ wallpaper.cropHint.set(0, 0, wallpaper.width, wallpaper.height);
+ }
}
private int getMaximumSizeDimension() {
@@ -1431,6 +1585,7 @@
}
}
+ // Restore the named resource bitmap to both source + crop files
boolean restoreNamedResourceLocked(WallpaperData wallpaper) {
if (wallpaper.name.length() > 4 && "res:".equals(wallpaper.name.substring(0, 4))) {
String resName = wallpaper.name.substring(4);
@@ -1456,6 +1611,7 @@
int resId = -1;
InputStream res = null;
FileOutputStream fos = null;
+ FileOutputStream cos = null;
try {
Context c = mContext.createPackageContext(pkg, Context.CONTEXT_RESTRICTED);
Resources r = c.getResources();
@@ -1469,13 +1625,16 @@
res = r.openRawResource(resId);
if (wallpaper.wallpaperFile.exists()) {
wallpaper.wallpaperFile.delete();
+ wallpaper.cropFile.delete();
}
fos = new FileOutputStream(wallpaper.wallpaperFile);
+ cos = new FileOutputStream(wallpaper.cropFile);
byte[] buffer = new byte[32768];
int amt;
while ((amt=res.read(buffer)) > 0) {
fos.write(buffer, 0, amt);
+ cos.write(buffer, 0, amt);
}
// mWallpaperObserver will notice the close and send the change broadcast
@@ -1491,8 +1650,12 @@
IoUtils.closeQuietly(res);
if (fos != null) {
FileUtils.sync(fos);
- IoUtils.closeQuietly(fos);
}
+ if (cos != null) {
+ FileUtils.sync(cos);
+ }
+ IoUtils.closeQuietly(fos);
+ IoUtils.closeQuietly(cos);
}
}
}
@@ -1520,6 +1683,7 @@
pw.print(wallpaper.width);
pw.print(" mHeight=");
pw.println(wallpaper.height);
+ pw.print(" mCropHint="); pw.println(wallpaper.cropHint);
pw.print(" mPadding="); pw.println(wallpaper.padding);
pw.print(" mName="); pw.println(wallpaper.name);
pw.print(" mWallpaperComponent="); pw.println(wallpaper.wallpaperComponent);