Loading packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java +253 −50 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ 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 android.os.ParcelFileDescriptor.MODE_READ_ONLY; import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_INELIGIBLE; import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_METADATA; Loading @@ -39,6 +40,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.graphics.BitmapFactory; import android.graphics.Point; import android.graphics.Rect; import android.hardware.display.DisplayManager; Loading Loading @@ -109,22 +111,16 @@ public class WallpaperBackupAgent extends BackupAgent { static final String LOCK_WALLPAPER_STAGE = "wallpaper-lock-stage"; @VisibleForTesting static final String WALLPAPER_INFO_STAGE = "wallpaper-info-stage"; @VisibleForTesting static final String WALLPAPER_BACKUP_DEVICE_INFO_STAGE = "wallpaper-backup-device-info-stage"; static final String EMPTY_SENTINEL = "empty"; static final String QUOTA_SENTINEL = "quota"; // Shared preferences constants. static final String PREFS_NAME = "wbprefs.xml"; static final String SYSTEM_GENERATION = "system_gen"; static final String LOCK_GENERATION = "lock_gen"; /** * An approximate area threshold to compare device dimension similarity */ static final int AREA_THRESHOLD = 50; // TODO (b/327637867): determine appropriate threshold static final float DEFAULT_ACCEPTABLE_PARALLAX = 0.2f; // If this file exists, it means we exceeded our quota last time private File mQuotaFile; Loading Loading @@ -336,7 +332,6 @@ public class WallpaperBackupAgent extends BackupAgent { mEventLogger.onSystemImageWallpaperBackupFailed(error); } private void backupLockWallpaperFileIfItExists(SharedPreferences sharedPrefs, boolean lockChanged, int lockGeneration, FullBackupDataOutput data) throws IOException { final File lockImageStage = new File(getFilesDir(), LOCK_WALLPAPER_STAGE); Loading Loading @@ -409,6 +404,16 @@ public class WallpaperBackupAgent extends BackupAgent { } } private static String readText(TypedXmlPullParser parser) throws IOException, XmlPullParserException { String result = ""; if (parser.next() == XmlPullParser.TEXT) { result = parser.getText(); parser.nextTag(); } return result; } @VisibleForTesting // fullBackupFile is final, so we intercept backups here in tests. protected void backupFile(File file, FullBackupDataOutput data) { Loading Loading @@ -438,18 +443,10 @@ public class WallpaperBackupAgent extends BackupAgent { boolean lockImageStageExists = lockImageStage.exists(); try { // Parse the device dimensions of the source device and compare with target to // to identify whether we need to skip the remainder of the restore process // Parse the device dimensions of the source device Pair<Point, Point> sourceDeviceDimensions = parseDeviceDimensions( deviceDimensionsStage); Point targetDeviceDimensions = getScreenDimensions(); if (sourceDeviceDimensions != null && targetDeviceDimensions != null && isSourceDeviceSignificantlySmallerThanTarget(sourceDeviceDimensions.first, targetDeviceDimensions)) { Slog.d(TAG, "The source device is significantly smaller than target"); } // First parse the live component name so that we know for logging if we care about // logging errors with the image restore. ComponentName wpService = parseWallpaperComponent(infoStage, "wp"); Loading @@ -466,9 +463,10 @@ public class WallpaperBackupAgent extends BackupAgent { // to back up the original image on the source device, or there was no user-supplied // wallpaper image present. if (lockImageStageExists) { restoreFromStage(lockImageStage, infoStage, "kwp", FLAG_LOCK); restoreFromStage(lockImageStage, infoStage, "kwp", FLAG_LOCK, sourceDeviceDimensions); } restoreFromStage(imageStage, infoStage, "wp", sysWhich); restoreFromStage(imageStage, infoStage, "wp", sysWhich, sourceDeviceDimensions); // And reset to the wallpaper service we should be using if (mLockHasLiveComponent) { Loading Loading @@ -543,16 +541,6 @@ public class WallpaperBackupAgent extends BackupAgent { } } private static String readText(TypedXmlPullParser parser) throws IOException, XmlPullParserException { String result = ""; if (parser.next() == XmlPullParser.TEXT) { result = parser.getText(); parser.nextTag(); } return result; } @VisibleForTesting void updateWallpaperComponent(ComponentName wpService, int which) throws IOException { Loading @@ -578,10 +566,13 @@ public class WallpaperBackupAgent extends BackupAgent { } } private void restoreFromStage(File stage, File info, String hintTag, int which) private void restoreFromStage(File stage, File info, String hintTag, int which, Pair<Point, Point> sourceDeviceDimensions) throws IOException { if (stage.exists()) { if (multiCrop()) { // TODO(b/332937943): implement offset adjustment by manually adjusting crop to // adhere to device aspect ratio SparseArray<Rect> cropHints = parseCropHints(info, hintTag); if (cropHints != null) { Slog.i(TAG, "Got restored wallpaper; applying which=" + which Loading @@ -601,7 +592,6 @@ public class WallpaperBackupAgent extends BackupAgent { } 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); Loading @@ -609,8 +599,33 @@ public class WallpaperBackupAgent extends BackupAgent { Slog.i(TAG, "Got restored wallpaper; applying which=" + which + "; cropHint = " + cropHint); try (FileInputStream in = new FileInputStream(stage)) { mWallpaperManager.setStream(in, cropHint.isEmpty() ? null : cropHint, true, which); if (sourceDeviceDimensions != null && sourceDeviceDimensions.first != null) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; ParcelFileDescriptor pdf = ParcelFileDescriptor.open(stage, MODE_READ_ONLY); BitmapFactory.decodeFileDescriptor(pdf.getFileDescriptor(), null, options); Point bitmapSize = new Point(options.outWidth, options.outHeight); Point sourceDeviceSize = new Point(sourceDeviceDimensions.first.x, sourceDeviceDimensions.first.y); Point targetDeviceDimensions = getScreenDimensions(); // TODO: for now we handle only the case where the target device has smaller // aspect ratio than the source device i.e. the target device is more narrow // than the source device if (isTargetMoreNarrowThanSource(targetDeviceDimensions, sourceDeviceSize)) { Rect adjustedCrop = findNewCropfromOldCrop(cropHint, sourceDeviceDimensions.first, true, targetDeviceDimensions, bitmapSize, true); cropHint.set(adjustedCrop); } } mWallpaperManager.setStream(in, cropHint.isEmpty() ? null : cropHint, true, which); // And log the success if ((which & FLAG_SYSTEM) > 0) { Loading @@ -629,6 +644,209 @@ public class WallpaperBackupAgent extends BackupAgent { } } /** * This method computes the crop of the stored wallpaper to preserve its center point as the * user had set it in the previous device. * * The algorithm involves first computing the original crop of the user (without parallax). Then * manually adjusting the user's original crop to respect the current device's aspect ratio * (thereby preserving the center point). Then finally, adding any leftover image real-estate * (i.e. space left over on the horizontal axis) to add parallax effect. Parallax is only added * if was present in the old device's settings. * */ private Rect findNewCropfromOldCrop(Rect oldCrop, Point oldDisplaySize, boolean oldRtl, Point newDisplaySize, Point bitmapSize, boolean newRtl) { Rect cropWithoutParallax = withoutParallax(oldCrop, oldDisplaySize, oldRtl, bitmapSize); oldCrop = oldCrop.isEmpty() ? new Rect(0, 0, bitmapSize.x, bitmapSize.y) : oldCrop; float oldParallaxAmount = ((float) oldCrop.width() / cropWithoutParallax.width()) - 1; Rect newCropWithSameCenterWithoutParallax = sameCenter(newDisplaySize, bitmapSize, cropWithoutParallax); Rect newCrop = newCropWithSameCenterWithoutParallax; // calculate the amount of left-over space there is in the image after adjusting the crop // from the above operation i.e. in a rtl configuration, this is the remaining space in the // image after subtracting the new crop's right edge coordinate from the image itself, and // for ltr, its just the new crop's left edge coordinate (as it's the distance from the // beginning of the image) int widthAvailableForParallaxOnTheNewDevice = (newRtl) ? newCrop.left : bitmapSize.x - newCrop.right; // calculate relatively how much this available space is as a fraction of the total cropped // image float availableParallaxAmount = (float) widthAvailableForParallaxOnTheNewDevice / newCrop.width(); float minAcceptableParallax = Math.min(DEFAULT_ACCEPTABLE_PARALLAX, oldParallaxAmount); if (DEBUG) { Slog.d(TAG, "- cropWithoutParallax: " + cropWithoutParallax); Slog.d(TAG, "- oldParallaxAmount: " + oldParallaxAmount); Slog.d(TAG, "- newCropWithSameCenterWithoutParallax: " + newCropWithSameCenterWithoutParallax); Slog.d(TAG, "- widthAvailableForParallaxOnTheNewDevice: " + widthAvailableForParallaxOnTheNewDevice); Slog.d(TAG, "- availableParallaxAmount: " + availableParallaxAmount); Slog.d(TAG, "- minAcceptableParallax: " + minAcceptableParallax); Slog.d(TAG, "- oldCrop: " + oldCrop); Slog.d(TAG, "- oldDisplaySize: " + oldDisplaySize); Slog.d(TAG, "- oldRtl: " + oldRtl); Slog.d(TAG, "- newDisplaySize: " + newDisplaySize); Slog.d(TAG, "- bitmapSize: " + bitmapSize); Slog.d(TAG, "- newRtl: " + newRtl); } if (availableParallaxAmount >= minAcceptableParallax) { // but in any case, don't put more parallax than the amount of the old device float parallaxToAdd = Math.min(availableParallaxAmount, oldParallaxAmount); int widthToAddForParallax = (int) (newCrop.width() * parallaxToAdd); if (DEBUG) { Slog.d(TAG, "- parallaxToAdd: " + parallaxToAdd); Slog.d(TAG, "- widthToAddForParallax: " + widthToAddForParallax); } if (newRtl) { newCrop.left -= widthToAddForParallax; } else { newCrop.right += widthToAddForParallax; } } return newCrop; } /** * This method computes the original crop of the user without parallax. * * NOTE: When the user sets the wallpaper with a specific crop, there may additional image added * to the crop to support parallax. In order to determine the user's actual crop the parallax * must be removed if it exists. */ Rect withoutParallax(Rect crop, Point displaySize, boolean rtl, Point bitmapSize) { // in the case an image's crop is not set, we assume the image itself is cropped if (crop.isEmpty()) { crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y); } if (DEBUG) { Slog.w(TAG, "- crop: " + crop); } Rect adjustedCrop = new Rect(crop); float suggestedDisplayRatio = (float) displaySize.x / displaySize.y; // here we calculate the width of the wallpaper image such that it has the same aspect ratio // as the given display i.e. the width of the image on a single page of the device without // parallax (i.e. displaySize will correspond to the display the crop was originally set on) int wallpaperWidthWithoutParallax = (int) (0.5f + (float) displaySize.x * crop.height() / displaySize.y); // subtracting wallpaperWidthWithoutParallax from the wallpaper crop gives the amount of // parallax added int widthToRemove = Math.max(0, crop.width() - wallpaperWidthWithoutParallax); if (DEBUG) { Slog.d(TAG, "- adjustedCrop: " + adjustedCrop); Slog.d(TAG, "- suggestedDisplayRatio: " + suggestedDisplayRatio); Slog.d(TAG, "- wallpaperWidthWithoutParallax: " + wallpaperWidthWithoutParallax); Slog.d(TAG, "- widthToRemove: " + widthToRemove); } if (rtl) { adjustedCrop.left += widthToRemove; } else { adjustedCrop.right -= widthToRemove; } if (DEBUG) { Slog.d(TAG, "- adjustedCrop: " + crop); } return adjustedCrop; } /** * This method computes a new crop based on the given crop in order to preserve the center point * of the given crop on the provided displaySize. This is only for the case where the device * displaySize has a smaller aspect ratio than the cropped image. * * NOTE: If the width to height ratio is less in the device display than cropped image * this means the aspect ratios are off and there will be distortions in the image * if the image is applied to the current display (i.e. the image will be skewed -> * pixels in the image will not align correctly with the same pixels in the image that are * above them) */ Rect sameCenter(Point displaySize, Point bitmapSize, Rect crop) { // in the case an image's crop is not set, we assume the image itself is cropped if (crop.isEmpty()) { crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y); } float screenRatio = (float) displaySize.x / displaySize.y; float cropRatio = (float) crop.width() / crop.height(); Rect adjustedCrop = new Rect(crop); if (screenRatio < cropRatio) { // the screen is more narrow than the image, and as such, the image will need to be // zoomed in till it fits in the vertical axis. Due to this, we need to manually adjust // the image's crop in order for it to fit into the screen without having the framework // do it (since the framework left aligns the image after zooming) // Calculate the height of the adjusted wallpaper crop so it respects the aspect ratio // of the device. To calculate the height, we will use the width of the current crop. // This is so we find the largest height possible which also respects the device aspect // ratio. int heightToAdd = (int) (0.5f + crop.width() / screenRatio - crop.height()); // Calculate how much extra image space available that can be used to adjust // the crop. If this amount is less than heightToAdd, from above, then that means we // can't use heightToAdd. Instead we will need to use the maximum possible height, which // is the height of the original bitmap. NOTE: the bitmap height may be different than // the crop. // since there is no guarantee to have height available on both sides // (e.g. the available height might be fully at the bottom), grab the minimum int availableHeight = 2 * Math.min(crop.top, bitmapSize.y - crop.bottom); int actualHeightToAdd = Math.min(heightToAdd, availableHeight); // half of the additional height is added to the top and bottom of the crop adjustedCrop.top -= actualHeightToAdd / 2 + actualHeightToAdd % 2; adjustedCrop.bottom += actualHeightToAdd / 2; // Calculate the width of the adjusted crop. Initially we used the fixed width of the // crop to calculate the heightToAdd, but since this height may be invalid (based on // the calculation above) we calculate the width again instead of using the fixed width, // using the adjustedCrop's updated height. int widthToRemove = (int) (0.5f + crop.width() - adjustedCrop.height() * screenRatio); // half of the additional width is subtracted from the left and right side of the crop int widthToRemoveLeft = widthToRemove / 2; int widthToRemoveRight = widthToRemove / 2 + widthToRemove % 2; adjustedCrop.left += widthToRemoveLeft; adjustedCrop.right -= widthToRemoveRight; if (DEBUG) { Slog.d(TAG, "cropRatio: " + cropRatio); Slog.d(TAG, "screenRatio: " + screenRatio); Slog.d(TAG, "heightToAdd: " + heightToAdd); Slog.d(TAG, "actualHeightToAdd: " + actualHeightToAdd); Slog.d(TAG, "availableHeight: " + availableHeight); Slog.d(TAG, "widthToRemove: " + widthToRemove); Slog.d(TAG, "adjustedCrop: " + adjustedCrop); } return adjustedCrop; } return adjustedCrop; } private boolean isTargetMoreNarrowThanSource(Point targetDisplaySize, Point srcDisplaySize) { float targetScreenRatio = (float) targetDisplaySize.x / targetDisplaySize.y; float srcScreenRatio = (float) srcDisplaySize.x / srcDisplaySize.y; return (targetScreenRatio < srcScreenRatio); } private void logRestoreErrorIfNoLiveComponent(int which, String error) { if (mSystemHasLiveComponent) { return; Loading @@ -644,6 +862,7 @@ public class WallpaperBackupAgent extends BackupAgent { mEventLogger.onLockImageWallpaperRestoreFailed(error); } } private Rect parseCropHint(File wallpaperInfo, String sectionTag) { Rect cropHint = new Rect(); try (FileInputStream stream = new FileInputStream(wallpaperInfo)) { Loading Loading @@ -907,22 +1126,6 @@ public class WallpaperBackupAgent extends BackupAgent { return internalDisplays; } /** * This method compares the source and target dimensions, and returns true if there is a * significant difference in area between them and the source dimensions are smaller than the * target dimensions. * * @param sourceDimensions is the dimensions of the source device * @param targetDimensions is the dimensions of the target device */ @VisibleForTesting boolean isSourceDeviceSignificantlySmallerThanTarget(Point sourceDimensions, Point targetDimensions) { int rawAreaDelta = (targetDimensions.x * targetDimensions.y) - (sourceDimensions.x * sourceDimensions.y); return rawAreaDelta > AREA_THRESHOLD; } @VisibleForTesting boolean isDeviceInRestore() { try { Loading packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java +0 −21 Original line number Diff line number Diff line Loading @@ -59,7 +59,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.Point; import android.graphics.Rect; import android.os.FileUtils; import android.os.ParcelFileDescriptor; Loading Loading @@ -841,26 +840,6 @@ public class WallpaperBackupAgentTest { testParseCropHints(testMap); } @Test public void test_sourceDimensionsAreLargerThanTarget() { // source device is larger than target, expecting to get false Point sourceDimensions = new Point(2208, 1840); Point targetDimensions = new Point(1080, 2092); boolean isSourceSmaller = mWallpaperBackupAgent .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions); assertThat(isSourceSmaller).isEqualTo(false); } @Test public void test_sourceDimensionsMuchSmallerThanTarget() { // source device is smaller than target, expecting to get true Point sourceDimensions = new Point(1080, 2092); Point targetDimensions = new Point(2208, 1840); boolean isSourceSmaller = mWallpaperBackupAgent .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions); assertThat(isSourceSmaller).isEqualTo(true); } private void testParseCropHints(Map<Integer, Rect> testMap) throws Exception { assumeTrue(multiCrop()); mockRestoredStaticWallpaperFile(testMap); Loading Loading
packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java +253 −50 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ 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 android.os.ParcelFileDescriptor.MODE_READ_ONLY; import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_INELIGIBLE; import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_METADATA; Loading @@ -39,6 +40,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.graphics.BitmapFactory; import android.graphics.Point; import android.graphics.Rect; import android.hardware.display.DisplayManager; Loading Loading @@ -109,22 +111,16 @@ public class WallpaperBackupAgent extends BackupAgent { static final String LOCK_WALLPAPER_STAGE = "wallpaper-lock-stage"; @VisibleForTesting static final String WALLPAPER_INFO_STAGE = "wallpaper-info-stage"; @VisibleForTesting static final String WALLPAPER_BACKUP_DEVICE_INFO_STAGE = "wallpaper-backup-device-info-stage"; static final String EMPTY_SENTINEL = "empty"; static final String QUOTA_SENTINEL = "quota"; // Shared preferences constants. static final String PREFS_NAME = "wbprefs.xml"; static final String SYSTEM_GENERATION = "system_gen"; static final String LOCK_GENERATION = "lock_gen"; /** * An approximate area threshold to compare device dimension similarity */ static final int AREA_THRESHOLD = 50; // TODO (b/327637867): determine appropriate threshold static final float DEFAULT_ACCEPTABLE_PARALLAX = 0.2f; // If this file exists, it means we exceeded our quota last time private File mQuotaFile; Loading Loading @@ -336,7 +332,6 @@ public class WallpaperBackupAgent extends BackupAgent { mEventLogger.onSystemImageWallpaperBackupFailed(error); } private void backupLockWallpaperFileIfItExists(SharedPreferences sharedPrefs, boolean lockChanged, int lockGeneration, FullBackupDataOutput data) throws IOException { final File lockImageStage = new File(getFilesDir(), LOCK_WALLPAPER_STAGE); Loading Loading @@ -409,6 +404,16 @@ public class WallpaperBackupAgent extends BackupAgent { } } private static String readText(TypedXmlPullParser parser) throws IOException, XmlPullParserException { String result = ""; if (parser.next() == XmlPullParser.TEXT) { result = parser.getText(); parser.nextTag(); } return result; } @VisibleForTesting // fullBackupFile is final, so we intercept backups here in tests. protected void backupFile(File file, FullBackupDataOutput data) { Loading Loading @@ -438,18 +443,10 @@ public class WallpaperBackupAgent extends BackupAgent { boolean lockImageStageExists = lockImageStage.exists(); try { // Parse the device dimensions of the source device and compare with target to // to identify whether we need to skip the remainder of the restore process // Parse the device dimensions of the source device Pair<Point, Point> sourceDeviceDimensions = parseDeviceDimensions( deviceDimensionsStage); Point targetDeviceDimensions = getScreenDimensions(); if (sourceDeviceDimensions != null && targetDeviceDimensions != null && isSourceDeviceSignificantlySmallerThanTarget(sourceDeviceDimensions.first, targetDeviceDimensions)) { Slog.d(TAG, "The source device is significantly smaller than target"); } // First parse the live component name so that we know for logging if we care about // logging errors with the image restore. ComponentName wpService = parseWallpaperComponent(infoStage, "wp"); Loading @@ -466,9 +463,10 @@ public class WallpaperBackupAgent extends BackupAgent { // to back up the original image on the source device, or there was no user-supplied // wallpaper image present. if (lockImageStageExists) { restoreFromStage(lockImageStage, infoStage, "kwp", FLAG_LOCK); restoreFromStage(lockImageStage, infoStage, "kwp", FLAG_LOCK, sourceDeviceDimensions); } restoreFromStage(imageStage, infoStage, "wp", sysWhich); restoreFromStage(imageStage, infoStage, "wp", sysWhich, sourceDeviceDimensions); // And reset to the wallpaper service we should be using if (mLockHasLiveComponent) { Loading Loading @@ -543,16 +541,6 @@ public class WallpaperBackupAgent extends BackupAgent { } } private static String readText(TypedXmlPullParser parser) throws IOException, XmlPullParserException { String result = ""; if (parser.next() == XmlPullParser.TEXT) { result = parser.getText(); parser.nextTag(); } return result; } @VisibleForTesting void updateWallpaperComponent(ComponentName wpService, int which) throws IOException { Loading @@ -578,10 +566,13 @@ public class WallpaperBackupAgent extends BackupAgent { } } private void restoreFromStage(File stage, File info, String hintTag, int which) private void restoreFromStage(File stage, File info, String hintTag, int which, Pair<Point, Point> sourceDeviceDimensions) throws IOException { if (stage.exists()) { if (multiCrop()) { // TODO(b/332937943): implement offset adjustment by manually adjusting crop to // adhere to device aspect ratio SparseArray<Rect> cropHints = parseCropHints(info, hintTag); if (cropHints != null) { Slog.i(TAG, "Got restored wallpaper; applying which=" + which Loading @@ -601,7 +592,6 @@ public class WallpaperBackupAgent extends BackupAgent { } 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); Loading @@ -609,8 +599,33 @@ public class WallpaperBackupAgent extends BackupAgent { Slog.i(TAG, "Got restored wallpaper; applying which=" + which + "; cropHint = " + cropHint); try (FileInputStream in = new FileInputStream(stage)) { mWallpaperManager.setStream(in, cropHint.isEmpty() ? null : cropHint, true, which); if (sourceDeviceDimensions != null && sourceDeviceDimensions.first != null) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; ParcelFileDescriptor pdf = ParcelFileDescriptor.open(stage, MODE_READ_ONLY); BitmapFactory.decodeFileDescriptor(pdf.getFileDescriptor(), null, options); Point bitmapSize = new Point(options.outWidth, options.outHeight); Point sourceDeviceSize = new Point(sourceDeviceDimensions.first.x, sourceDeviceDimensions.first.y); Point targetDeviceDimensions = getScreenDimensions(); // TODO: for now we handle only the case where the target device has smaller // aspect ratio than the source device i.e. the target device is more narrow // than the source device if (isTargetMoreNarrowThanSource(targetDeviceDimensions, sourceDeviceSize)) { Rect adjustedCrop = findNewCropfromOldCrop(cropHint, sourceDeviceDimensions.first, true, targetDeviceDimensions, bitmapSize, true); cropHint.set(adjustedCrop); } } mWallpaperManager.setStream(in, cropHint.isEmpty() ? null : cropHint, true, which); // And log the success if ((which & FLAG_SYSTEM) > 0) { Loading @@ -629,6 +644,209 @@ public class WallpaperBackupAgent extends BackupAgent { } } /** * This method computes the crop of the stored wallpaper to preserve its center point as the * user had set it in the previous device. * * The algorithm involves first computing the original crop of the user (without parallax). Then * manually adjusting the user's original crop to respect the current device's aspect ratio * (thereby preserving the center point). Then finally, adding any leftover image real-estate * (i.e. space left over on the horizontal axis) to add parallax effect. Parallax is only added * if was present in the old device's settings. * */ private Rect findNewCropfromOldCrop(Rect oldCrop, Point oldDisplaySize, boolean oldRtl, Point newDisplaySize, Point bitmapSize, boolean newRtl) { Rect cropWithoutParallax = withoutParallax(oldCrop, oldDisplaySize, oldRtl, bitmapSize); oldCrop = oldCrop.isEmpty() ? new Rect(0, 0, bitmapSize.x, bitmapSize.y) : oldCrop; float oldParallaxAmount = ((float) oldCrop.width() / cropWithoutParallax.width()) - 1; Rect newCropWithSameCenterWithoutParallax = sameCenter(newDisplaySize, bitmapSize, cropWithoutParallax); Rect newCrop = newCropWithSameCenterWithoutParallax; // calculate the amount of left-over space there is in the image after adjusting the crop // from the above operation i.e. in a rtl configuration, this is the remaining space in the // image after subtracting the new crop's right edge coordinate from the image itself, and // for ltr, its just the new crop's left edge coordinate (as it's the distance from the // beginning of the image) int widthAvailableForParallaxOnTheNewDevice = (newRtl) ? newCrop.left : bitmapSize.x - newCrop.right; // calculate relatively how much this available space is as a fraction of the total cropped // image float availableParallaxAmount = (float) widthAvailableForParallaxOnTheNewDevice / newCrop.width(); float minAcceptableParallax = Math.min(DEFAULT_ACCEPTABLE_PARALLAX, oldParallaxAmount); if (DEBUG) { Slog.d(TAG, "- cropWithoutParallax: " + cropWithoutParallax); Slog.d(TAG, "- oldParallaxAmount: " + oldParallaxAmount); Slog.d(TAG, "- newCropWithSameCenterWithoutParallax: " + newCropWithSameCenterWithoutParallax); Slog.d(TAG, "- widthAvailableForParallaxOnTheNewDevice: " + widthAvailableForParallaxOnTheNewDevice); Slog.d(TAG, "- availableParallaxAmount: " + availableParallaxAmount); Slog.d(TAG, "- minAcceptableParallax: " + minAcceptableParallax); Slog.d(TAG, "- oldCrop: " + oldCrop); Slog.d(TAG, "- oldDisplaySize: " + oldDisplaySize); Slog.d(TAG, "- oldRtl: " + oldRtl); Slog.d(TAG, "- newDisplaySize: " + newDisplaySize); Slog.d(TAG, "- bitmapSize: " + bitmapSize); Slog.d(TAG, "- newRtl: " + newRtl); } if (availableParallaxAmount >= minAcceptableParallax) { // but in any case, don't put more parallax than the amount of the old device float parallaxToAdd = Math.min(availableParallaxAmount, oldParallaxAmount); int widthToAddForParallax = (int) (newCrop.width() * parallaxToAdd); if (DEBUG) { Slog.d(TAG, "- parallaxToAdd: " + parallaxToAdd); Slog.d(TAG, "- widthToAddForParallax: " + widthToAddForParallax); } if (newRtl) { newCrop.left -= widthToAddForParallax; } else { newCrop.right += widthToAddForParallax; } } return newCrop; } /** * This method computes the original crop of the user without parallax. * * NOTE: When the user sets the wallpaper with a specific crop, there may additional image added * to the crop to support parallax. In order to determine the user's actual crop the parallax * must be removed if it exists. */ Rect withoutParallax(Rect crop, Point displaySize, boolean rtl, Point bitmapSize) { // in the case an image's crop is not set, we assume the image itself is cropped if (crop.isEmpty()) { crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y); } if (DEBUG) { Slog.w(TAG, "- crop: " + crop); } Rect adjustedCrop = new Rect(crop); float suggestedDisplayRatio = (float) displaySize.x / displaySize.y; // here we calculate the width of the wallpaper image such that it has the same aspect ratio // as the given display i.e. the width of the image on a single page of the device without // parallax (i.e. displaySize will correspond to the display the crop was originally set on) int wallpaperWidthWithoutParallax = (int) (0.5f + (float) displaySize.x * crop.height() / displaySize.y); // subtracting wallpaperWidthWithoutParallax from the wallpaper crop gives the amount of // parallax added int widthToRemove = Math.max(0, crop.width() - wallpaperWidthWithoutParallax); if (DEBUG) { Slog.d(TAG, "- adjustedCrop: " + adjustedCrop); Slog.d(TAG, "- suggestedDisplayRatio: " + suggestedDisplayRatio); Slog.d(TAG, "- wallpaperWidthWithoutParallax: " + wallpaperWidthWithoutParallax); Slog.d(TAG, "- widthToRemove: " + widthToRemove); } if (rtl) { adjustedCrop.left += widthToRemove; } else { adjustedCrop.right -= widthToRemove; } if (DEBUG) { Slog.d(TAG, "- adjustedCrop: " + crop); } return adjustedCrop; } /** * This method computes a new crop based on the given crop in order to preserve the center point * of the given crop on the provided displaySize. This is only for the case where the device * displaySize has a smaller aspect ratio than the cropped image. * * NOTE: If the width to height ratio is less in the device display than cropped image * this means the aspect ratios are off and there will be distortions in the image * if the image is applied to the current display (i.e. the image will be skewed -> * pixels in the image will not align correctly with the same pixels in the image that are * above them) */ Rect sameCenter(Point displaySize, Point bitmapSize, Rect crop) { // in the case an image's crop is not set, we assume the image itself is cropped if (crop.isEmpty()) { crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y); } float screenRatio = (float) displaySize.x / displaySize.y; float cropRatio = (float) crop.width() / crop.height(); Rect adjustedCrop = new Rect(crop); if (screenRatio < cropRatio) { // the screen is more narrow than the image, and as such, the image will need to be // zoomed in till it fits in the vertical axis. Due to this, we need to manually adjust // the image's crop in order for it to fit into the screen without having the framework // do it (since the framework left aligns the image after zooming) // Calculate the height of the adjusted wallpaper crop so it respects the aspect ratio // of the device. To calculate the height, we will use the width of the current crop. // This is so we find the largest height possible which also respects the device aspect // ratio. int heightToAdd = (int) (0.5f + crop.width() / screenRatio - crop.height()); // Calculate how much extra image space available that can be used to adjust // the crop. If this amount is less than heightToAdd, from above, then that means we // can't use heightToAdd. Instead we will need to use the maximum possible height, which // is the height of the original bitmap. NOTE: the bitmap height may be different than // the crop. // since there is no guarantee to have height available on both sides // (e.g. the available height might be fully at the bottom), grab the minimum int availableHeight = 2 * Math.min(crop.top, bitmapSize.y - crop.bottom); int actualHeightToAdd = Math.min(heightToAdd, availableHeight); // half of the additional height is added to the top and bottom of the crop adjustedCrop.top -= actualHeightToAdd / 2 + actualHeightToAdd % 2; adjustedCrop.bottom += actualHeightToAdd / 2; // Calculate the width of the adjusted crop. Initially we used the fixed width of the // crop to calculate the heightToAdd, but since this height may be invalid (based on // the calculation above) we calculate the width again instead of using the fixed width, // using the adjustedCrop's updated height. int widthToRemove = (int) (0.5f + crop.width() - adjustedCrop.height() * screenRatio); // half of the additional width is subtracted from the left and right side of the crop int widthToRemoveLeft = widthToRemove / 2; int widthToRemoveRight = widthToRemove / 2 + widthToRemove % 2; adjustedCrop.left += widthToRemoveLeft; adjustedCrop.right -= widthToRemoveRight; if (DEBUG) { Slog.d(TAG, "cropRatio: " + cropRatio); Slog.d(TAG, "screenRatio: " + screenRatio); Slog.d(TAG, "heightToAdd: " + heightToAdd); Slog.d(TAG, "actualHeightToAdd: " + actualHeightToAdd); Slog.d(TAG, "availableHeight: " + availableHeight); Slog.d(TAG, "widthToRemove: " + widthToRemove); Slog.d(TAG, "adjustedCrop: " + adjustedCrop); } return adjustedCrop; } return adjustedCrop; } private boolean isTargetMoreNarrowThanSource(Point targetDisplaySize, Point srcDisplaySize) { float targetScreenRatio = (float) targetDisplaySize.x / targetDisplaySize.y; float srcScreenRatio = (float) srcDisplaySize.x / srcDisplaySize.y; return (targetScreenRatio < srcScreenRatio); } private void logRestoreErrorIfNoLiveComponent(int which, String error) { if (mSystemHasLiveComponent) { return; Loading @@ -644,6 +862,7 @@ public class WallpaperBackupAgent extends BackupAgent { mEventLogger.onLockImageWallpaperRestoreFailed(error); } } private Rect parseCropHint(File wallpaperInfo, String sectionTag) { Rect cropHint = new Rect(); try (FileInputStream stream = new FileInputStream(wallpaperInfo)) { Loading Loading @@ -907,22 +1126,6 @@ public class WallpaperBackupAgent extends BackupAgent { return internalDisplays; } /** * This method compares the source and target dimensions, and returns true if there is a * significant difference in area between them and the source dimensions are smaller than the * target dimensions. * * @param sourceDimensions is the dimensions of the source device * @param targetDimensions is the dimensions of the target device */ @VisibleForTesting boolean isSourceDeviceSignificantlySmallerThanTarget(Point sourceDimensions, Point targetDimensions) { int rawAreaDelta = (targetDimensions.x * targetDimensions.y) - (sourceDimensions.x * sourceDimensions.y); return rawAreaDelta > AREA_THRESHOLD; } @VisibleForTesting boolean isDeviceInRestore() { try { Loading
packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java +0 −21 Original line number Diff line number Diff line Loading @@ -59,7 +59,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.Point; import android.graphics.Rect; import android.os.FileUtils; import android.os.ParcelFileDescriptor; Loading Loading @@ -841,26 +840,6 @@ public class WallpaperBackupAgentTest { testParseCropHints(testMap); } @Test public void test_sourceDimensionsAreLargerThanTarget() { // source device is larger than target, expecting to get false Point sourceDimensions = new Point(2208, 1840); Point targetDimensions = new Point(1080, 2092); boolean isSourceSmaller = mWallpaperBackupAgent .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions); assertThat(isSourceSmaller).isEqualTo(false); } @Test public void test_sourceDimensionsMuchSmallerThanTarget() { // source device is smaller than target, expecting to get true Point sourceDimensions = new Point(1080, 2092); Point targetDimensions = new Point(2208, 1840); boolean isSourceSmaller = mWallpaperBackupAgent .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions); assertThat(isSourceSmaller).isEqualTo(true); } private void testParseCropHints(Map<Integer, Rect> testMap) throws Exception { assumeTrue(multiCrop()); mockRestoredStaticWallpaperFile(testMap); Loading