summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Aurélien Pomini <pomini@google.com> 2023-05-17 15:24:53 +0000
committer Aurélien Pomini <pomini@google.com> 2024-01-19 14:08:01 +0000
commitd9915e4a91d7c9b2a232134e4518f929eb6880a0 (patch)
tree33a5a0eaf7b6e9ad7be366f82ee4f15ce69c480a
parentae30e283781894e19841d26f3805001c0c5f24d8 (diff)
New methods setBitmap, setStream, getBitmapCrop, getWallpaperColors
See go/wallpaper-multi-crop Flag: ACONFIG com.android.window.flags.multi_crop DEVELOPMENT Bug: 270726737 Test: atest WallpaperManagerTest Test: atest WallpaperControllerTests Test: atest WallpaperBackupAgentTest Test: quite a lot of manual testing Change-Id: Ic0bb0bd450c02aad01ea59fbfdaab415fb9838f9
-rw-r--r--core/java/android/app/IWallpaperManager.aidl48
-rw-r--r--core/java/android/app/WallpaperManager.java333
-rw-r--r--data/etc/services.core.protolog.json6
-rw-r--r--packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java67
-rw-r--r--packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java11
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperCropper.java399
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperData.java31
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperDataParser.java158
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java105
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java167
-rw-r--r--services/core/java/com/android/server/wm/WallpaperController.java137
-rw-r--r--services/core/java/com/android/server/wm/WallpaperWindowToken.java16
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerInternal.java17
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java25
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java13
16 files changed, 1397 insertions, 139 deletions
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index d7d654672abc..5acb9b5cf9ae 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -16,6 +16,7 @@
package android.app;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Bundle;
@@ -26,6 +27,8 @@ import android.app.WallpaperInfo;
import android.content.ComponentName;
import android.app.WallpaperColors;
+import java.util.List;
+
/** @hide */
interface IWallpaperManager {
@@ -39,15 +42,21 @@ interface IWallpaperManager {
* 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."
+ * 'screenOrientations' and 'crops' define how the wallpaper will be positioned for
+ * different screen orientations. If some screen orientations are missing, crops for these
+ * orientations will be added by the system.
+ *
+ * If 'screenOrientations' is null, 'crops' can be null or a singleton list. The system will
+ * fit the provided crop (or the whole image, if 'crops' is 'null') for the current device
+ * orientation, and add crops for the missing orientations.
*
* The completion callback's "onWallpaperChanged()" method is invoked when the
* new wallpaper content is ready to display.
*/
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.SET_WALLPAPER)")
ParcelFileDescriptor setWallpaper(String name, in String callingPackage,
- in Rect cropHint, boolean allowBackup, out Bundle extras, int which,
- IWallpaperManagerCallback completion, int userId);
+ in int[] screenOrientations, in List<Rect> crops, boolean allowBackup,
+ out Bundle extras, int which, IWallpaperManagerCallback completion, int userId);
/**
* Set the live wallpaper.
@@ -78,6 +87,30 @@ interface IWallpaperManager {
boolean getCropped);
/**
+ * For a given user and a list of display sizes, get a list of Rect representing the
+ * area of the current wallpaper that is displayed for each display size.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL)")
+ @SuppressWarnings(value={"untyped-collection"})
+ List getBitmapCrops(in List<Point> displaySizes, int which, boolean originalBitmap, int userId);
+
+ /**
+ * Return how a bitmap of a given size would be cropped for a given list of display sizes when
+ * set with the given suggested crops.
+ * @hide
+ */
+ @SuppressWarnings(value={"untyped-collection"})
+ List getFutureBitmapCrops(in Point bitmapSize, in List<Point> displaySizes,
+ in int[] screenOrientations, in List<Rect> crops);
+
+ /**
+ * Return how a bitmap of a given size would be cropped when set with the given suggested crops.
+ * @hide
+ */
+ @SuppressWarnings(value={"untyped-collection"})
+ Rect getBitmapCrop(in Point bitmapSize, in int[] screenOrientations, in List<Rect> crops);
+
+ /**
* Retrieve the given user's current wallpaper ID of the given kind.
*/
int getWallpaperIdForUser(int which, int userId);
@@ -245,11 +278,4 @@ interface IWallpaperManager {
* @hide
*/
boolean isStaticWallpaper(int which);
-
- /**
- * Temporary method for project b/270726737.
- * Return true if the wallpaper supports different crops for different display dimensions.
- * @hide
- */
- boolean isMultiCropEnabled();
}
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 62db90f79091..63f37f150d33 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -18,11 +18,14 @@ package android.app;
import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE;
import static android.Manifest.permission.READ_WALLPAPER_INTERNAL;
+import static android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+import static com.android.window.flags.Flags.FLAG_MULTI_CROP;
import static com.android.window.flags.Flags.multiCrop;
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -58,6 +61,7 @@ import android.graphics.ImageDecoder;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
+import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
@@ -84,6 +88,7 @@ import android.util.ArraySet;
import android.util.Log;
import android.util.MathUtils;
import android.util.Pair;
+import android.util.SparseArray;
import android.view.Display;
import android.view.WindowManagerGlobal;
@@ -104,6 +109,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -289,6 +295,79 @@ public class WallpaperManager {
public static final String EXTRA_FROM_FOREGROUND_APP =
"android.service.wallpaper.extra.FROM_FOREGROUND_APP";
+ /**
+ * The different screen orientations. {@link #getOrientation} provides their exact definition.
+ * This is only used internally by the framework and the WallpaperBackupAgent.
+ * @hide
+ */
+ @IntDef(value = {
+ ORIENTATION_UNKNOWN,
+ PORTRAIT,
+ LANDSCAPE,
+ SQUARE_PORTRAIT,
+ SQUARE_LANDSCAPE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ScreenOrientation {}
+
+ /**
+ * @hide
+ */
+ public static final int ORIENTATION_UNKNOWN = -1;
+
+ /**
+ * Portrait orientation of most screens
+ * @hide
+ */
+ public static final int PORTRAIT = 0;
+
+ /**
+ * Landscape orientation of most screens
+ * @hide
+ */
+ public static final int LANDSCAPE = 1;
+
+ /**
+ * Portrait orientation with similar width and height (e.g. the inner screen of a foldable)
+ * @hide
+ */
+ public static final int SQUARE_PORTRAIT = 2;
+
+ /**
+ * Landscape orientation with similar width and height (e.g. the inner screen of a foldable)
+ * @hide
+ */
+ public static final int SQUARE_LANDSCAPE = 3;
+
+ /**
+ * Converts a (width, height) screen size to a {@link ScreenOrientation}.
+ * @param screenSize the dimensions of a screen
+ * @return the corresponding {@link ScreenOrientation}.
+ * @hide
+ */
+ public static @ScreenOrientation int getOrientation(Point screenSize) {
+ float ratio = ((float) screenSize.x) / screenSize.y;
+ // ratios between 3/4 and 4/3 are considered square
+ return ratio >= 4 / 3f ? LANDSCAPE
+ : ratio > 1f ? SQUARE_LANDSCAPE
+ : ratio > 3 / 4f ? SQUARE_PORTRAIT
+ : PORTRAIT;
+ }
+
+ /**
+ * Get the 90° rotation of a given orientation
+ * @hide
+ */
+ public static @ScreenOrientation int getRotatedOrientation(@ScreenOrientation int orientation) {
+ switch (orientation) {
+ case PORTRAIT: return LANDSCAPE;
+ case LANDSCAPE: return PORTRAIT;
+ case SQUARE_PORTRAIT: return SQUARE_LANDSCAPE;
+ case SQUARE_LANDSCAPE: return SQUARE_PORTRAIT;
+ default: return ORIENTATION_UNKNOWN;
+ }
+ }
+
// flags for which kind of wallpaper to act on
/** @hide */
@@ -867,15 +946,8 @@ public class WallpaperManager {
* @hide
*/
public static boolean isMultiCropEnabled() {
- if (sGlobals == null) {
- sIsMultiCropEnabled = multiCrop();
- }
if (sIsMultiCropEnabled == null) {
- try {
- sIsMultiCropEnabled = sGlobals.mService.isMultiCropEnabled();
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
+ sIsMultiCropEnabled = multiCrop();
}
return sIsMultiCropEnabled;
}
@@ -1502,6 +1574,99 @@ public class WallpaperManager {
}
/**
+ * For the current user, given a list of display sizes, return a list of rectangles representing
+ * the area of the current wallpaper that would be shown for each of these sizes.
+ *
+ * @param displaySizes the display sizes.
+ * @param which wallpaper type. Must be either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}.
+ * @param originalBitmap If true, return areas relative to the original bitmap.
+ * If false, return areas relative to the cropped bitmap.
+ * @return A List of Rect where the Rect is within the cropped/original bitmap, and corresponds
+ * to what is displayed. The Rect may have a larger width/height ratio than the screen
+ * due to parallax. Return {@code null} if the wallpaper is not an ImageWallpaper.
+ * Also return {@code null} when called with which={@link #FLAG_LOCK} if there is a
+ * shared home + lock wallpaper.
+ * @hide
+ */
+ @FlaggedApi(FLAG_MULTI_CROP)
+ @RequiresPermission(READ_WALLPAPER_INTERNAL)
+ @Nullable
+ public List<Rect> getBitmapCrops(@NonNull List<Point> displaySizes,
+ @SetWallpaperFlags int which, boolean originalBitmap) {
+ checkExactlyOneWallpaperFlagSet(which);
+ try {
+ return sGlobals.mService.getBitmapCrops(displaySizes, which, originalBitmap,
+ mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * For preview purposes.
+ * Return how a bitmap of a given size would be cropped for a given list of display sizes, if
+ * it was set as wallpaper via {@link #setBitmapWithCrops(Bitmap, Map, boolean, int)} or
+ * {@link #setStreamWithCrops(InputStream, Map, boolean, int)}.
+ *
+ * @return A List of Rect where the Rect is within the bitmap, and corresponds to what is
+ * displayed for each display size. The Rect may have a larger width/height ratio than
+ * the display due to parallax.
+ * @hide
+ */
+ @FlaggedApi(FLAG_MULTI_CROP)
+ @Nullable
+ public List<Rect> getBitmapCrops(@NonNull Point bitmapSize, @NonNull List<Point> displaySizes,
+ @Nullable Map<Point, Rect> cropHints) {
+ try {
+ if (cropHints == null) cropHints = Map.of();
+ Set<Map.Entry<Point, Rect>> entries = cropHints.entrySet();
+ int[] screenOrientations = entries.stream().mapToInt(entry ->
+ getOrientation(entry.getKey())).toArray();
+ List<Rect> crops = entries.stream().map(Map.Entry::getValue).toList();
+ return sGlobals.mService.getFutureBitmapCrops(bitmapSize, displaySizes,
+ screenOrientations, crops);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * For preview purposes.
+ * Compute the wallpaper colors of the given bitmap, if it was set as wallpaper via
+ * {@link #setBitmapWithCrops(Bitmap, Map, boolean, int)} or
+ * {@link #setStreamWithCrops(InputStream, Map, boolean, int)}.
+ * Return {@code null} if an error occurred and the colors could not be computed.
+ *
+ * @hide
+ */
+ @FlaggedApi(FLAG_MULTI_CROP)
+ @RequiresPermission(SET_WALLPAPER_DIM_AMOUNT)
+ @Nullable
+ public WallpaperColors getWallpaperColors(@NonNull Bitmap bitmap,
+ @Nullable Map<Point, Rect> cropHints) {
+ if (sGlobals.mService == null) {
+ Log.w(TAG, "WallpaperService not running");
+ throw new RuntimeException(new DeadSystemException());
+ }
+ try {
+ if (cropHints == null) cropHints = Map.of();
+ Set<Map.Entry<Point, Rect>> entries = cropHints.entrySet();
+ int[] screenOrientations = entries.stream().mapToInt(entry ->
+ getOrientation(entry.getKey())).toArray();
+ List<Rect> crops = entries.stream().map(Map.Entry::getValue).toList();
+ Point bitmapSize = new Point(bitmap.getWidth(), bitmap.getHeight());
+ Rect crop = sGlobals.mService.getBitmapCrop(bitmapSize, screenOrientations, crops);
+ float dimAmount = getWallpaperDimAmount();
+ Bitmap croppedBitmap = Bitmap.createBitmap(
+ bitmap, crop.left, crop.top, crop.width(), crop.height());
+ WallpaperColors result = WallpaperColors.fromBitmap(croppedBitmap, dimAmount);
+ return result;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* <strong> Important note: </strong>
* <ul>
* <li>Up to version S, this method requires the
@@ -1971,7 +2136,7 @@ public class WallpaperManager {
/* Set the wallpaper to the default values */
ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(
"res:" + resources.getResourceName(resid),
- mContext.getOpPackageName(), null, false, result, which, completion,
+ mContext.getOpPackageName(), null, null, false, result, which, completion,
mContext.getUserId());
if (fd != null) {
FileOutputStream fos = null;
@@ -2089,6 +2254,11 @@ public class WallpaperManager {
public int setBitmap(Bitmap fullImage, Rect visibleCropHint,
boolean allowBackup, @SetWallpaperFlags int which, int userId)
throws IOException {
+ if (multiCrop()) {
+ SparseArray<Rect> cropMap = new SparseArray<>();
+ if (visibleCropHint != null) cropMap.put(ORIENTATION_UNKNOWN, visibleCropHint);
+ return setBitmapWithCrops(fullImage, cropMap, allowBackup, which, userId);
+ }
validateRect(visibleCropHint);
if (sGlobals.mService == null) {
Log.w(TAG, "WallpaperService not running");
@@ -2096,9 +2266,69 @@ public class WallpaperManager {
}
final Bundle result = new Bundle();
final WallpaperSetCompletion completion = new WallpaperSetCompletion();
+ final List<Rect> crops = visibleCropHint == null ? null : List.of(visibleCropHint);
try {
ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
- mContext.getOpPackageName(), visibleCropHint, allowBackup,
+ mContext.getOpPackageName(), null, crops, allowBackup, result, which,
+ completion, userId);
+ 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);
+ }
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return result.getInt(EXTRA_NEW_WALLPAPER_ID, 0);
+ }
+
+ /**
+ * Version of setBitmap that defines how the wallpaper will be positioned for different
+ * display sizes.
+ * Requires permission {@link android.Manifest.permission#SET_WALLPAPER}.
+ * @param cropHints map from screen dimensions to a sub-region of the image to display for those
+ * dimensions. The {@code Rect} sub-region may have a larger width/height ratio
+ * than the screen dimensions to apply a horizontal parallax effect. If the
+ * map is empty or some entries are missing, the system will apply a default
+ * strategy to position the wallpaper for any unspecified screen dimensions.
+ * @hide
+ */
+ @FlaggedApi(FLAG_MULTI_CROP)
+ @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
+ public int setBitmapWithCrops(@Nullable Bitmap fullImage, @NonNull Map<Point, Rect> cropHints,
+ boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
+ SparseArray<Rect> crops = new SparseArray<>();
+ cropHints.forEach((k, v) -> crops.put(getOrientation(k), v));
+ return setBitmapWithCrops(fullImage, crops, allowBackup, which, mContext.getUserId());
+ }
+
+ @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
+ private int setBitmapWithCrops(@Nullable Bitmap fullImage, @NonNull SparseArray<Rect> cropHints,
+ boolean allowBackup, @SetWallpaperFlags int which, int userId) throws IOException {
+ if (sGlobals.mService == null) {
+ Log.w(TAG, "WallpaperService not running");
+ throw new RuntimeException(new DeadSystemException());
+ }
+ int size = cropHints.size();
+ int[] screenOrientations = new int[size];
+ List<Rect> crops = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ screenOrientations[i] = cropHints.keyAt(i);
+ Rect cropHint = cropHints.valueAt(i);
+ validateRect(cropHint);
+ crops.add(cropHint);
+ }
+ final Bundle result = new Bundle();
+ final WallpaperSetCompletion completion = new WallpaperSetCompletion();
+ try {
+ ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
+ mContext.getOpPackageName(), screenOrientations, crops, allowBackup,
result, which, completion, userId);
if (fd != null) {
FileOutputStream fos = null;
@@ -2214,6 +2444,11 @@ public class WallpaperManager {
public int setStream(InputStream bitmapData, Rect visibleCropHint,
boolean allowBackup, @SetWallpaperFlags int which)
throws IOException {
+ if (multiCrop()) {
+ SparseArray<Rect> cropMap = new SparseArray<>();
+ if (visibleCropHint != null) cropMap.put(ORIENTATION_UNKNOWN, visibleCropHint);
+ return setStreamWithCrops(bitmapData, cropMap, allowBackup, which);
+ }
validateRect(visibleCropHint);
if (sGlobals.mService == null) {
Log.w(TAG, "WallpaperService not running");
@@ -2221,10 +2456,11 @@ public class WallpaperManager {
}
final Bundle result = new Bundle();
final WallpaperSetCompletion completion = new WallpaperSetCompletion();
+ final List<Rect> crops = visibleCropHint == null ? null : List.of(visibleCropHint);
try {
ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
- mContext.getOpPackageName(), visibleCropHint, allowBackup,
- result, which, completion, mContext.getUserId());
+ mContext.getOpPackageName(), null, crops, allowBackup, result, which,
+ completion, mContext.getUserId());
if (fd != null) {
FileOutputStream fos = null;
try {
@@ -2244,6 +2480,75 @@ public class WallpaperManager {
}
/**
+ * Version of setStream that defines how the wallpaper will be positioned for different
+ * display sizes.
+ * Requires permission {@link android.Manifest.permission#SET_WALLPAPER}.
+ * @param cropHints map from screen dimensions to a sub-region of the image to display for those
+ * dimensions. The {@code Rect} sub-region may have a larger width/height ratio
+ * than the screen dimensions to apply a horizontal parallax effect. If the
+ * map is empty or some entries are missing, the system will apply a default
+ * strategy to position the wallpaper for any unspecified screen dimensions.
+ * @hide
+ */
+ @FlaggedApi(FLAG_MULTI_CROP)
+ @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
+ public int setStreamWithCrops(InputStream bitmapData, @NonNull Map<Point, Rect> cropHints,
+ boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
+ SparseArray<Rect> crops = new SparseArray<>();
+ cropHints.forEach((k, v) -> crops.put(getOrientation(k), v));
+ return setStreamWithCrops(bitmapData, crops, allowBackup, which);
+ }
+
+ /**
+ * Similar to {@link #setStreamWithCrops(InputStream, Map, boolean, int)}, but using
+ * {@link ScreenOrientation} as keys of the cropHints map. Used for backup & restore, since
+ * WallpaperBackupAgent stores orientations rather than the exact display size.
+ * Requires permission {@link android.Manifest.permission#SET_WALLPAPER}.
+ * @param cropHints map from {@link ScreenOrientation} to a sub-region of the image to display
+ * for that screen orientation.
+ * @hide
+ */
+ @FlaggedApi(FLAG_MULTI_CROP)
+ @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
+ public int setStreamWithCrops(InputStream bitmapData, @NonNull SparseArray<Rect> cropHints,
+ boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
+ if (sGlobals.mService == null) {
+ Log.w(TAG, "WallpaperService not running");
+ throw new RuntimeException(new DeadSystemException());
+ }
+ int size = cropHints.size();
+ int[] screenOrientations = new int[size];
+ List<Rect> crops = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ screenOrientations[i] = cropHints.keyAt(i);
+ Rect cropHint = cropHints.valueAt(i);
+ validateRect(cropHint);
+ crops.add(cropHint);
+ }
+ final Bundle result = new Bundle();
+ final WallpaperSetCompletion completion = new WallpaperSetCompletion();
+ try {
+ ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
+ mContext.getOpPackageName(), screenOrientations, crops, allowBackup,
+ result, which, completion, mContext.getUserId());
+ if (fd != null) {
+ FileOutputStream fos = null;
+ try {
+ fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+ copyStreamToWallpaperFile(bitmapData, fos);
+ fos.close();
+ completion.waitForCompletion();
+ } finally {
+ IoUtils.closeQuietly(fos);
+ }
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return result.getInt(EXTRA_NEW_WALLPAPER_ID, 0);
+ }
+
+ /**
* Return whether any users are currently set to use the wallpaper
* with the given resource ID. That is, their wallpaper has been
* set through {@link #setResource(int)} with the same resource id.
@@ -2499,7 +2804,7 @@ public class WallpaperManager {
* @hide
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT)
+ @RequiresPermission(SET_WALLPAPER_DIM_AMOUNT)
public void setWallpaperDimAmount(@FloatRange (from = 0f, to = 1f) float dimAmount) {
if (sGlobals.mService == null) {
Log.w(TAG, "WallpaperService not running");
@@ -2519,7 +2824,7 @@ public class WallpaperManager {
* @hide
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT)
+ @RequiresPermission(SET_WALLPAPER_DIM_AMOUNT)
public @FloatRange (from = 0f, to = 1f) float getWallpaperDimAmount() {
if (sGlobals.mService == null) {
Log.w(TAG, "WallpaperService not running");
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 3a778c314606..c77004d4eb17 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -4333,6 +4333,12 @@
"group": "WM_DEBUG_ANIM",
"at": "com\/android\/server\/wm\/WindowStateAnimator.java"
},
+ "1810872941": {
+ "message": "setWallpaperCropHints: non-existent wallpaper token: %s",
+ "level": "WARN",
+ "group": "WM_ERROR",
+ "at": "com\/android\/server\/wm\/WindowManagerService.java"
+ },
"1820873642": {
"message": "SyncGroup %d: Unfinished dependencies: %s",
"level": "VERBOSE",
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index 98421a9e1d3e..f31eb44f23f5 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -18,11 +18,13 @@ package com.android.wallpaperbackup;
import static android.app.WallpaperManager.FLAG_LOCK;
import static android.app.WallpaperManager.FLAG_SYSTEM;
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_INELIGIBLE;
import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_METADATA;
import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_WALLPAPER;
import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_QUOTA_EXCEEDED;
+import static com.android.window.flags.Flags.multiCrop;
import android.app.AppGlobals;
import android.app.WallpaperManager;
@@ -43,7 +45,9 @@ import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.Pair;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
@@ -55,6 +59,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.util.List;
/**
* Backs up and restores wallpaper and metadata related to it.
@@ -432,6 +437,27 @@ public class WallpaperBackupAgent extends BackupAgent {
private void restoreFromStage(File stage, File info, String hintTag, int which)
throws IOException {
if (stage.exists()) {
+ if (multiCrop()) {
+ SparseArray<Rect> cropHints = parseCropHints(info, hintTag);
+ if (cropHints != null) {
+ Slog.i(TAG, "Got restored wallpaper; applying which=" + which
+ + "; cropHints = " + cropHints);
+ try (FileInputStream in = new FileInputStream(stage)) {
+ mWallpaperManager.setStreamWithCrops(in, cropHints, true, which);
+ }
+ // And log the success
+ if ((which & FLAG_SYSTEM) > 0) {
+ mEventLogger.onSystemImageWallpaperRestored();
+ }
+ if ((which & FLAG_LOCK) > 0) {
+ mEventLogger.onLockImageWallpaperRestored();
+ }
+ } else {
+ logRestoreError(which, ERROR_NO_METADATA);
+ }
+ return;
+ }
+
// Parse the restored info file to find the crop hint. Note that this currently
// relies on a priori knowledge of the wallpaper info file schema.
Rect cropHint = parseCropHint(info, hintTag);
@@ -501,6 +527,47 @@ public class WallpaperBackupAgent extends BackupAgent {
return cropHint;
}
+ private SparseArray<Rect> parseCropHints(File wallpaperInfo, String sectionTag) {
+ SparseArray<Rect> cropHints = new SparseArray<>();
+ try (FileInputStream stream = new FileInputStream(wallpaperInfo)) {
+ XmlPullParser parser = Xml.resolvePullParser(stream);
+ int type;
+ do {
+ type = parser.next();
+ if (type != XmlPullParser.START_TAG) continue;
+ String tag = parser.getName();
+ if (!sectionTag.equals(tag)) continue;
+ for (Pair<Integer, String> pair: List.of(
+ new Pair<>(WallpaperManager.PORTRAIT, "Portrait"),
+ new Pair<>(WallpaperManager.LANDSCAPE, "Landscape"),
+ new Pair<>(WallpaperManager.SQUARE_PORTRAIT, "SquarePortrait"),
+ new Pair<>(WallpaperManager.SQUARE_LANDSCAPE, "SquareLandscape"))) {
+ Rect cropHint = new Rect(
+ getAttributeInt(parser, "cropLeft" + pair.second, 0),
+ getAttributeInt(parser, "cropTop" + pair.second, 0),
+ getAttributeInt(parser, "cropRight" + pair.second, 0),
+ getAttributeInt(parser, "cropBottom" + pair.second, 0));
+ if (!cropHint.isEmpty()) cropHints.put(pair.first, cropHint);
+ }
+ if (cropHints.size() == 0) {
+ // migration case: the crops per screen orientation are not specified.
+ // use the old attributes to restore the crop for one screen orientation.
+ Rect cropHint = new Rect(
+ getAttributeInt(parser, "cropLeft", 0),
+ getAttributeInt(parser, "cropTop", 0),
+ getAttributeInt(parser, "cropRight", 0),
+ getAttributeInt(parser, "cropBottom", 0));
+ if (!cropHint.isEmpty()) cropHints.put(ORIENTATION_UNKNOWN, cropHint);
+ }
+ } while (type != XmlPullParser.END_DOCUMENT);
+ } catch (Exception e) {
+ // Whoops; can't process the info file at all. Report failure.
+ Slog.w(TAG, "Failed to parse restored crops: " + e.getMessage());
+ return null;
+ }
+ return cropHints;
+ }
+
private ComponentName parseWallpaperComponent(File wallpaperInfo, String sectionTag) {
ComponentName name = null;
try (FileInputStream stream = new FileInputStream(wallpaperInfo)) {
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index fb521e11c083..053ed779a27a 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -31,6 +31,7 @@ import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_IMG_LOC
import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_IMG_SYSTEM;
import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_LIVE_LOCK;
import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_LIVE_SYSTEM;
+import static com.android.window.flags.Flags.multiCrop;
import static com.google.common.truth.Truth.assertThat;
@@ -60,6 +61,7 @@ import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
import android.service.wallpaper.WallpaperService;
+import android.util.SparseArray;
import android.util.Xml;
import androidx.test.InstrumentationRegistry;
@@ -711,8 +713,13 @@ public class WallpaperBackupAgentTest {
@Test
public void testOnRestore_throwsException_logsErrors() throws Exception {
- when(mWallpaperManager.setStream(any(), any(), anyBoolean(), anyInt())).thenThrow(
- new RuntimeException());
+ if (!multiCrop()) {
+ when(mWallpaperManager.setStream(any(), any(), anyBoolean(), anyInt()))
+ .thenThrow(new RuntimeException());
+ } else {
+ when(mWallpaperManager.setStreamWithCrops(any(), any(SparseArray.class), anyBoolean(),
+ anyInt())).thenThrow(new RuntimeException());
+ }
mockStagedWallpaperFile(SYSTEM_WALLPAPER_STAGE);
mockStagedWallpaperFile(WALLPAPER_INFO_STAGE);
mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD,
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index b773ade09b89..51acc8e01cda 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -16,21 +16,28 @@
package com.android.server.wallpaper;
+import static android.app.WallpaperManager.getOrientation;
+import static android.app.WallpaperManager.getRotatedOrientation;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.server.wallpaper.WallpaperUtils.RECORD_FILE;
import static com.android.server.wallpaper.WallpaperUtils.RECORD_LOCK_FILE;
import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER;
import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
+import static com.android.window.flags.Flags.multiCrop;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageDecoder;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.FileUtils;
import android.os.SELinux;
+import android.text.TextUtils;
import android.util.Slog;
+import android.util.SparseArray;
import android.view.DisplayInfo;
+import android.view.View;
import com.android.server.utils.TimingsTraceAndSlog;
@@ -39,28 +46,334 @@ import libcore.io.IoUtils;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
/**
* Helper file for wallpaper cropping
- * Meant to have a single instance, only used by the WallpaperManagerService
+ * Meant to have a single instance, only used internally by system_server
+ * @hide
*/
-class WallpaperCropper {
+public class WallpaperCropper {
private static final String TAG = WallpaperCropper.class.getSimpleName();
private static final boolean DEBUG = false;
private static final boolean DEBUG_CROP = true;
+ /**
+ * Maximum acceptable parallax.
+ * A value of 1 means "the additional width for parallax is at most 100% of the screen width"
+ */
+ private static final float MAX_PARALLAX = 1f;
+
+ /**
+ * We define three ways to adjust a crop. These modes are used depending on the situation:
+ * - When going from unfolded to folded, we want to remove content
+ * - When going from folded to unfolded, we want to add content
+ * - For a screen rotation, we want to keep the same amount of content
+ */
+ private static final int ADD = 1;
+ private static final int REMOVE = 2;
+ private static final int BALANCE = 3;
+
+
private final WallpaperDisplayHelper mWallpaperDisplayHelper;
+ /**
+ * Helpers exposed to the window manager part (WallpaperController)
+ */
+ public interface WallpaperCropUtils {
+
+ /**
+ * Equivalent to {@link #getCrop(Point, Point, SparseArray, boolean)}
+ */
+ Rect getCrop(Point displaySize, Point bitmapSize,
+ SparseArray<Rect> suggestedCrops, boolean rtl);
+ }
+
WallpaperCropper(WallpaperDisplayHelper wallpaperDisplayHelper) {
mWallpaperDisplayHelper = wallpaperDisplayHelper;
}
/**
- * Once a new wallpaper has been written via setWallpaper(...), it needs to be cropped
- * for display.
+ * Given the dimensions of the original wallpaper image, some optional suggested crops
+ * (either defined by the user, or coming from a backup), and whether the device is RTL,
+ * generate a crop for the current display. This is done through the following process:
+ * <ul>
+ * <li> If no suggested crops are provided, center the full image on the display. </li>
+ * <li> If there is a suggested crop the given displaySize, reuse the suggested crop and
+ * adjust it using {@link #getAdjustedCrop}. </li>
+ * <li> If there are suggested crops, but not for the orientation of the given displaySize,
+ * reuse one of the suggested crop for another orientation and adjust if using
+ * {@link #getAdjustedCrop}. </li>
+ * </ul>
+ *
+ * @param displaySize The dimensions of the surface where we want to render the wallpaper
+ * @param bitmapSize The dimensions of the wallpaper bitmap
+ * @param rtl Whether the device is right-to-left
+ * @param suggestedCrops An optional list of user-defined crops for some orientations.
+ * If there is a suggested crop for
*
- * This will generate the crop and write it in the file
+ * @return A Rect indicating how to crop the bitmap for the current display.
+ */
+ public Rect getCrop(Point displaySize, Point bitmapSize,
+ SparseArray<Rect> suggestedCrops, boolean rtl) {
+
+ // Case 1: if no crops are provided, center align the full image
+ if (suggestedCrops == null || suggestedCrops.size() == 0) {
+ Rect crop = new Rect(0, 0, displaySize.x, displaySize.y);
+ float scale = Math.min(
+ ((float) bitmapSize.x) / displaySize.x,
+ ((float) bitmapSize.y) / displaySize.y);
+ crop.scale(scale);
+ crop.offset((bitmapSize.x - crop.width()) / 2,
+ (bitmapSize.y - crop.height()) / 2);
+ return crop;
+ }
+ int orientation = getOrientation(displaySize);
+
+ // Case 2: if the orientation exists in the suggested crops, adjust the suggested crop
+ Rect suggestedCrop = suggestedCrops.get(orientation);
+ if (suggestedCrop != null) {
+ if (suggestedCrop.left < 0 || suggestedCrop.top < 0
+ || suggestedCrop.right > bitmapSize.x || suggestedCrop.bottom > bitmapSize.y) {
+ Slog.w(TAG, "invalid suggested crop: " + suggestedCrop);
+ Rect fullImage = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ return getAdjustedCrop(fullImage, bitmapSize, displaySize, true, rtl, ADD);
+ } else {
+ return getAdjustedCrop(suggestedCrop, bitmapSize, displaySize, true, rtl, ADD);
+ }
+ }
+
+ // Case 3: if we have the 90° rotated orientation in the suggested crops, reuse it and
+ // trying to preserve the zoom level and the center of the image
+ SparseArray<Point> defaultDisplaySizes = mWallpaperDisplayHelper.getDefaultDisplaySizes();
+ int rotatedOrientation = getRotatedOrientation(orientation);
+ suggestedCrop = suggestedCrops.get(rotatedOrientation);
+ Point suggestedDisplaySize = defaultDisplaySizes.get(rotatedOrientation);
+ if (suggestedCrop != null) {
+ // only keep the visible part (without parallax)
+ Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl);
+ return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, BALANCE);
+ }
+
+ // Case 4: if the device is a foldable, if we're looking for a folded orientation and have
+ // the suggested crop of the relative unfolded orientation, reuse it by removing content.
+ int unfoldedOrientation = mWallpaperDisplayHelper.getUnfoldedOrientation(orientation);
+ suggestedCrop = suggestedCrops.get(unfoldedOrientation);
+ suggestedDisplaySize = defaultDisplaySizes.get(unfoldedOrientation);
+ if (suggestedCrop != null) {
+ // only keep the visible part (without parallax)
+ Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl);
+ return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, REMOVE);
+ }
+
+ // Case 5: if the device is a foldable, if we're looking for an unfolded orientation and
+ // have the suggested crop of the relative folded orientation, reuse it by adding content.
+ int foldedOrientation = mWallpaperDisplayHelper.getFoldedOrientation(orientation);
+ suggestedCrop = suggestedCrops.get(foldedOrientation);
+ suggestedDisplaySize = defaultDisplaySizes.get(foldedOrientation);
+ if (suggestedCrop != null) {
+ // only keep the visible part (without parallax)
+ Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl);
+ return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, ADD);
+ }
+
+ // Case 6: for a foldable device, try to combine case 3 + case 4 or 5:
+ // rotate, then fold or unfold
+ Point rotatedDisplaySize = defaultDisplaySizes.get(rotatedOrientation);
+ if (rotatedDisplaySize != null) {
+ int rotatedFolded = mWallpaperDisplayHelper.getFoldedOrientation(rotatedOrientation);
+ int rotateUnfolded = mWallpaperDisplayHelper.getUnfoldedOrientation(rotatedOrientation);
+ for (int suggestedOrientation : new int[]{rotatedFolded, rotateUnfolded}) {
+ suggestedCrop = suggestedCrops.get(suggestedOrientation);
+ if (suggestedCrop != null) {
+ Rect rotatedCrop = getCrop(rotatedDisplaySize, bitmapSize, suggestedCrops, rtl);
+ SparseArray<Rect> rotatedCropMap = new SparseArray<>();
+ rotatedCropMap.put(rotatedOrientation, rotatedCrop);
+ return getCrop(displaySize, bitmapSize, rotatedCropMap, rtl);
+ }
+ }
+ }
+
+ // Case 7: could not properly reuse the suggested crops. Fall back to case 1.
+ Slog.w(TAG, "Could not find a proper default crop for display: " + displaySize
+ + ", bitmap size: " + bitmapSize + ", suggested crops: " + suggestedCrops
+ + ", orientation: " + orientation + ", rtl: " + rtl
+ + ", defaultDisplaySizes: " + defaultDisplaySizes);
+ return getCrop(displaySize, bitmapSize, new SparseArray<>(), rtl);
+ }
+
+ /**
+ * Given a crop, a displaySize for the orientation of that crop, compute the visible part of the
+ * crop. This removes any additional width used for parallax. No-op if displaySize == null.
+ */
+ private static Rect noParallax(Rect crop, Point displaySize, Point bitmapSize, boolean rtl) {
+ if (displaySize == null) return crop;
+ Rect adjustedCrop = getAdjustedCrop(crop, bitmapSize, displaySize, true, rtl, ADD);
+ // only keep the visible part (without parallax)
+ float suggestedDisplayRatio = 1f * displaySize.x / displaySize.y;
+ int widthToRemove = (int) (adjustedCrop.width()
+ - (((float) adjustedCrop.height()) * suggestedDisplayRatio) + 0.5f);
+ if (rtl) {
+ adjustedCrop.left += widthToRemove;
+ } else {
+ adjustedCrop.right -= widthToRemove;
+ }
+ return adjustedCrop;
+ }
+
+ /**
+ * Adjust a given crop:
+ * <ul>
+ * <li>If parallax = true, make sure we have a parallax of at most {@link #MAX_PARALLAX},
+ * by removing content from the right (or left if RTL) if necessary.
+ * </li>
+ * <li>If parallax = false, make sure we do not have additional width for parallax. If we
+ * have additional width for parallax, remove half of the additional width on both sides.
+ * </li>
+ * <li>Make sure the crop fills the screen, i.e. that the width/height ratio of the crop
+ * is at least the width/height ratio of the screen. If it is less, add width to the crop
+ * (if possible on both sides) to fill the screen. If not enough width available, remove
+ * height to the crop.
+ * </li>
+ * </ul>
+ */
+ private static Rect getAdjustedCrop(Rect crop, Point bitmapSize, Point screenSize,
+ boolean parallax, boolean rtl, int mode) {
+ Rect adjustedCrop = new Rect(crop);
+ float cropRatio = ((float) crop.width()) / crop.height();
+ float screenRatio = ((float) screenSize.x) / screenSize.y;
+ if (cropRatio >= screenRatio) {
+ if (!parallax) {
+ // rotate everything 90 degrees clockwise, compute the result, and rotate back
+ int newLeft = bitmapSize.y - crop.bottom;
+ int newRight = newLeft + crop.height();
+ int newTop = crop.left;
+ int newBottom = newTop + crop.width();
+ Rect rotatedCrop = new Rect(newLeft, newTop, newRight, newBottom);
+ Point rotatedBitmap = new Point(bitmapSize.y, bitmapSize.x);
+ Point rotatedScreen = new Point(screenSize.y, screenSize.x);
+ Rect rect = getAdjustedCrop(rotatedCrop, rotatedBitmap, rotatedScreen, false, rtl,
+ mode);
+ int resultLeft = rect.top;
+ int resultRight = resultLeft + rect.height();
+ int resultTop = rotatedBitmap.x - rect.right;
+ int resultBottom = resultTop + rect.width();
+ return new Rect(resultLeft, resultTop, resultRight, resultBottom);
+ }
+ float additionalWidthForParallax = cropRatio / screenRatio - 1f;
+ if (additionalWidthForParallax > MAX_PARALLAX) {
+ int widthToRemove = (int) Math.ceil(
+ (additionalWidthForParallax - MAX_PARALLAX) * screenRatio * crop.height());
+ if (rtl) {
+ adjustedCrop.left += widthToRemove;
+ } else {
+ adjustedCrop.right -= widthToRemove;
+ }
+ }
+ } else {
+ int widthToAdd = mode == REMOVE ? 0
+ : mode == ADD ? (int) (0.5 + crop.height() * screenRatio - crop.width())
+ : (int) (0.5 + crop.height() - crop.width());
+ int availableWidth = bitmapSize.x - crop.width();
+ if (availableWidth >= widthToAdd) {
+ int widthToAddLeft = widthToAdd / 2;
+ int widthToAddRight = widthToAdd / 2 + widthToAdd % 2;
+
+ if (crop.left < widthToAddLeft) {
+ widthToAddRight += (widthToAddLeft - crop.left);
+ widthToAddLeft = crop.left;
+ } else if (bitmapSize.x - crop.right < widthToAddRight) {
+ widthToAddLeft += (widthToAddRight - (bitmapSize.x - crop.right));
+ widthToAddRight = bitmapSize.x - crop.right;
+ }
+ adjustedCrop.left -= widthToAddLeft;
+ adjustedCrop.right += widthToAddRight;
+ } else {
+ adjustedCrop.left = 0;
+ adjustedCrop.right = bitmapSize.x;
+ }
+ int heightToRemove = (int) (crop.height() - (adjustedCrop.width() / screenRatio));
+ adjustedCrop.top += heightToRemove / 2 + heightToRemove % 2;
+ adjustedCrop.bottom -= heightToRemove / 2;
+ }
+ return adjustedCrop;
+ }
+
+ /**
+ * To find the smallest sub-image that contains all the given crops.
+ * This is used in {@link #generateCrop(WallpaperData)}
+ * to determine how the file from {@link WallpaperData#getCropFile()} needs to be cropped.
+ *
+ * @param crops a list of rectangles
+ * @return the smallest rectangle that contains them all.
+ */
+ public static Rect getTotalCrop(SparseArray<Rect> crops) {
+ int left = Integer.MAX_VALUE, top = Integer.MAX_VALUE;
+ int right = Integer.MIN_VALUE, bottom = Integer.MIN_VALUE;
+ for (int i = 0; i < crops.size(); i++) {
+ Rect rect = crops.valueAt(i);
+ left = Math.min(left, rect.left);
+ top = Math.min(top, rect.top);
+ right = Math.max(right, rect.right);
+ bottom = Math.max(bottom, rect.bottom);
+ }
+ return new Rect(left, top, right, bottom);
+ }
+
+ /**
+ * The crops stored in {@link WallpaperData#mCropHints} are relative to the original image.
+ * This computes the crops relative to the sub-image that will actually be rendered on a window.
+ */
+ SparseArray<Rect> getRelativeCropHints(WallpaperData wallpaper) {
+ SparseArray<Rect> result = new SparseArray<>();
+ for (int i = 0; i < wallpaper.mCropHints.size(); i++) {
+ Rect adjustedRect = new Rect(wallpaper.mCropHints.valueAt(i));
+ adjustedRect.offset(-wallpaper.cropHint.left, -wallpaper.cropHint.top);
+ adjustedRect.scale(1f / wallpaper.mSampleSize);
+ result.put(wallpaper.mCropHints.keyAt(i), adjustedRect);
+ }
+ return result;
+ }
+
+ /**
+ * Inverse operation of {@link #getRelativeCropHints}
+ */
+ static List<Rect> getOriginalCropHints(
+ WallpaperData wallpaper, List<Rect> relativeCropHints) {
+ List<Rect> result = new ArrayList<>();
+ for (Rect crop : relativeCropHints) {
+ Rect originalRect = new Rect(crop);
+ originalRect.scale(wallpaper.mSampleSize);
+ originalRect.offset(wallpaper.cropHint.left, wallpaper.cropHint.right);
+ result.add(originalRect);
+ }
+ return result;
+ }
+
+ /**
+ * Given some suggested crops, find cropHints for all orientations of the default display.
+ */
+ SparseArray<Rect> getDefaultCrops(SparseArray<Rect> suggestedCrops, Point bitmapSize) {
+ SparseArray<Rect> result = new SparseArray<>();
+ // add missing cropHints for all orientation of the default display
+ SparseArray<Point> defaultDisplaySizes = mWallpaperDisplayHelper.getDefaultDisplaySizes();
+ boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
+ == View.LAYOUT_DIRECTION_RTL;
+ for (int i = 0; i < defaultDisplaySizes.size(); i++) {
+ int orientation = defaultDisplaySizes.keyAt(i);
+ Point displaySize = defaultDisplaySizes.valueAt(i);
+ Rect newCrop = getCrop(displaySize, bitmapSize, suggestedCrops, rtl);
+ result.put(orientation, newCrop);
+ }
+ return result;
+ }
+
+ /**
+ * Once a new wallpaper has been written via setWallpaper(...), it needs to be cropped
+ * for display. This will generate the crop and write it in the file.
*/
void generateCrop(WallpaperData wallpaper) {
TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
@@ -75,27 +388,47 @@ class WallpaperCropper {
// Only generate crop for default display.
final WallpaperDisplayHelper.DisplayData wpData =
mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
- final Rect cropHint = new Rect(wallpaper.cropHint);
final DisplayInfo displayInfo = mWallpaperDisplayHelper.getDisplayInfo(DEFAULT_DISPLAY);
- if (DEBUG) {
- Slog.v(TAG, "Generating crop for new wallpaper(s): 0x"
- + Integer.toHexString(wallpaper.mWhich)
- + " to " + wallpaper.getCropFile().getName()
- + " crop=(" + cropHint.width() + 'x' + cropHint.height()
- + ") dim=(" + wpData.mWidth + 'x' + wpData.mHeight + ')');
- }
-
// Analyse the source; needed in multiple cases
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(wallpaper.getWallpaperFile().getAbsolutePath(), options);
if (options.outWidth <= 0 || options.outHeight <= 0) {
Slog.w(TAG, "Invalid wallpaper data");
- success = false;
} else {
boolean needCrop = false;
boolean needScale;
+ boolean multiCrop = multiCrop() && wallpaper.mSupportsMultiCrop;
+
+ Point bitmapSize = new Point(options.outWidth, options.outHeight);
+
+ final Rect cropHint;
+ if (multiCrop) {
+ SparseArray<Rect> defaultDisplayCrops =
+ getDefaultCrops(wallpaper.mCropHints, bitmapSize);
+ // adapt the entries in wallpaper.mCropHints for the actual display
+ SparseArray<Rect> updatedCropHints = new SparseArray<>();
+ for (int i = 0; i < wallpaper.mCropHints.size(); i++) {
+ Rect defaultCrop = defaultDisplayCrops.valueAt(i);
+ if (defaultCrop != null) {
+ updatedCropHints.put(defaultDisplayCrops.keyAt(i), defaultCrop);
+ }
+ }
+ wallpaper.mCropHints = updatedCropHints;
+ cropHint = getTotalCrop(defaultDisplayCrops);
+ wallpaper.cropHint.set(cropHint);
+ } else {
+ cropHint = new Rect(wallpaper.cropHint);
+ }
+
+ if (DEBUG) {
+ Slog.v(TAG, "Generating crop for new wallpaper(s): 0x"
+ + Integer.toHexString(wallpaper.mWhich)
+ + " to " + wallpaper.getCropFile().getName()
+ + " crop=(" + cropHint.width() + 'x' + cropHint.height()
+ + ") dim=(" + wpData.mWidth + 'x' + wpData.mHeight + ')');
+ }
// Empty crop means use the full image
if (cropHint.isEmpty()) {
@@ -128,7 +461,7 @@ class WallpaperCropper {
|| cropHint.width() > GLHelper.getMaxTextureSize();
//make sure screen aspect ratio is preserved if width is scaled under screen size
- if (needScale) {
+ if (needScale && !multiCrop) {
final float scaleByHeight = (float) wpData.mHeight / (float) cropHint.height();
final int newWidth = (int) (cropHint.width() * scaleByHeight);
if (newWidth < displayInfo.logicalWidth) {
@@ -171,7 +504,7 @@ class WallpaperCropper {
BufferedOutputStream bos = null;
try {
// This actually downsamples only by powers of two, but that's okay; we do
- // a proper scaling blit later. This is to minimize transient RAM use.
+ // a proper scaling a bit later. This is to minimize transient RAM use.
// We calculate the largest power-of-two under the actual ratio rather than
// just let the decode take care of it because we also want to remap where the
// cropHint rectangle lies in the decoded [super]rect.
@@ -185,19 +518,31 @@ class WallpaperCropper {
final Rect estimateCrop = new Rect(cropHint);
estimateCrop.scale(1f / options.inSampleSize);
- final float hRatio = (float) wpData.mHeight / estimateCrop.height();
+ float hRatio = (float) wpData.mHeight / estimateCrop.height();
+ if (multiCrop) {
+ // make sure the crop height is at most the display largest dimension
+ hRatio = (float) mWallpaperDisplayHelper.getDefaultDisplayLargestDimension()
+ / estimateCrop.height();
+ hRatio = Math.min(hRatio, 1f);
+ }
final int destHeight = (int) (estimateCrop.height() * hRatio);
final int destWidth = (int) (estimateCrop.width() * hRatio);
// We estimated an invalid crop, try to adjust the cropHint to get a valid one.
if (destWidth > GLHelper.getMaxTextureSize()) {
- int newHeight = (int) (wpData.mHeight / hRatio);
- int newWidth = (int) (wpData.mWidth / hRatio);
-
if (DEBUG) {
- Slog.v(TAG, "Invalid crop dimensions, trying to adjust.");
+ Slog.w(TAG, "Invalid crop dimensions, trying to adjust.");
+ }
+ if (multiCrop) {
+ // clear custom crop guidelines, fallback to system default
+ wallpaper.mCropHints.clear();
+ generateCropInternal(wallpaper);
+ return;
}
+ int newHeight = (int) (wpData.mHeight / hRatio);
+ int newWidth = (int) (wpData.mWidth / hRatio);
+
estimateCrop.set(cropHint);
estimateCrop.left += (cropHint.width() - newWidth) / 2;
estimateCrop.top += (cropHint.height() - newHeight) / 2;
@@ -210,8 +555,8 @@ class WallpaperCropper {
// We've got the safe cropHint; now we want to scale it properly to
// the desired rectangle.
// That's a height-biased operation: make it fit the hinted height.
- final int safeHeight = (int) (estimateCrop.height() * hRatio);
- final int safeWidth = (int) (estimateCrop.width() * hRatio);
+ final int safeHeight = (int) (estimateCrop.height() * hRatio + 0.5f);
+ final int safeWidth = (int) (estimateCrop.width() * hRatio + 0.5f);
if (DEBUG_CROP) {
Slog.v(TAG, "Decode parameters:");
@@ -248,6 +593,12 @@ class WallpaperCropper {
// We are safe to create final crop with safe dimensions now.
final Bitmap finalCrop = Bitmap.createScaledBitmap(cropped,
safeWidth, safeHeight, true);
+
+ if (multiCrop) {
+ wallpaper.mSampleSize =
+ ((float) cropHint.height()) / finalCrop.getHeight();
+ }
+
if (DEBUG) {
Slog.v(TAG, "Final extract:");
Slog.v(TAG, " dims: w=" + wpData.mWidth
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperData.java b/services/core/java/com/android/server/wallpaper/WallpaperData.java
index 5c867017f4e0..02594d2d8d22 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperData.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperData.java
@@ -17,6 +17,7 @@
package com.android.server.wallpaper;
import static android.app.WallpaperManager.FLAG_LOCK;
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER;
import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_CROP;
@@ -26,6 +27,7 @@ import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
import android.app.IWallpaperManagerCallback;
import android.app.WallpaperColors;
+import android.app.WallpaperManager.ScreenOrientation;
import android.app.WallpaperManager.SetWallpaperFlags;
import android.content.ComponentName;
import android.graphics.Rect;
@@ -126,10 +128,16 @@ class WallpaperData {
RemoteCallbackList<IWallpaperManagerCallback> callbacks = new RemoteCallbackList<>();
/**
- * The crop hint supplied for displaying a subset of the source image
+ * Defines which part of the {@link #getWallpaperFile()} image is in the {@link #getCropFile()}.
*/
final Rect cropHint = new Rect(0, 0, 0, 0);
+ /**
+ * How much the crop is sub-sampled. A value > 1 means that the image quality was reduced.
+ * This is the ratio between the cropHint height and the actual {@link #getCropFile()} height.
+ */
+ float mSampleSize = 1f;
+
// Describes the context of a call to WallpaperManagerService#bindWallpaperComponentLocked
enum BindSource {
UNKNOWN,
@@ -156,6 +164,23 @@ class WallpaperData {
private final SparseArray<File> mWallpaperFiles = new SparseArray<>();
private final SparseArray<File> mCropFiles = new SparseArray<>();
+ /**
+ * Mapping of {@link ScreenOrientation} -> crop hint. The crop hints are relative to the
+ * original image stored in {@link #getWallpaperFile()}.
+ * Only used when multi crop flag is enabled.
+ */
+ SparseArray<Rect> mCropHints = new SparseArray<>();
+
+ /**
+ * cropHints will be ignored if this flag is false
+ */
+ boolean mSupportsMultiCrop;
+
+ /**
+ * The phone orientation when the wallpaper was set. Only relevant for image wallpapers
+ */
+ int mOrientationWhenSet = ORIENTATION_UNKNOWN;
+
WallpaperData(int userId, @SetWallpaperFlags int wallpaperType) {
this.userId = userId;
this.mWhich = wallpaperType;
@@ -176,6 +201,10 @@ class WallpaperData {
this.mWhich = source.mWhich;
this.wallpaperId = source.wallpaperId;
this.cropHint.set(source.cropHint);
+ if (source.mCropHints != null) {
+ this.mCropHints = source.mCropHints.clone();
+ }
+ this.mSupportsMultiCrop = source.mSupportsMultiCrop;
this.allowBackup = source.allowBackup;
this.primaryColors = source.primaryColors;
this.mWallpaperDimAmount = source.mWallpaperDimAmount;
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index de98df55c3ea..88e9672cd0a1 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -18,6 +18,7 @@ package com.android.server.wallpaper;
import static android.app.WallpaperManager.FLAG_LOCK;
import static android.app.WallpaperManager.FLAG_SYSTEM;
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.server.wallpaper.WallpaperDisplayHelper.DisplayData;
@@ -26,9 +27,11 @@ import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_CROP;
import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_INFO;
import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
import static com.android.server.wallpaper.WallpaperUtils.makeWallpaperIdLocked;
+import static com.android.window.flags.Flags.multiCrop;
import android.annotation.Nullable;
import android.app.WallpaperColors;
+import android.app.WallpaperManager;
import android.app.WallpaperManager.SetWallpaperFlags;
import android.app.backup.WallpaperBackupHelper;
import android.content.ComponentName;
@@ -36,7 +39,9 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Color;
+import android.graphics.Rect;
import android.os.FileUtils;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
@@ -60,14 +65,16 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
/**
* Helper for the wallpaper loading / saving / xml parsing
* Only meant to be used lock held by WallpaperManagerService
* Only meant to be instantiated once by WallpaperManagerService
+ * @hide
*/
-class WallpaperDataParser {
+public class WallpaperDataParser {
private static final String TAG = WallpaperDataParser.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -132,6 +139,7 @@ class WallpaperDataParser {
*/
public WallpaperLoadingResult loadSettingsLocked(int userId, boolean keepDimensionHints,
boolean migrateFromOld, @SetWallpaperFlags int which) {
+ // TODO(b/270726737) remove the "keepDimensionHints" arg when removing the multi crop flag
JournaledFile journal = makeJournaledFile(userId);
FileInputStream stream = null;
File file = journal.chooseForRead();
@@ -174,7 +182,9 @@ class WallpaperDataParser {
WallpaperData wallpaperToParse =
"wp".equals(tag) ? wallpaper : lockWallpaper;
- parseWallpaperAttributes(parser, wallpaperToParse, keepDimensionHints);
+ if (!multiCrop()) {
+ parseWallpaperAttributes(parser, wallpaperToParse, keepDimensionHints);
+ }
String comp = parser.getAttributeValue(null, "component");
wallpaperToParse.nextWallpaperComponent = comp != null
@@ -186,6 +196,10 @@ class WallpaperDataParser {
wallpaperToParse.nextWallpaperComponent = mImageWallpaper;
}
+ if (multiCrop()) {
+ parseWallpaperAttributes(parser, wallpaperToParse, keepDimensionHints);
+ }
+
if (DEBUG) {
Slog.v(TAG, "mWidth:" + wpdData.mWidth);
Slog.v(TAG, "mHeight:" + wpdData.mHeight);
@@ -300,20 +314,48 @@ class WallpaperDataParser {
wallpaper.wallpaperId = makeWallpaperIdLocked();
}
- final DisplayData wpData = mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
-
- if (!keepDimensionHints) {
- wpData.mWidth = parser.getAttributeInt(null, "width");
- wpData.mHeight = parser.getAttributeInt(null, "height");
+ Rect totalCropHint = new Rect(
+ getAttributeInt(parser, "totalCropLeft", 0),
+ getAttributeInt(parser, "totalCropTop", 0),
+ getAttributeInt(parser, "totalCropRight", 0),
+ getAttributeInt(parser, "totalCropBottom", 0));
+ wallpaper.mSupportsMultiCrop = multiCrop() && (
+ parser.getAttributeBoolean(null, "supportsMultiCrop", false)
+ || mImageWallpaper.equals(wallpaper.wallpaperComponent));
+ if (wallpaper.mSupportsMultiCrop) {
+ wallpaper.mCropHints = new SparseArray<>();
+ for (Pair<Integer, String> pair: screenDimensionPairs()) {
+ Rect cropHint = new Rect(
+ parser.getAttributeInt(null, "cropLeft" + pair.second, 0),
+ parser.getAttributeInt(null, "cropTop" + pair.second, 0),
+ parser.getAttributeInt(null, "cropRight" + pair.second, 0),
+ parser.getAttributeInt(null, "cropBottom" + pair.second, 0));
+ if (!cropHint.isEmpty()) wallpaper.mCropHints.put(pair.first, cropHint);
+ }
+ if (wallpaper.mCropHints.size() == 0) {
+ // migration case: the crops per screen orientation are not specified.
+ // use the old attributes to find the crop for one screen orientation.
+ Integer orientation = totalCropHint.width() < totalCropHint.height()
+ ? WallpaperManager.PORTRAIT : WallpaperManager.LANDSCAPE;
+ if (!totalCropHint.isEmpty()) wallpaper.mCropHints.put(orientation, totalCropHint);
+ } else {
+ wallpaper.cropHint.set(totalCropHint);
+ }
+ } else {
+ wallpaper.cropHint.set(totalCropHint);
+ }
+ final DisplayData wpData = mWallpaperDisplayHelper
+ .getDisplayDataOrCreate(DEFAULT_DISPLAY);
+ if (!keepDimensionHints && !multiCrop()) {
+ wpData.mWidth = parser.getAttributeInt(null, "width", 0);
+ wpData.mHeight = parser.getAttributeInt(null, "height", 0);
+ }
+ if (!multiCrop()) {
+ wpData.mPadding.left = getAttributeInt(parser, "paddingLeft", 0);
+ wpData.mPadding.top = getAttributeInt(parser, "paddingTop", 0);
+ wpData.mPadding.right = getAttributeInt(parser, "paddingRight", 0);
+ wpData.mPadding.bottom = getAttributeInt(parser, "paddingBottom", 0);
}
- 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);
- wpData.mPadding.left = getAttributeInt(parser, "paddingLeft", 0);
- wpData.mPadding.top = getAttributeInt(parser, "paddingTop", 0);
- wpData.mPadding.right = getAttributeInt(parser, "paddingRight", 0);
- wpData.mPadding.bottom = getAttributeInt(parser, "paddingBottom", 0);
wallpaper.mWallpaperDimAmount = getAttributeFloat(parser, "dimAmount", 0f);
BindSource bindSource;
try {
@@ -365,11 +407,11 @@ class WallpaperDataParser {
wallpaper.allowBackup = parser.getAttributeBoolean(null, "backup", false);
}
- private int getAttributeInt(TypedXmlPullParser parser, String name, int defValue) {
+ private static int getAttributeInt(TypedXmlPullParser parser, String name, int defValue) {
return parser.getAttributeInt(null, name, defValue);
}
- private float getAttributeFloat(TypedXmlPullParser parser, String name, float defValue) {
+ private static float getAttributeFloat(TypedXmlPullParser parser, String name, float defValue) {
return parser.getAttributeFloat(null, name, defValue);
}
@@ -412,28 +454,66 @@ class WallpaperDataParser {
if (DEBUG) {
Slog.v(TAG, "writeWallpaperAttributes id=" + wallpaper.wallpaperId);
}
- final DisplayData wpdData = mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
out.startTag(null, tag);
out.attributeInt(null, "id", wallpaper.wallpaperId);
- out.attributeInt(null, "width", wpdData.mWidth);
- out.attributeInt(null, "height", wpdData.mHeight);
- out.attributeInt(null, "cropLeft", wallpaper.cropHint.left);
- out.attributeInt(null, "cropTop", wallpaper.cropHint.top);
- out.attributeInt(null, "cropRight", wallpaper.cropHint.right);
- out.attributeInt(null, "cropBottom", wallpaper.cropHint.bottom);
+ out.attributeBoolean(null, "supportsMultiCrop", wallpaper.mSupportsMultiCrop);
- if (wpdData.mPadding.left != 0) {
- out.attributeInt(null, "paddingLeft", wpdData.mPadding.left);
- }
- if (wpdData.mPadding.top != 0) {
- out.attributeInt(null, "paddingTop", wpdData.mPadding.top);
- }
- if (wpdData.mPadding.right != 0) {
- out.attributeInt(null, "paddingRight", wpdData.mPadding.right);
- }
- if (wpdData.mPadding.bottom != 0) {
- out.attributeInt(null, "paddingBottom", wpdData.mPadding.bottom);
+ if (multiCrop() && wallpaper.mSupportsMultiCrop) {
+ if (wallpaper.mCropHints == null) {
+ Slog.e(TAG, "cropHints should not be null when saved");
+ wallpaper.mCropHints = new SparseArray<>();
+ }
+ for (Pair<Integer, String> pair : screenDimensionPairs()) {
+ Rect cropHint = wallpaper.mCropHints.get(pair.first);
+ if (cropHint == null) continue;
+ out.attributeInt(null, "cropLeft" + pair.second, cropHint.left);
+ out.attributeInt(null, "cropTop" + pair.second, cropHint.top);
+ out.attributeInt(null, "cropRight" + pair.second, cropHint.right);
+ out.attributeInt(null, "cropBottom" + pair.second, cropHint.bottom);
+
+ // to support back compatibility in B&R, save the crops for one orientation in the
+ // legacy "cropLeft", "cropTop", "cropRight", "cropBottom" entries
+ int orientationToPutInLegacyCrop = wallpaper.mOrientationWhenSet;
+ if (mWallpaperDisplayHelper.isFoldable()) {
+ int unfoldedOrientation = mWallpaperDisplayHelper
+ .getUnfoldedOrientation(orientationToPutInLegacyCrop);
+ if (unfoldedOrientation != ORIENTATION_UNKNOWN) {
+ orientationToPutInLegacyCrop = unfoldedOrientation;
+ }
+ }
+ if (pair.first == orientationToPutInLegacyCrop) {
+ out.attributeInt(null, "cropLeft", cropHint.left);
+ out.attributeInt(null, "cropTop", cropHint.top);
+ out.attributeInt(null, "cropRight", cropHint.right);
+ out.attributeInt(null, "cropBottom", cropHint.bottom);
+ }
+ }
+ out.attributeInt(null, "totalCropLeft", wallpaper.cropHint.left);
+ out.attributeInt(null, "totalCropTop", wallpaper.cropHint.top);
+ out.attributeInt(null, "totalCropRight", wallpaper.cropHint.right);
+ out.attributeInt(null, "totalCropBottom", wallpaper.cropHint.bottom);
+ } else if (!multiCrop()) {
+ final DisplayData wpdData =
+ mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
+ out.attributeInt(null, "width", wpdData.mWidth);
+ out.attributeInt(null, "height", wpdData.mHeight);
+ out.attributeInt(null, "cropLeft", wallpaper.cropHint.left);
+ out.attributeInt(null, "cropTop", wallpaper.cropHint.top);
+ out.attributeInt(null, "cropRight", wallpaper.cropHint.right);
+ out.attributeInt(null, "cropBottom", wallpaper.cropHint.bottom);
+ if (wpdData.mPadding.left != 0) {
+ out.attributeInt(null, "paddingLeft", wpdData.mPadding.left);
+ }
+ if (wpdData.mPadding.top != 0) {
+ out.attributeInt(null, "paddingTop", wpdData.mPadding.top);
+ }
+ if (wpdData.mPadding.right != 0) {
+ out.attributeInt(null, "paddingRight", wpdData.mPadding.right);
+ }
+ if (wpdData.mPadding.bottom != 0) {
+ out.attributeInt(null, "paddingBottom", wpdData.mPadding.bottom);
+ }
}
out.attributeFloat(null, "dimAmount", wallpaper.mWallpaperDimAmount);
@@ -564,4 +644,12 @@ class WallpaperDataParser {
}
return false;
}
+
+ private static List<Pair<Integer, String>> screenDimensionPairs() {
+ return List.of(
+ new Pair<>(WallpaperManager.PORTRAIT, "Portrait"),
+ new Pair<>(WallpaperManager.LANDSCAPE, "Landscape"),
+ new Pair<>(WallpaperManager.SQUARE_PORTRAIT, "SquarePortrait"),
+ new Pair<>(WallpaperManager.SQUARE_LANDSCAPE, "SquareLandscape"));
+ }
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
index f48178c5b9f7..19fd9a90518d 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
@@ -16,19 +16,31 @@
package com.android.server.wallpaper;
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
+import static android.app.WallpaperManager.getRotatedOrientation;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.window.flags.Flags.multiCrop;
+
+import android.app.WallpaperManager;
+import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.os.Binder;
import android.os.Debug;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayInfo;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
import com.android.server.wm.WindowManagerInternal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
import java.util.function.Consumer;
/**
@@ -50,12 +62,55 @@ class WallpaperDisplayHelper {
private final SparseArray<DisplayData> mDisplayDatas = new SparseArray<>();
private final DisplayManager mDisplayManager;
private final WindowManagerInternal mWindowManagerInternal;
+ private final SparseArray<Point> mDefaultDisplaySizes = new SparseArray<>();
+
+ // related orientations pairs for foldable (folded orientation, unfolded orientation)
+ private final List<Pair<Integer, Integer>> mFoldableOrientationPairs = new ArrayList<>();
+
+ private boolean mIsFoldable;
WallpaperDisplayHelper(
DisplayManager displayManager,
- WindowManagerInternal windowManagerInternal) {
+ WindowManager windowManager,
+ WindowManagerInternal windowManagerInternal,
+ boolean isFoldable) {
mDisplayManager = displayManager;
mWindowManagerInternal = windowManagerInternal;
+ mIsFoldable = isFoldable;
+ if (!multiCrop()) return;
+ Set<WindowMetrics> metrics = windowManager.getPossibleMaximumWindowMetrics(DEFAULT_DISPLAY);
+ boolean populateOrientationPairs = isFoldable && metrics.size() == 2;
+ float surface = 0;
+ int firstOrientation = -1;
+ for (WindowMetrics metric: metrics) {
+ Rect bounds = metric.getBounds();
+ Point displaySize = new Point(bounds.width(), bounds.height());
+ Point reversedDisplaySize = new Point(displaySize.y, displaySize.x);
+ for (Point point : List.of(displaySize, reversedDisplaySize)) {
+ int orientation = WallpaperManager.getOrientation(point);
+ // don't add an entry if there is already a larger display of the same orientation
+ Point display = mDefaultDisplaySizes.get(orientation);
+ if (display == null || display.x * display.y < point.x * point.y) {
+ mDefaultDisplaySizes.put(orientation, point);
+ }
+ }
+ if (populateOrientationPairs) {
+ int orientation = WallpaperManager.getOrientation(displaySize);
+ float newSurface = displaySize.x * displaySize.y * metric.getDensity();
+ if (surface <= 0) {
+ surface = newSurface;
+ firstOrientation = orientation;
+ } else {
+ Pair<Integer, Integer> pair = (newSurface > surface)
+ ? new Pair<>(firstOrientation, orientation)
+ : new Pair<>(orientation, firstOrientation);
+ Pair<Integer, Integer> rotatedPair = new Pair<>(
+ getRotatedOrientation(pair.first), getRotatedOrientation(pair.second));
+ mFoldableOrientationPairs.add(pair);
+ mFoldableOrientationPairs.add(rotatedPair);
+ }
+ }
+ }
}
DisplayData getDisplayDataOrCreate(int displayId) {
@@ -68,6 +123,12 @@ class WallpaperDisplayHelper {
return wpdData;
}
+ int getDefaultDisplayCurrentOrientation() {
+ Point displaySize = new Point();
+ mDisplayManager.getDisplay(DEFAULT_DISPLAY).getSize(displaySize);
+ return WallpaperManager.getOrientation(displaySize);
+ }
+
void removeDisplayData(int displayId) {
mDisplayDatas.remove(displayId);
}
@@ -133,4 +194,46 @@ class WallpaperDisplayHelper {
boolean isValidDisplay(int displayId) {
return mDisplayManager.getDisplay(displayId) != null;
}
+
+ SparseArray<Point> getDefaultDisplaySizes() {
+ return mDefaultDisplaySizes;
+ }
+
+ /** Return the number of pixel of the largest dimension of the default display */
+ int getDefaultDisplayLargestDimension() {
+ int result = -1;
+ for (int i = 0; i < mDefaultDisplaySizes.size(); i++) {
+ Point size = mDefaultDisplaySizes.valueAt(i);
+ result = Math.max(result, Math.max(size.x, size.y));
+ }
+ return result;
+ }
+
+ boolean isFoldable() {
+ return mIsFoldable;
+ }
+
+ /**
+ * If a given orientation corresponds to an unfolded orientation on foldable, return the
+ * corresponding folded orientation. Otherwise, return UNKNOWN. Always return UNKNOWN if the
+ * device is not a foldable.
+ */
+ int getFoldedOrientation(int orientation) {
+ for (Pair<Integer, Integer> pair : mFoldableOrientationPairs) {
+ if (pair.second.equals(orientation)) return pair.first;
+ }
+ return ORIENTATION_UNKNOWN;
+ }
+
+ /**
+ * If a given orientation corresponds to a folded orientation on foldable, return the
+ * corresponding unfolded orientation. Otherwise, return UNKNOWN. Always return UNKNOWN if the
+ * device is not a foldable.
+ */
+ int getUnfoldedOrientation(int orientation) {
+ for (Pair<Integer, Integer> pair : mFoldableOrientationPairs) {
+ if (pair.first.equals(orientation)) return pair.second;
+ }
+ return ORIENTATION_UNKNOWN;
+ }
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 3782b429f93a..cd998a2fdf0e 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -22,6 +22,7 @@ import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREG
import static android.app.WallpaperManager.COMMAND_REAPPLY;
import static android.app.WallpaperManager.FLAG_LOCK;
import static android.app.WallpaperManager.FLAG_SYSTEM;
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.ParcelFileDescriptor.MODE_CREATE;
@@ -74,6 +75,7 @@ import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.hardware.display.DisplayManager;
@@ -103,12 +105,15 @@ import android.service.wallpaper.IWallpaperService;
import android.service.wallpaper.WallpaperService;
import android.system.ErrnoException;
import android.system.Os;
+import android.text.TextUtils;
import android.util.EventLog;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
@@ -137,6 +142,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -189,8 +195,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
private final Object mLock = new Object();
- /** True to support different crops for different display dimensions */
- private final boolean mIsMultiCropEnabled;
/** Tracks wallpaper being migrated from system+lock to lock when setting static wp. */
WallpaperDestinationChangeHandler mPendingMigrationViaStatic;
@@ -804,6 +808,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
null /* options */);
mWindowManagerInternal.setWallpaperShowWhenLocked(
mToken, (wallpaper.mWhich & FLAG_LOCK) != 0);
+ if (multiCrop() && wallpaper.mSupportsMultiCrop) {
+ mWindowManagerInternal.setWallpaperCropHints(mToken,
+ mWallpaperCropper.getRelativeCropHints(wallpaper));
+ } else {
+ mWindowManagerInternal.setWallpaperCropHints(mToken, new SparseArray<>());
+ }
final DisplayData wpdData =
mWallpaperDisplayHelper.getDisplayDataOrCreate(mDisplayId);
try {
@@ -1479,10 +1489,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mIPackageManager = AppGlobals.getPackageManager();
mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
- DisplayManager dm = mContext.getSystemService(DisplayManager.class);
- dm.registerDisplayListener(mDisplayListener, null /* handler */);
- mWallpaperDisplayHelper = new WallpaperDisplayHelper(dm, mWindowManagerInternal);
+ DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+ displayManager.registerDisplayListener(mDisplayListener, null /* handler */);
+ WindowManager windowManager = mContext.getSystemService(WindowManager.class);
+ boolean isFoldable = mContext.getResources()
+ .getIntArray(R.array.config_foldedDeviceStates).length > 0;
+ mWallpaperDisplayHelper = new WallpaperDisplayHelper(
+ displayManager, windowManager, mWindowManagerInternal, isFoldable);
mWallpaperCropper = new WallpaperCropper(mWallpaperDisplayHelper);
+ mWindowManagerInternal.setWallpaperCropUtils(mWallpaperCropper::getCrop);
mActivityManager = mContext.getSystemService(ActivityManager.class);
if (mContext.getResources().getBoolean(
@@ -1522,7 +1537,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
mColorsChangedListeners = new SparseArray<>();
mWallpaperDataParser = new WallpaperDataParser(mContext, mWallpaperDisplayHelper,
mWallpaperCropper);
- mIsMultiCropEnabled = multiCrop();
LocalServices.addService(WallpaperManagerInternal.class, new LocalService());
}
@@ -2199,6 +2213,66 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
+ @Override
+ public List<Rect> getBitmapCrops(List<Point> displaySizes, @SetWallpaperFlags int which,
+ boolean originalBitmap, int userId) {
+ userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, false, true, "getBitmapCrop", null);
+ synchronized (mLock) {
+ checkPermission(READ_WALLPAPER_INTERNAL);
+ WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId)
+ : mWallpaperMap.get(userId);
+ if (wallpaper == null || !wallpaper.mSupportsMultiCrop) return null;
+ SparseArray<Rect> relativeSuggestedCrops =
+ mWallpaperCropper.getRelativeCropHints(wallpaper);
+ Point croppedBitmapSize =
+ new Point(wallpaper.cropHint.width(), wallpaper.cropHint.height());
+ SparseArray<Rect> relativeDefaultCrops =
+ mWallpaperCropper.getDefaultCrops(relativeSuggestedCrops, croppedBitmapSize);
+ SparseArray<Rect> adjustedRelativeSuggestedCrops = new SparseArray<>();
+ for (int i = 0; i < relativeDefaultCrops.size(); i++) {
+ int key = relativeDefaultCrops.keyAt(i);
+ if (relativeSuggestedCrops.contains(key)) {
+ adjustedRelativeSuggestedCrops.put(key, relativeDefaultCrops.get(key));
+ }
+ }
+ List<Rect> result = new ArrayList<>();
+ boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
+ == View.LAYOUT_DIRECTION_RTL;
+ for (Point displaySize : displaySizes) {
+ result.add(mWallpaperCropper.getCrop(
+ displaySize, croppedBitmapSize, adjustedRelativeSuggestedCrops, rtl));
+ }
+ if (originalBitmap) result = WallpaperCropper.getOriginalCropHints(wallpaper, result);
+ return result;
+ }
+ }
+
+ @Override
+ public List<Rect> getFutureBitmapCrops(Point bitmapSize, List<Point> displaySizes,
+ int[] screenOrientations, List<Rect> crops) {
+ SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops, ORIENTATION_UNKNOWN);
+ SparseArray<Rect> defaultCrops = mWallpaperCropper.getDefaultCrops(cropMap, bitmapSize);
+ List<Rect> result = new ArrayList<>();
+ boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
+ == View.LAYOUT_DIRECTION_RTL;
+ for (Point displaySize : displaySizes) {
+ result.add(mWallpaperCropper.getCrop(displaySize, bitmapSize, defaultCrops, rtl));
+ }
+ return result;
+ }
+
+ @Override
+ public Rect getBitmapCrop(Point bitmapSize, int[] screenOrientations, List<Rect> crops) {
+ if (!multiCrop()) {
+ throw new UnsupportedOperationException(
+ "This method should only be called with the multi crop flag enabled");
+ }
+ SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops, ORIENTATION_UNKNOWN);
+ SparseArray<Rect> defaultCrops = mWallpaperCropper.getDefaultCrops(cropMap, bitmapSize);
+ return WallpaperCropper.getTotalCrop(defaultCrops);
+ }
+
private boolean hasPermission(String permission) {
return mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED;
}
@@ -2755,8 +2829,18 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
@Override
public ParcelFileDescriptor setWallpaper(String name, String callingPackage,
- Rect cropHint, boolean allowBackup, Bundle extras, int which,
- IWallpaperManagerCallback completion, int userId) {
+ int[] screenOrientations, List<Rect> crops, boolean allowBackup,
+ Bundle extras, int which, IWallpaperManagerCallback completion, int userId) {
+
+ if (DEBUG) {
+ Slog.d(TAG, "setWallpaper: name = " + name + ", callingPackage = " + callingPackage
+ + ", screenOrientations = "
+ + (screenOrientations == null ? null
+ : Arrays.stream(screenOrientations).boxed().toList())
+ + ", crops = " + crops
+ + ", allowBackup = " + allowBackup);
+ }
+
userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
false /* all */, true /* full */, "changing wallpaper", null /* pkg */);
checkPermission(android.Manifest.permission.SET_WALLPAPER);
@@ -2771,10 +2855,17 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
return null;
}
+ int currentOrientation = mWallpaperDisplayHelper.getDefaultDisplayCurrentOrientation();
+ SparseArray<Rect> cropMap = !multiCrop() ? null
+ : getCropMap(screenOrientations, crops, currentOrientation);
+ Rect cropHint = multiCrop() || crops == null ? null : crops.get(0);
+ final boolean fromForegroundApp = !multiCrop() ? false
+ : isFromForegroundApp(callingPackage);
+
// "null" means the no-op crop, preserving the full input image
- if (cropHint == null) {
+ if (cropHint == null && !multiCrop()) {
cropHint = new Rect(0, 0, 0, 0);
- } else {
+ } else if (!multiCrop()) {
if (cropHint.width() < 0 || cropHint.height() < 0
|| cropHint.left < 0
|| cropHint.top < 0) {
@@ -2814,10 +2905,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
wallpaper.mSystemWasBoth = systemIsBoth;
wallpaper.mWhich = which;
wallpaper.setComplete = completion;
- wallpaper.fromForegroundApp = isFromForegroundApp(callingPackage);
- wallpaper.cropHint.set(cropHint);
+ wallpaper.fromForegroundApp = multiCrop() ? fromForegroundApp
+ : isFromForegroundApp(callingPackage);
+ if (!multiCrop()) wallpaper.cropHint.set(cropHint);
+ if (multiCrop()) wallpaper.mSupportsMultiCrop = true;
+ if (multiCrop()) wallpaper.mCropHints = cropMap;
wallpaper.allowBackup = allowBackup;
wallpaper.mWallpaperDimAmount = getWallpaperDimAmount();
+ wallpaper.mOrientationWhenSet = currentOrientation;
}
return pfd;
} finally {
@@ -2826,11 +2921,47 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
+ private SparseArray<Rect> getCropMap(int[] screenOrientations, List<Rect> crops,
+ int currentOrientation) {
+ if ((crops == null ^ screenOrientations == null)
+ || (crops != null && crops.size() != screenOrientations.length)) {
+ throw new IllegalArgumentException(
+ "Illegal crops/orientations lists: must both be null, or both the same size");
+ }
+ SparseArray<Rect> cropMap = new SparseArray<>();
+ boolean unknown = false;
+ if (crops != null && crops.size() != 0) {
+ for (int i = 0; i < crops.size(); i++) {
+ Rect crop = crops.get(i);
+ int width = crop.width(), height = crop.height();
+ if (width < 0 || height < 0 || crop.left < 0 || crop.top < 0) {
+ throw new IllegalArgumentException("Invalid crop rect supplied: " + crop);
+ }
+ int orientation = screenOrientations[i];
+ if (orientation == ORIENTATION_UNKNOWN) {
+ if (currentOrientation == ORIENTATION_UNKNOWN) {
+ throw new IllegalArgumentException(
+ "Invalid orientation: " + ORIENTATION_UNKNOWN);
+ }
+ unknown = true;
+ orientation = currentOrientation;
+ }
+ cropMap.put(orientation, crop);
+ }
+ }
+ if (unknown && cropMap.size() > 1) {
+ throw new IllegalArgumentException("Invalid crops supplied: the UNKNOWN screen "
+ + "orientation should only be used in a singleton map (in which case it"
+ + "represents the current orientation of the default display)");
+ }
+ return cropMap;
+ }
+
private void migrateStaticSystemToLockWallpaperLocked(int userId) {
WallpaperData sysWP = mWallpaperMap.get(userId);
if (sysWP == null) {
if (DEBUG) {
- Slog.i(TAG, "No system wallpaper? Not tracking for lock-only");
+ Slog.i(TAG, "No system wallpaper? Not tracking for lock-only");
}
return;
}
@@ -2839,6 +2970,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
WallpaperData lockWP = new WallpaperData(userId, FLAG_LOCK);
lockWP.wallpaperId = sysWP.wallpaperId;
lockWP.cropHint.set(sysWP.cropHint);
+ lockWP.mSupportsMultiCrop = sysWP.mSupportsMultiCrop;
+ if (sysWP.mCropHints != null) {
+ lockWP.mCropHints = sysWP.mCropHints.clone();
+ }
lockWP.allowBackup = sysWP.allowBackup;
lockWP.primaryColors = sysWP.primaryColors;
lockWP.mWallpaperDimAmount = sysWP.mWallpaperDimAmount;
@@ -2956,6 +3091,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
final long ident = Binder.clearCallingIdentity();
try {
+ newWallpaper.mSupportsMultiCrop = mImageWallpaper.equals(name);
newWallpaper.imageWallpaperPending = false;
newWallpaper.mWhich = which;
newWallpaper.mSystemWasBoth = systemIsBoth;
@@ -3428,11 +3564,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
return (wallpaper != null) ? wallpaper.allowBackup : false;
}
- @Override
- public boolean isMultiCropEnabled() {
- return mIsMultiCropEnabled;
- }
-
private void onDisplayReadyInternal(int displayId) {
synchronized (mLock) {
if (mLastWallpaper == null) {
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index a9f0554b2bec..d68f932400a2 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -34,6 +34,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.H.WALLPAPER_DRAW_PENDING_TIMEOUT;
+import static com.android.window.flags.Flags.multiCrop;
import android.annotation.Nullable;
import android.content.res.Resources;
@@ -48,6 +49,7 @@ import android.os.SystemClock;
import android.util.ArraySet;
import android.util.MathUtils;
import android.util.Slog;
+import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.SurfaceControl;
@@ -60,6 +62,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLogImpl;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ToBooleanFunction;
+import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -73,6 +76,7 @@ import java.util.function.Consumer;
class WallpaperController {
private static final String TAG = TAG_WITH_CLASS_NAME ? "WallpaperController" : TAG_WM;
private WindowManagerService mService;
+ private WallpaperCropUtils mWallpaperCropUtils = null;
private DisplayContent mDisplayContent;
private final ArrayList<WallpaperWindowToken> mWallpaperTokens = new ArrayList<>();
@@ -240,9 +244,8 @@ class WallpaperController {
mMinWallpaperScale =
resources.getFloat(com.android.internal.R.dimen.config_wallpaperMinScale);
mMaxWallpaperScale = resources.getFloat(R.dimen.config_wallpaperMaxScale);
- mShouldOffsetWallpaperCenter =
- resources.getBoolean(
- com.android.internal.R.bool.config_offsetWallpaperToCenterOfLargestDisplay);
+ mShouldOffsetWallpaperCenter = resources.getBoolean(
+ com.android.internal.R.bool.config_offsetWallpaperToCenterOfLargestDisplay);
}
void resetLargestDisplay(Display display) {
@@ -266,7 +269,7 @@ class WallpaperController {
}
@Nullable private Point findLargestDisplaySize() {
- if (!mShouldOffsetWallpaperCenter) {
+ if (!mShouldOffsetWallpaperCenter || multiCrop()) {
return null;
}
Point largestDisplaySize = new Point();
@@ -284,6 +287,10 @@ class WallpaperController {
return largestDisplaySize;
}
+ void setWallpaperCropUtils(WallpaperCropUtils wallpaperCropUtils) {
+ mWallpaperCropUtils = wallpaperCropUtils;
+ }
+
WindowState getWallpaperTarget() {
return mWallpaperTarget;
}
@@ -357,26 +364,92 @@ class WallpaperController {
boolean updateWallpaperOffset(WindowState wallpaperWin, boolean sync) {
// Size of the display the wallpaper is rendered on.
final Rect lastWallpaperBounds = wallpaperWin.getParentFrame();
- // Full size of the wallpaper (usually larger than bounds above to parallax scroll when
- // swiping through Launcher pages).
- final Rect wallpaperFrame = wallpaperWin.getFrame();
+ int screenWidth = lastWallpaperBounds.width();
+ int screenHeight = lastWallpaperBounds.height();
+ float screenRatio = ((float) screenWidth) / screenHeight;
+ Point screenSize = new Point(screenWidth, screenHeight);
+
WallpaperWindowToken token = wallpaperWin.mToken.asWallpaperToken();
- final int diffWidth = wallpaperFrame.width() - lastWallpaperBounds.width();
- final int diffHeight = wallpaperFrame.height() - lastWallpaperBounds.height();
- if ((wallpaperWin.mAttrs.flags & WindowManager.LayoutParams.FLAG_SCALED) != 0
- && Math.abs(diffWidth) > 1 && Math.abs(diffHeight) > 1) {
- Slog.d(TAG, "Skip wallpaper offset with inconsistent orientation, bounds="
- + lastWallpaperBounds + " frame=" + wallpaperFrame);
- // With FLAG_SCALED, the requested size should at least make the frame match one of
- // side. If both sides contain differences, the client side may not have updated the
- // latest size according to the current orientation. So skip calculating the offset to
- // avoid the wallpaper not filling the screen.
- return false;
+ /*
+ * TODO(b/270726737) adapt comments once flag gets removed and multiCrop is always true
+ * Size of the wallpaper. May have more width/height ratio than the screen for parallax.
+ *
+ * If multiCrop is true, we use a map, cropHints, defining which sub-area of the wallpaper
+ * to show for a given screen orientation. In this case, wallpaperFrame represents the
+ * sub-area of WallpaperWin to show for the current screen size.
+ *
+ * If multiCrop is false, don't show a custom sub-area of the wallpaper. Just show the
+ * whole wallpaperWin if possible, and center and zoom if necessary.
+ */
+ final Rect wallpaperFrame;
+
+ /*
+ * The values cropZoom, cropOffsetX and cropOffsetY are only used if multiCrop is true.
+ * Zoom and offsets to be applied in order to show wallpaperFrame on screen.
+ */
+ final float cropZoom;
+ final int cropOffsetX;
+ final int cropOffsetY;
+
+ /*
+ * Difference of width/height between the wallpaper and the screen.
+ * This is the additional room that we have to apply offsets (i.e. parallax).
+ */
+ final int diffWidth;
+ final int diffHeight;
+
+ /*
+ * zoom, offsetX and offsetY are not related to cropping the wallpaper:
+ * - zoom is used to apply an additional zoom (e.g. for launcher animations).
+ * - offsetX, offsetY are used to apply an offset to the wallpaper (e.g. parallax effect).
+ */
+ final float zoom;
+ int offsetX;
+ int offsetY;
+
+ if (multiCrop()) {
+ if (mWallpaperCropUtils == null) {
+ Slog.e(TAG, "Update wallpaper offsets before the system is ready. Aborting");
+ return false;
+ }
+ Point bitmapSize = new Point(
+ wallpaperWin.mRequestedWidth, wallpaperWin.mRequestedHeight);
+ SparseArray<Rect> cropHints = token.getCropHints();
+ wallpaperFrame = mWallpaperCropUtils.getCrop(
+ screenSize, bitmapSize, cropHints, wallpaperWin.isRtl());
+
+ cropZoom = wallpaperFrame.isEmpty() ? 1f
+ : ((float) screenHeight) / wallpaperFrame.height() / wallpaperWin.mVScale;
+
+ // A positive x / y offset shifts the wallpaper to the right / bottom respectively.
+ cropOffsetX = -wallpaperFrame.left
+ + (int) ((cropZoom - 1f) * wallpaperFrame.height() * screenRatio / 2f);
+ cropOffsetY = -wallpaperFrame.top
+ + (int) ((cropZoom - 1f) * wallpaperFrame.height() / 2f);
+
+ diffWidth = (int) (wallpaperFrame.width() * wallpaperWin.mHScale) - screenWidth;
+ diffHeight = (int) (wallpaperFrame.height() * wallpaperWin.mVScale) - screenHeight;
+ } else {
+ wallpaperFrame = wallpaperWin.getFrame();
+ cropZoom = 1f;
+ cropOffsetX = 0;
+ cropOffsetY = 0;
+ diffWidth = wallpaperFrame.width() - screenWidth;
+ diffHeight = wallpaperFrame.height() - screenHeight;
+
+ if ((wallpaperWin.mAttrs.flags & WindowManager.LayoutParams.FLAG_SCALED) != 0
+ && Math.abs(diffWidth) > 1 && Math.abs(diffHeight) > 1) {
+ Slog.d(TAG, "Skip wallpaper offset with inconsistent orientation, bounds="
+ + lastWallpaperBounds + " frame=" + wallpaperFrame);
+ // With FLAG_SCALED, the requested size should at least make the frame match one of
+ // side. If both sides contain differences, the client side may not have updated the
+ // latest size according to the current orientation. So skip calculating the offset
+ // to avoid the wallpaper not filling the screen.
+ return false;
+ }
}
- int newXOffset = 0;
- int newYOffset = 0;
boolean rawChanged = false;
// Set the default wallpaper x-offset to either edge of the screen (depending on RTL), to
// match the behavior of most Launchers
@@ -396,17 +469,17 @@ class WallpaperController {
int displayOffset = getDisplayWidthOffset(availw, lastWallpaperBounds,
wallpaperWin.isRtl());
availw -= displayOffset;
- int offset = availw > 0 ? -(int)(availw * wpx + .5f) : 0;
+ offsetX = availw > 0 ? -(int) (availw * wpx + .5f) : 0;
if (token.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
// if device is LTR, then offset wallpaper to the left (the wallpaper is drawn
// always starting from the left of the screen).
- offset += token.mWallpaperDisplayOffsetX;
+ offsetX += token.mWallpaperDisplayOffsetX;
} else if (!wallpaperWin.isRtl()) {
// In RTL the offset is calculated so that the wallpaper ends up right aligned (see
// offset above).
- offset -= displayOffset;
+ offsetX -= displayOffset;
}
- newXOffset = offset;
+ offsetX += cropOffsetX * wallpaperWin.mHScale;
if (wallpaperWin.mWallpaperX != wpx || wallpaperWin.mWallpaperXStep != wpxs) {
wallpaperWin.mWallpaperX = wpx;
@@ -416,11 +489,11 @@ class WallpaperController {
float wpy = token.mWallpaperY >= 0 ? token.mWallpaperY : 0.5f;
float wpys = token.mWallpaperYStep >= 0 ? token.mWallpaperYStep : -1.0f;
- offset = diffHeight > 0 ? -(int) (diffHeight * wpy + .5f) : 0;
+ offsetY = diffHeight > 0 ? -(int) (diffHeight * wpy + .5f) : 0;
if (token.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
- offset += token.mWallpaperDisplayOffsetY;
+ offsetY += token.mWallpaperDisplayOffsetY;
}
- newYOffset = offset;
+ offsetY += cropOffsetY * wallpaperWin.mVScale;
if (wallpaperWin.mWallpaperY != wpy || wallpaperWin.mWallpaperYStep != wpys) {
wallpaperWin.mWallpaperY = wpy;
@@ -432,10 +505,10 @@ class WallpaperController {
wallpaperWin.mWallpaperZoomOut = mLastWallpaperZoomOut;
rawChanged = true;
}
-
- boolean changed = wallpaperWin.setWallpaperOffset(newXOffset, newYOffset,
- wallpaperWin.mShouldScaleWallpaper
- ? zoomOutToScale(wallpaperWin.mWallpaperZoomOut) : 1);
+ zoom = wallpaperWin.mShouldScaleWallpaper
+ ? zoomOutToScale(wallpaperWin.mWallpaperZoomOut) : 1f;
+ final float totalZoom = zoom * cropZoom;
+ boolean changed = wallpaperWin.setWallpaperOffset(offsetX, offsetY, totalZoom);
if (rawChanged && (wallpaperWin.mAttrs.privateFlags &
WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS) != 0) {
@@ -496,7 +569,7 @@ class WallpaperController {
* display).
*/
private int getDisplayWidthOffset(int availWidth, Rect displayFrame, boolean isRtl) {
- if (!mShouldOffsetWallpaperCenter) {
+ if (!mShouldOffsetWallpaperCenter || multiCrop()) {
return 0;
}
if (mLargestDisplaySize == null) {
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 15bd6078dc2d..1bcd882b5d64 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -25,9 +25,11 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.Nullable;
+import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
+import android.util.SparseArray;
import android.view.animation.Animation;
import com.android.internal.protolog.common.ProtoLog;
@@ -49,6 +51,12 @@ class WallpaperWindowToken extends WindowToken {
int mWallpaperDisplayOffsetX = Integer.MIN_VALUE;
int mWallpaperDisplayOffsetY = Integer.MIN_VALUE;
+ /**
+ * Map from {@link android.app.WallpaperManager.ScreenOrientation} to crop rectangles.
+ * Crop rectangles represent the part of the wallpaper displayed for each screen orientation.
+ */
+ private SparseArray<Rect> mCropHints = new SparseArray<>();
+
WallpaperWindowToken(WindowManagerService service, IBinder token, boolean explicit,
DisplayContent dc, boolean ownerCanManageAppTokens) {
this(service, token, explicit, dc, ownerCanManageAppTokens, null /* options */);
@@ -98,6 +106,14 @@ class WallpaperWindowToken extends WindowToken {
return mShowWhenLocked;
}
+ void setCropHints(SparseArray<Rect> cropHints) {
+ mCropHints = cropHints.clone();
+ }
+
+ SparseArray<Rect> getCropHints() {
+ return mCropHints;
+ }
+
void sendWindowWallpaperCommand(
String action, int x, int y, int z, Bundle extras, boolean sync) {
for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 22b690e85c35..b55b8a7c11bb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -33,6 +33,7 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
import android.util.Pair;
+import android.util.SparseArray;
import android.view.ContentRecordingSession;
import android.view.Display;
import android.view.IInputFilter;
@@ -51,6 +52,7 @@ import android.window.ScreenCapture;
import com.android.internal.policy.KeyInterceptionInfo;
import com.android.server.input.InputManagerService;
import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils;
import com.android.server.wm.SensitiveContentPackages.PackageInfo;
import java.lang.annotation.Retention;
@@ -698,6 +700,21 @@ public abstract class WindowManagerInternal {
public abstract void setWallpaperShowWhenLocked(IBinder windowToken, boolean showWhenLocked);
/**
+ * Sets the crop hints of a {@link WallpaperWindowToken}. Only effective for image wallpapers.
+ *
+ * @param windowToken wallpaper token previously added via {@link #addWindowToken}
+ * @param cropHints a map that represents which part of the wallpaper should be shown, for
+ * each type of {@link android.app.WallpaperManager.ScreenOrientation}.
+ */
+ public abstract void setWallpaperCropHints(IBinder windowToken, SparseArray<Rect> cropHints);
+
+ /**
+ * Transmits the {@link WallpaperCropUtils} instance to {@link WallpaperController}.
+ * {@link WallpaperCropUtils} contains the helpers to properly position the wallpaper.
+ */
+ public abstract void setWallpaperCropUtils(WallpaperCropUtils wallpaperCropUtils);
+
+ /**
* Returns {@code true} if a Window owned by {@code uid} has focus.
*/
public abstract boolean isUidFocused(int uid);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9179acf2caed..f40b4c91ca69 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -153,6 +153,7 @@ import static com.android.server.wm.WindowManagerServiceDumpProto.INPUT_METHOD_W
import static com.android.server.wm.WindowManagerServiceDumpProto.POLICY;
import static com.android.server.wm.WindowManagerServiceDumpProto.ROOT_WINDOW_CONTAINER;
import static com.android.server.wm.WindowManagerServiceDumpProto.WINDOW_FRAMES_VALID;
+import static com.android.window.flags.Flags.multiCrop;
import android.Manifest;
import android.Manifest.permission;
@@ -238,6 +239,7 @@ import android.util.EventLog;
import android.util.MergedConfiguration;
import android.util.Pair;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
@@ -342,6 +344,7 @@ import com.android.server.policy.WindowManagerPolicy;
import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
import com.android.server.power.ShutdownThread;
import com.android.server.utils.PriorityDump;
+import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils;
import dalvik.annotation.optimization.NeverCompile;
@@ -8129,6 +8132,25 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
+ public void setWallpaperCropHints(IBinder binder, SparseArray<Rect> cropHints) {
+ synchronized (mGlobalLock) {
+ final WindowToken token = mRoot.getWindowToken(binder);
+ if (token == null || token.asWallpaperToken() == null) {
+ ProtoLog.w(WM_ERROR,
+ "setWallpaperCropHints: non-existent wallpaper token: %s", binder);
+ return;
+ }
+ token.asWallpaperToken().setCropHints(cropHints);
+ }
+ }
+
+ @Override
+ public void setWallpaperCropUtils(WallpaperCropUtils wallpaperCropUtils) {
+ mRoot.getDisplayContent(DEFAULT_DISPLAY).mWallpaperController
+ .setWallpaperCropUtils(wallpaperCropUtils);
+ }
+
+ @Override
public boolean isUidFocused(int uid) {
synchronized (mGlobalLock) {
for (int i = mRoot.getChildCount() - 1; i >= 0; i--) {
@@ -9354,7 +9376,8 @@ public class WindowManagerService extends IWindowManager.Stub
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
- if (!mAtmService.isCallerRecents(callingUid)) {
+ if (!mAtmService.isCallerRecents(callingUid)
+ && (!multiCrop() || callingUid != SYSTEM_UID)) {
Slog.e(TAG, "Unable to verify uid for getPossibleDisplayInfo"
+ " on uid " + callingUid);
return new ArrayList<>();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 6216acbfe465..73d386a328f5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -32,6 +32,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.window.flags.Flags.multiCrop;
import static com.google.common.truth.Truth.assertThat;
@@ -39,6 +40,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -484,6 +486,7 @@ public class WallpaperControllerTests extends WindowTestsBase {
@Test
public void testUpdateWallpaperOffset_resize_shouldCenterEnabled() {
+ assumeFalse(multiCrop());
final DisplayContent dc = new TestDisplayContent.Builder(mAtm, INITIAL_WIDTH,
INITIAL_HEIGHT).build();
dc.mWallpaperController.setShouldOffsetWallpaperCenter(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 7ae5a1156d07..114b9c3a68f2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -119,6 +119,7 @@ import android.window.TransitionRequestInfo;
import com.android.internal.policy.AttributeCache;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils;
import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
import org.junit.After;
@@ -286,6 +287,18 @@ class WindowTestsBase extends SystemServiceTestsBase {
mAtm.mWindowManager.mLetterboxConfiguration
.setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(false);
+ // Setup WallpaperController crop utils with a simple center-align strategy
+ WallpaperCropUtils cropUtils = (displaySize, bitmapSize, suggestedCrops, rtl) -> {
+ Rect crop = new Rect(0, 0, displaySize.x, displaySize.y);
+ crop.scale(Math.min(
+ ((float) bitmapSize.x) / displaySize.x,
+ ((float) bitmapSize.y) / displaySize.y));
+ crop.offset((bitmapSize.x - crop.width()) / 2, (bitmapSize.y - crop.height()) / 2);
+ return crop;
+ };
+ mDisplayContent.mWallpaperController.setWallpaperCropUtils(cropUtils);
+ mDefaultDisplay.mWallpaperController.setWallpaperCropUtils(cropUtils);
+
checkDeviceSpecificOverridesNotApplied();
}