1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.wallpaper.module; 17 18 import android.annotation.SuppressLint; 19 import android.app.Activity; 20 import android.app.WallpaperColors; 21 import android.app.WallpaperManager; 22 import android.content.Context; 23 import android.content.res.Resources; 24 import android.graphics.Bitmap; 25 import android.graphics.Bitmap.CompressFormat; 26 import android.graphics.BitmapFactory; 27 import android.graphics.Point; 28 import android.graphics.PointF; 29 import android.graphics.Rect; 30 import android.graphics.drawable.BitmapDrawable; 31 import android.os.AsyncTask; 32 import android.os.ParcelFileDescriptor; 33 import android.text.TextUtils; 34 import android.util.Log; 35 import android.view.Display; 36 import android.view.WindowManager; 37 38 import androidx.annotation.Nullable; 39 40 import com.android.wallpaper.asset.Asset; 41 import com.android.wallpaper.asset.Asset.BitmapReceiver; 42 import com.android.wallpaper.asset.Asset.DimensionsReceiver; 43 import com.android.wallpaper.asset.BitmapUtils; 44 import com.android.wallpaper.asset.StreamableAsset; 45 import com.android.wallpaper.asset.StreamableAsset.StreamReceiver; 46 import com.android.wallpaper.compat.BuildCompat; 47 import com.android.wallpaper.compat.WallpaperManagerCompat; 48 import com.android.wallpaper.model.WallpaperInfo; 49 import com.android.wallpaper.module.BitmapCropper.Callback; 50 import com.android.wallpaper.util.BitmapTransformer; 51 import com.android.wallpaper.util.DisplayUtils; 52 import com.android.wallpaper.util.ScreenSizeCalculator; 53 import com.android.wallpaper.util.WallpaperCropUtils; 54 55 import java.io.ByteArrayInputStream; 56 import java.io.ByteArrayOutputStream; 57 import java.io.FileInputStream; 58 import java.io.IOException; 59 import java.io.InputStream; 60 import java.util.List; 61 62 /** 63 * Concrete implementation of WallpaperPersister which actually sets wallpapers to the system via 64 * the WallpaperManager. 65 */ 66 public class DefaultWallpaperPersister implements WallpaperPersister { 67 68 private static final int DEFAULT_COMPRESS_QUALITY = 100; 69 private static final String TAG = "WallpaperPersister"; 70 71 private final Context mAppContext; // The application's context. 72 // Context that accesses files in device protected storage 73 private final WallpaperManager mWallpaperManager; 74 private final WallpaperManagerCompat mWallpaperManagerCompat; 75 private final WallpaperPreferences mWallpaperPreferences; 76 private final WallpaperChangedNotifier mWallpaperChangedNotifier; 77 private final DisplayUtils mDisplayUtils; 78 79 private WallpaperInfo mWallpaperInfoInPreview; 80 81 @SuppressLint("ServiceCast") DefaultWallpaperPersister(Context context)82 public DefaultWallpaperPersister(Context context) { 83 mAppContext = context.getApplicationContext(); 84 // Retrieve WallpaperManager using Context#getSystemService instead of 85 // WallpaperManager#getInstance so it can be mocked out in test. 86 Injector injector = InjectorProvider.getInjector(); 87 mWallpaperManager = (WallpaperManager) context.getSystemService(Context.WALLPAPER_SERVICE); 88 mWallpaperManagerCompat = injector.getWallpaperManagerCompat(context); 89 mWallpaperPreferences = injector.getPreferences(context); 90 mWallpaperChangedNotifier = WallpaperChangedNotifier.getInstance(); 91 mDisplayUtils = injector.getDisplayUtils(context); 92 } 93 94 @Override setIndividualWallpaper(final WallpaperInfo wallpaper, Asset asset, @Nullable Rect cropRect, float scale, @Destination final int destination, final SetWallpaperCallback callback)95 public void setIndividualWallpaper(final WallpaperInfo wallpaper, Asset asset, 96 @Nullable Rect cropRect, float scale, @Destination final int destination, 97 final SetWallpaperCallback callback) { 98 // Set wallpaper without downscaling directly from an input stream if there's no crop rect 99 // specified by the caller and the asset is streamable. 100 if (cropRect == null && asset instanceof StreamableAsset) { 101 ((StreamableAsset) asset).fetchInputStream(new StreamReceiver() { 102 @Override 103 public void onInputStreamOpened(@Nullable InputStream inputStream) { 104 if (inputStream == null) { 105 callback.onError(null /* throwable */); 106 return; 107 } 108 setIndividualWallpaper(wallpaper, inputStream, destination, callback); 109 } 110 }); 111 return; 112 } 113 114 // If no crop rect is specified but the wallpaper asset is not streamable, then fall back to 115 // using the device's display size. 116 if (cropRect == null) { 117 Display display = ((WindowManager) mAppContext.getSystemService(Context.WINDOW_SERVICE)) 118 .getDefaultDisplay(); 119 Point screenSize = ScreenSizeCalculator.getInstance().getScreenSize(display); 120 asset.decodeBitmap(screenSize.x, screenSize.y, new BitmapReceiver() { 121 @Override 122 public void onBitmapDecoded(@Nullable Bitmap bitmap) { 123 if (bitmap == null) { 124 callback.onError(null /* throwable */); 125 return; 126 } 127 setIndividualWallpaper(wallpaper, bitmap, destination, callback); 128 } 129 }); 130 return; 131 } 132 133 BitmapCropper bitmapCropper = InjectorProvider.getInjector().getBitmapCropper(); 134 bitmapCropper.cropAndScaleBitmap(asset, scale, cropRect, false, new Callback() { 135 @Override 136 public void onBitmapCropped(Bitmap croppedBitmap) { 137 setIndividualWallpaper(wallpaper, croppedBitmap, destination, callback); 138 } 139 140 @Override 141 public void onError(@Nullable Throwable e) { 142 callback.onError(e); 143 } 144 }); 145 } 146 147 @Override setIndividualWallpaperWithPosition(Activity activity, WallpaperInfo wallpaper, @WallpaperPosition int wallpaperPosition, SetWallpaperCallback callback)148 public void setIndividualWallpaperWithPosition(Activity activity, WallpaperInfo wallpaper, 149 @WallpaperPosition int wallpaperPosition, SetWallpaperCallback callback) { 150 Display display = ((WindowManager) mAppContext.getSystemService(Context.WINDOW_SERVICE)) 151 .getDefaultDisplay(); 152 Point screenSize = ScreenSizeCalculator.getInstance().getScreenSize(display); 153 154 Asset asset = wallpaper.getAsset(activity); 155 asset.decodeRawDimensions(activity, new DimensionsReceiver() { 156 @Override 157 public void onDimensionsDecoded(@Nullable Point dimensions) { 158 if (dimensions == null) { 159 callback.onError(null); 160 return; 161 } 162 163 switch (wallpaperPosition) { 164 // Crop out screen-sized center portion of the source image if it's larger 165 // than the screen 166 // in both dimensions. Otherwise, decode the entire bitmap and fill the space 167 // around it to fill a new screen-sized bitmap with plain black pixels. 168 case WALLPAPER_POSITION_CENTER: 169 setIndividualWallpaperWithCenterPosition( 170 wallpaper, asset, dimensions, screenSize, callback); 171 break; 172 173 // Crop out a screen-size portion of the source image and set the bitmap region. 174 case WALLPAPER_POSITION_CENTER_CROP: 175 setIndividualWallpaperWithCenterCropPosition( 176 wallpaper, asset, dimensions, screenSize, callback); 177 break; 178 179 // Decode full bitmap sized for screen and stretch it to fill the screen 180 // dimensions. 181 case WALLPAPER_POSITION_STRETCH: 182 asset.decodeBitmap(screenSize.x, screenSize.y, new BitmapReceiver() { 183 @Override 184 public void onBitmapDecoded(@Nullable Bitmap bitmap) { 185 setIndividualWallpaperStretch(wallpaper, bitmap, 186 screenSize /* stretchSize */, 187 WallpaperPersister.DEST_BOTH, callback); 188 } 189 }); 190 break; 191 192 default: 193 Log.e(TAG, "Unsupported wallpaper position option specified: " 194 + wallpaperPosition); 195 callback.onError(null); 196 } 197 } 198 }); 199 } 200 201 /** 202 * Sets an individual wallpaper to both home + lock static wallpaper destinations with a center 203 * wallpaper position. 204 * 205 * @param wallpaper The wallpaper model object representing the wallpaper to be set. 206 * @param asset The wallpaper asset that should be used to set a wallpaper. 207 * @param dimensions Raw dimensions of the wallpaper asset. 208 * @param screenSize Dimensions of the device screen. 209 * @param callback Callback used to notify original caller of wallpaper set operation result. 210 */ setIndividualWallpaperWithCenterPosition(WallpaperInfo wallpaper, Asset asset, Point dimensions, Point screenSize, SetWallpaperCallback callback)211 private void setIndividualWallpaperWithCenterPosition(WallpaperInfo wallpaper, Asset asset, 212 Point dimensions, Point screenSize, SetWallpaperCallback callback) { 213 if (dimensions.x >= screenSize.x && dimensions.y >= screenSize.y) { 214 Rect cropRect = new Rect( 215 (dimensions.x - screenSize.x) / 2, 216 (dimensions.y - screenSize.y) / 2, 217 dimensions.x - ((dimensions.x - screenSize.x) / 2), 218 dimensions.y - ((dimensions.y - screenSize.y) / 2)); 219 asset.decodeBitmapRegion(cropRect, screenSize.x, screenSize.y, false, 220 bitmap -> setIndividualWallpaper(wallpaper, bitmap, 221 WallpaperPersister.DEST_BOTH, callback)); 222 } else { 223 // Decode the full bitmap and pass with the screen size as a fill rect. 224 asset.decodeBitmap(dimensions.x, dimensions.y, new BitmapReceiver() { 225 @Override 226 public void onBitmapDecoded(@Nullable Bitmap bitmap) { 227 if (bitmap == null) { 228 callback.onError(null); 229 return; 230 } 231 232 setIndividualWallpaperFill(wallpaper, bitmap, screenSize /* fillSize */, 233 WallpaperPersister.DEST_BOTH, callback); 234 } 235 }); 236 } 237 } 238 239 /** 240 * Sets an individual wallpaper to both home + lock static wallpaper destinations with a center 241 * cropped wallpaper position. 242 * 243 * @param wallpaper The wallpaper model object representing the wallpaper to be set. 244 * @param asset The wallpaper asset that should be used to set a wallpaper. 245 * @param dimensions Raw dimensions of the wallpaper asset. 246 * @param screenSize Dimensions of the device screen. 247 * @param callback Callback used to notify original caller of wallpaper set operation result. 248 */ setIndividualWallpaperWithCenterCropPosition(WallpaperInfo wallpaper, Asset asset, Point dimensions, Point screenSize, SetWallpaperCallback callback)249 private void setIndividualWallpaperWithCenterCropPosition(WallpaperInfo wallpaper, Asset asset, 250 Point dimensions, Point screenSize, SetWallpaperCallback callback) { 251 float scale = Math.max((float) screenSize.x / dimensions.x, 252 (float) screenSize.y / dimensions.y); 253 254 int scaledImageWidth = (int) (dimensions.x * scale); 255 int scaledImageHeight = (int) (dimensions.y * scale); 256 257 // Crop rect is in post-scale units. 258 Rect cropRect = new Rect( 259 (scaledImageWidth - screenSize.x) / 2, 260 (scaledImageHeight - screenSize.y) / 2, 261 scaledImageWidth - ((scaledImageWidth - screenSize.x) / 2), 262 scaledImageHeight - (((scaledImageHeight - screenSize.y) / 2))); 263 264 setIndividualWallpaper( 265 wallpaper, asset, cropRect, scale, WallpaperPersister.DEST_BOTH, callback); 266 } 267 268 /** 269 * Sets a static individual wallpaper to the system via the WallpaperManager. 270 * 271 * @param wallpaper Wallpaper model object. 272 * @param croppedBitmap Bitmap representing the individual wallpaper image. 273 * @param destination The destination - where to set the wallpaper to. 274 * @param callback Called once the wallpaper was set or if an error occurred. 275 */ setIndividualWallpaper(WallpaperInfo wallpaper, Bitmap croppedBitmap, @Destination int destination, SetWallpaperCallback callback)276 private void setIndividualWallpaper(WallpaperInfo wallpaper, Bitmap croppedBitmap, 277 @Destination int destination, SetWallpaperCallback callback) { 278 SetWallpaperTask setWallpaperTask = 279 new SetWallpaperTask(wallpaper, croppedBitmap, destination, callback); 280 setWallpaperTask.execute(); 281 } 282 283 /** 284 * Sets a static individual wallpaper to the system via the WallpaperManager with a fill option. 285 * 286 * @param wallpaper Wallpaper model object. 287 * @param croppedBitmap Bitmap representing the individual wallpaper image. 288 * @param fillSize Specifies the final bitmap size that should be set to WallpaperManager. 289 * This final bitmap will show the visible area of the provided bitmap 290 * after applying a mask with black background the source bitmap and 291 * centering. There may be black borders around the original bitmap if 292 * it's smaller than the fillSize in one or both dimensions. 293 * @param destination The destination - where to set the wallpaper to. 294 * @param callback Called once the wallpaper was set or if an error occurred. 295 */ setIndividualWallpaperFill(WallpaperInfo wallpaper, Bitmap croppedBitmap, Point fillSize, @Destination int destination, SetWallpaperCallback callback)296 private void setIndividualWallpaperFill(WallpaperInfo wallpaper, Bitmap croppedBitmap, 297 Point fillSize, @Destination int destination, SetWallpaperCallback callback) { 298 SetWallpaperTask setWallpaperTask = 299 new SetWallpaperTask(wallpaper, croppedBitmap, destination, callback); 300 setWallpaperTask.setFillSize(fillSize); 301 setWallpaperTask.execute(); 302 } 303 304 /** 305 * Sets a static individual wallpaper to the system via the WallpaperManager with a stretch 306 * option. 307 * 308 * @param wallpaper Wallpaper model object. 309 * @param croppedBitmap Bitmap representing the individual wallpaper image. 310 * @param stretchSize Specifies the final size to which the bitmap should be stretched 311 * prior 312 * to being set to the device. 313 * @param destination The destination - where to set the wallpaper to. 314 * @param callback Called once the wallpaper was set or if an error occurred. 315 */ setIndividualWallpaperStretch(WallpaperInfo wallpaper, Bitmap croppedBitmap, Point stretchSize, @Destination int destination, SetWallpaperCallback callback)316 private void setIndividualWallpaperStretch(WallpaperInfo wallpaper, Bitmap croppedBitmap, 317 Point stretchSize, @Destination int destination, SetWallpaperCallback callback) { 318 SetWallpaperTask setWallpaperTask = 319 new SetWallpaperTask(wallpaper, croppedBitmap, destination, callback); 320 setWallpaperTask.setStretchSize(stretchSize); 321 setWallpaperTask.execute(); 322 } 323 324 /** 325 * Sets a static individual wallpaper stream to the system via the WallpaperManager. 326 * 327 * @param wallpaper Wallpaper model object. 328 * @param inputStream JPEG or PNG stream of wallpaper image's bytes. 329 * @param destination The destination - where to set the wallpaper to. 330 * @param callback Called once the wallpaper was set or if an error occurred. 331 */ setIndividualWallpaper(WallpaperInfo wallpaper, InputStream inputStream, @Destination int destination, SetWallpaperCallback callback)332 private void setIndividualWallpaper(WallpaperInfo wallpaper, InputStream inputStream, 333 @Destination int destination, SetWallpaperCallback callback) { 334 SetWallpaperTask setWallpaperTask = 335 new SetWallpaperTask(wallpaper, inputStream, destination, callback); 336 setWallpaperTask.execute(); 337 } 338 339 @Override setWallpaperInRotation(Bitmap wallpaperBitmap, List<String> attributions, int actionLabelRes, int actionIconRes, String actionUrl, String collectionId)340 public boolean setWallpaperInRotation(Bitmap wallpaperBitmap, List<String> attributions, 341 int actionLabelRes, int actionIconRes, String actionUrl, String collectionId) { 342 343 return setWallpaperInRotationStatic(wallpaperBitmap, attributions, actionUrl, 344 actionLabelRes, actionIconRes, collectionId); 345 } 346 347 @Override setWallpaperBitmapInNextRotation(Bitmap wallpaperBitmap, List<String> attributions, String actionUrl, String collectionId)348 public int setWallpaperBitmapInNextRotation(Bitmap wallpaperBitmap, List<String> attributions, 349 String actionUrl, String collectionId) { 350 return cropAndSetWallpaperBitmapInRotationStatic(wallpaperBitmap, 351 attributions, actionUrl, collectionId); 352 } 353 354 @Override finalizeWallpaperForNextRotation(List<String> attributions, String actionUrl, int actionLabelRes, int actionIconRes, String collectionId, int wallpaperId)355 public boolean finalizeWallpaperForNextRotation(List<String> attributions, String actionUrl, 356 int actionLabelRes, int actionIconRes, String collectionId, int wallpaperId) { 357 return saveStaticWallpaperMetadata(attributions, actionUrl, actionLabelRes, 358 actionIconRes, collectionId, wallpaperId); 359 } 360 361 /** 362 * Sets wallpaper image and attributions when a static wallpaper is responsible for presenting 363 * the 364 * current "daily wallpaper". 365 */ setWallpaperInRotationStatic(Bitmap wallpaperBitmap, List<String> attributions, String actionUrl, int actionLabelRes, int actionIconRes, String collectionId)366 private boolean setWallpaperInRotationStatic(Bitmap wallpaperBitmap, List<String> attributions, 367 String actionUrl, int actionLabelRes, int actionIconRes, String collectionId) { 368 final int wallpaperId = cropAndSetWallpaperBitmapInRotationStatic(wallpaperBitmap, 369 attributions, actionUrl, collectionId); 370 371 if (wallpaperId == 0) { 372 return false; 373 } 374 375 return saveStaticWallpaperMetadata(attributions, actionUrl, actionLabelRes, 376 actionIconRes, collectionId, wallpaperId); 377 } 378 379 @Override saveStaticWallpaperMetadata(List<String> attributions, String actionUrl, int actionLabelRes, int actionIconRes, String collectionId, int wallpaperId)380 public boolean saveStaticWallpaperMetadata(List<String> attributions, 381 String actionUrl, 382 int actionLabelRes, 383 int actionIconRes, 384 String collectionId, 385 int wallpaperId) { 386 mWallpaperPreferences.clearHomeWallpaperMetadata(); 387 388 boolean isLockWallpaperSet = isSeparateLockScreenWallpaperSet(); 389 390 // Persist wallpaper IDs if the rotating wallpaper component 391 mWallpaperPreferences.setHomeWallpaperManagerId(wallpaperId); 392 393 // Only copy over wallpaper ID to lock wallpaper if no explicit lock wallpaper is set 394 // (so metadata isn't lost if a user explicitly sets a home-only wallpaper). 395 if (!isLockWallpaperSet) { 396 mWallpaperPreferences.setLockWallpaperId(wallpaperId); 397 } 398 399 400 mWallpaperPreferences.setHomeWallpaperAttributions(attributions); 401 mWallpaperPreferences.setHomeWallpaperActionUrl(actionUrl); 402 mWallpaperPreferences.setHomeWallpaperActionLabelRes(actionLabelRes); 403 mWallpaperPreferences.setHomeWallpaperActionIconRes(actionIconRes); 404 // Only set base image URL for static Backdrop images, not for rotation. 405 mWallpaperPreferences.setHomeWallpaperBaseImageUrl(null); 406 mWallpaperPreferences.setHomeWallpaperCollectionId(collectionId); 407 408 // Set metadata to lock screen also when the rotating wallpaper so if user sets a home 409 // screen-only wallpaper later, these attributions will still be available. 410 if (!isLockWallpaperSet) { 411 mWallpaperPreferences.setLockWallpaperAttributions(attributions); 412 mWallpaperPreferences.setLockWallpaperActionUrl(actionUrl); 413 mWallpaperPreferences.setLockWallpaperActionLabelRes(actionLabelRes); 414 mWallpaperPreferences.setLockWallpaperActionIconRes(actionIconRes); 415 mWallpaperPreferences.setLockWallpaperCollectionId(collectionId); 416 } 417 418 return true; 419 } 420 421 /** 422 * Sets a wallpaper in rotation as a static wallpaper to the {@link WallpaperManager} with the 423 * option allowBackup=false to save user data. 424 * 425 * @return wallpaper ID for the wallpaper bitmap. 426 */ cropAndSetWallpaperBitmapInRotationStatic(Bitmap wallpaperBitmap, List<String> attributions, String actionUrl, String collectionId)427 private int cropAndSetWallpaperBitmapInRotationStatic(Bitmap wallpaperBitmap, 428 List<String> attributions, String actionUrl, String collectionId) { 429 // Calculate crop and scale of the wallpaper to match the default one used in preview 430 Point wallpaperSize = new Point(wallpaperBitmap.getWidth(), wallpaperBitmap.getHeight()); 431 Resources resources = mAppContext.getResources(); 432 Display croppingDisplay = mDisplayUtils.getWallpaperDisplay(); 433 Point defaultCropSurfaceSize = WallpaperCropUtils.getDefaultCropSurfaceSize( 434 resources, croppingDisplay); 435 Point screenSize = ScreenSizeCalculator.getInstance().getScreenSize(croppingDisplay); 436 437 // Determine minimum zoom to fit maximum visible area of wallpaper on crop surface. 438 float minWallpaperZoom = 439 WallpaperCropUtils.calculateMinZoom(wallpaperSize, screenSize); 440 441 PointF centerPosition = WallpaperCropUtils.calculateDefaultCenter(mAppContext, 442 wallpaperSize, WallpaperCropUtils.calculateVisibleRect(wallpaperSize, screenSize)); 443 444 Point scaledCenter = new Point((int) (minWallpaperZoom * centerPosition.x), 445 (int) (minWallpaperZoom * centerPosition.y)); 446 447 int offsetX = Math.max(0, -(screenSize.x / 2 - scaledCenter.x)); 448 int offsetY = Math.max(0, -(screenSize.y / 2 - scaledCenter.y)); 449 450 Rect cropRect = WallpaperCropUtils.calculateCropRect(mAppContext, minWallpaperZoom, 451 wallpaperSize, defaultCropSurfaceSize, screenSize, offsetX, offsetY); 452 453 Rect scaledCropRect = new Rect( 454 (int) Math.floor((float) cropRect.left / minWallpaperZoom), 455 (int) Math.floor((float) cropRect.top / minWallpaperZoom), 456 (int) Math.floor((float) cropRect.right / minWallpaperZoom), 457 (int) Math.floor((float) cropRect.bottom / minWallpaperZoom)); 458 459 // Scale and crop the bitmap 460 wallpaperBitmap = Bitmap.createBitmap(wallpaperBitmap, 461 scaledCropRect.left, 462 scaledCropRect.top, 463 scaledCropRect.width(), 464 scaledCropRect.height()); 465 int whichWallpaper = getDefaultWhichWallpaper(); 466 467 int wallpaperId = setBitmapToWallpaperManagerCompat(wallpaperBitmap, 468 /* allowBackup */ false, whichWallpaper); 469 if (wallpaperId > 0) { 470 mWallpaperPreferences.storeLatestHomeWallpaper(String.valueOf(wallpaperId), 471 attributions, actionUrl, collectionId, wallpaperBitmap, 472 WallpaperColors.fromBitmap(wallpaperBitmap)); 473 } 474 return wallpaperId; 475 } 476 477 /* 478 * Note: this method will return use home-only (FLAG_SYSTEM) instead of both home and lock 479 * if there's a distinct lock-only static wallpaper set so we don't override the lock wallpaper. 480 */ 481 @Override getDefaultWhichWallpaper()482 public int getDefaultWhichWallpaper() { 483 return isSeparateLockScreenWallpaperSet() 484 ? WallpaperManagerCompat.FLAG_SYSTEM 485 : WallpaperManagerCompat.FLAG_SYSTEM | WallpaperManagerCompat.FLAG_LOCK; 486 } 487 488 @Override setBitmapToWallpaperManagerCompat(Bitmap wallpaperBitmap, boolean allowBackup, int whichWallpaper)489 public int setBitmapToWallpaperManagerCompat(Bitmap wallpaperBitmap, boolean allowBackup, 490 int whichWallpaper) { 491 ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(); 492 if (wallpaperBitmap.compress(CompressFormat.PNG, DEFAULT_COMPRESS_QUALITY, tmpOut)) { 493 try { 494 byte[] outByteArray = tmpOut.toByteArray(); 495 return mWallpaperManagerCompat.setStream( 496 new ByteArrayInputStream(outByteArray), 497 null /* visibleCropHint */, 498 allowBackup, 499 whichWallpaper); 500 } catch (IOException e) { 501 Log.e(TAG, "unable to write stream to wallpaper manager"); 502 return 0; 503 } 504 } else { 505 Log.e(TAG, "unable to compress wallpaper"); 506 try { 507 return mWallpaperManagerCompat.setBitmap( 508 wallpaperBitmap, 509 null /* visibleCropHint */, 510 allowBackup, 511 whichWallpaper); 512 } catch (IOException e) { 513 Log.e(TAG, "unable to set wallpaper"); 514 return 0; 515 } 516 } 517 } 518 setStreamToWallpaperManagerCompat(InputStream inputStream, boolean allowBackup, int whichWallpaper)519 private int setStreamToWallpaperManagerCompat(InputStream inputStream, boolean allowBackup, 520 int whichWallpaper) { 521 try { 522 return mWallpaperManagerCompat.setStream(inputStream, null, allowBackup, 523 whichWallpaper); 524 } catch (IOException e) { 525 return 0; 526 } 527 } 528 529 @Override setWallpaperInfoInPreview(WallpaperInfo wallpaper)530 public void setWallpaperInfoInPreview(WallpaperInfo wallpaper) { 531 mWallpaperInfoInPreview = wallpaper; 532 } 533 534 @Override onLiveWallpaperSet()535 public void onLiveWallpaperSet() { 536 android.app.WallpaperInfo currentWallpaperComponent = mWallpaperManager.getWallpaperInfo(); 537 android.app.WallpaperInfo previewedWallpaperComponent = mWallpaperInfoInPreview != null 538 ? mWallpaperInfoInPreview.getWallpaperComponent() : null; 539 540 // If there is no live wallpaper set on the WallpaperManager or it doesn't match the 541 // WallpaperInfo which was last previewed, then do nothing and nullify last previewed 542 // wallpaper. 543 if (currentWallpaperComponent == null || previewedWallpaperComponent == null 544 || !currentWallpaperComponent.getPackageName() 545 .equals(previewedWallpaperComponent.getPackageName())) { 546 mWallpaperInfoInPreview = null; 547 return; 548 } 549 550 setLiveWallpaperMetadata(); 551 } 552 553 /** 554 * Returns whether a separate lock-screen (static) wallpaper is set to the WallpaperManager. 555 */ isSeparateLockScreenWallpaperSet()556 private boolean isSeparateLockScreenWallpaperSet() { 557 ParcelFileDescriptor lockWallpaperFile = 558 mWallpaperManagerCompat.getWallpaperFile(WallpaperManagerCompat.FLAG_LOCK); 559 560 boolean isLockWallpaperSet = false; 561 562 if (lockWallpaperFile != null) { 563 isLockWallpaperSet = true; 564 565 try { 566 lockWallpaperFile.close(); 567 } catch (IOException e) { 568 Log.e(TAG, "Unable to close PFD for lock wallpaper", e); 569 } 570 } 571 572 return isLockWallpaperSet; 573 } 574 575 /** 576 * Sets the live wallpaper's metadata on SharedPreferences. 577 */ setLiveWallpaperMetadata()578 private void setLiveWallpaperMetadata() { 579 android.app.WallpaperInfo previewedWallpaperComponent = 580 mWallpaperInfoInPreview.getWallpaperComponent(); 581 582 mWallpaperPreferences.clearHomeWallpaperMetadata(); 583 // NOTE: We explicitly do not also clear the lock wallpaper metadata. Since the user may 584 // have set the live wallpaper on the home screen only, we leave the lock wallpaper metadata 585 // intact. If the user has set the live wallpaper for both home and lock screens, then the 586 // WallpaperRefresher will pick up on that and update the preferences later. 587 mWallpaperPreferences 588 .setHomeWallpaperAttributions(mWallpaperInfoInPreview.getAttributions(mAppContext)); 589 mWallpaperPreferences.setHomeWallpaperPackageName( 590 previewedWallpaperComponent.getPackageName()); 591 mWallpaperPreferences.setHomeWallpaperServiceName( 592 previewedWallpaperComponent.getServiceName()); 593 mWallpaperPreferences.setHomeWallpaperCollectionId( 594 mWallpaperInfoInPreview.getCollectionId(mAppContext)); 595 mWallpaperPreferences.setWallpaperPresentationMode( 596 WallpaperPreferences.PRESENTATION_MODE_STATIC); 597 mWallpaperPreferences.clearDailyRotations(); 598 } 599 600 private class SetWallpaperTask extends AsyncTask<Void, Void, Boolean> { 601 602 private final WallpaperInfo mWallpaper; 603 @Destination 604 private final int mDestination; 605 private final WallpaperPersister.SetWallpaperCallback mCallback; 606 607 private Bitmap mBitmap; 608 private InputStream mInputStream; 609 610 /** 611 * Optional parameters for applying a post-decoding fill or stretch transformation. 612 */ 613 @Nullable 614 private Point mFillSize; 615 @Nullable 616 private Point mStretchSize; 617 SetWallpaperTask(WallpaperInfo wallpaper, Bitmap bitmap, @Destination int destination, WallpaperPersister.SetWallpaperCallback callback)618 SetWallpaperTask(WallpaperInfo wallpaper, Bitmap bitmap, @Destination int destination, 619 WallpaperPersister.SetWallpaperCallback callback) { 620 super(); 621 mWallpaper = wallpaper; 622 mBitmap = bitmap; 623 mDestination = destination; 624 mCallback = callback; 625 } 626 627 /** 628 * Constructor for SetWallpaperTask which takes an InputStream instead of a bitmap. The task 629 * will close the InputStream once it is done with it. 630 */ SetWallpaperTask(WallpaperInfo wallpaper, InputStream stream, @Destination int destination, WallpaperPersister.SetWallpaperCallback callback)631 SetWallpaperTask(WallpaperInfo wallpaper, InputStream stream, 632 @Destination int destination, WallpaperPersister.SetWallpaperCallback callback) { 633 mWallpaper = wallpaper; 634 mInputStream = stream; 635 mDestination = destination; 636 mCallback = callback; 637 } 638 setFillSize(Point fillSize)639 void setFillSize(Point fillSize) { 640 if (mStretchSize != null) { 641 throw new IllegalArgumentException( 642 "Can't pass a fill size option if a stretch size is " 643 + "already set."); 644 } 645 mFillSize = fillSize; 646 } 647 setStretchSize(Point stretchSize)648 void setStretchSize(Point stretchSize) { 649 if (mFillSize != null) { 650 throw new IllegalArgumentException( 651 "Can't pass a stretch size option if a fill size is " 652 + "already set."); 653 } 654 mStretchSize = stretchSize; 655 } 656 657 @Override doInBackground(Void... unused)658 protected Boolean doInBackground(Void... unused) { 659 int whichWallpaper; 660 if (mDestination == DEST_HOME_SCREEN) { 661 whichWallpaper = WallpaperManagerCompat.FLAG_SYSTEM; 662 } else if (mDestination == DEST_LOCK_SCREEN) { 663 whichWallpaper = WallpaperManagerCompat.FLAG_LOCK; 664 } else { // DEST_BOTH 665 whichWallpaper = WallpaperManagerCompat.FLAG_SYSTEM 666 | WallpaperManagerCompat.FLAG_LOCK; 667 } 668 669 670 boolean wasLockWallpaperSet = 671 InjectorProvider.getInjector().getWallpaperStatusChecker().isLockWallpaperSet( 672 mAppContext); 673 674 boolean allowBackup = mWallpaper.getBackupPermission() == WallpaperInfo.BACKUP_ALLOWED; 675 final int wallpaperId; 676 if (mBitmap != null) { 677 // Apply fill or stretch transformations on mBitmap if necessary. 678 if (mFillSize != null) { 679 mBitmap = BitmapTransformer.applyFillTransformation(mBitmap, mFillSize); 680 } 681 if (mStretchSize != null) { 682 mBitmap = Bitmap.createScaledBitmap(mBitmap, mStretchSize.x, mStretchSize.y, 683 true); 684 } 685 686 wallpaperId = setBitmapToWallpaperManagerCompat(mBitmap, allowBackup, 687 whichWallpaper); 688 } else if (mInputStream != null) { 689 wallpaperId = setStreamToWallpaperManagerCompat(mInputStream, allowBackup, 690 whichWallpaper); 691 } else { 692 Log.e(TAG, 693 "Both the wallpaper bitmap and input stream are null so we're unable to " 694 + "set any " 695 + "kind of wallpaper here."); 696 wallpaperId = 0; 697 } 698 699 if (wallpaperId > 0) { 700 if (mDestination == DEST_HOME_SCREEN 701 && mWallpaperPreferences.getWallpaperPresentationMode() 702 == WallpaperPreferences.PRESENTATION_MODE_ROTATING 703 && !wasLockWallpaperSet 704 && BuildCompat.isAtLeastN()) { 705 copyRotatingWallpaperToLock(); 706 } 707 setImageWallpaperMetadata(mDestination, wallpaperId); 708 return true; 709 } else { 710 return false; 711 } 712 } 713 714 @Override onPostExecute(Boolean isSuccess)715 protected void onPostExecute(Boolean isSuccess) { 716 if (mInputStream != null) { 717 try { 718 mInputStream.close(); 719 } catch (IOException e) { 720 Log.e(TAG, "Failed to close input stream " + e); 721 mCallback.onError(e /* throwable */); 722 return; 723 } 724 } 725 726 if (isSuccess) { 727 mCallback.onSuccess(mWallpaper); 728 mWallpaperChangedNotifier.notifyWallpaperChanged(); 729 } else { 730 mCallback.onError(null /* throwable */); 731 } 732 } 733 734 /** 735 * Copies home wallpaper metadata to lock, and if rotation was enabled with a live wallpaper 736 * previously, then copies over the rotating wallpaper image to the WallpaperManager also. 737 * <p> 738 * Used to accommodate the case where a user had gone from a home+lock daily rotation to 739 * selecting a static wallpaper on home-only. The image and metadata that was previously 740 * rotating is now copied to the lock screen. 741 */ copyRotatingWallpaperToLock()742 private void copyRotatingWallpaperToLock() { 743 744 mWallpaperPreferences.setLockWallpaperAttributions( 745 mWallpaperPreferences.getHomeWallpaperAttributions()); 746 mWallpaperPreferences.setLockWallpaperActionUrl( 747 mWallpaperPreferences.getHomeWallpaperActionUrl()); 748 mWallpaperPreferences.setLockWallpaperActionLabelRes( 749 mWallpaperPreferences.getHomeWallpaperActionLabelRes()); 750 mWallpaperPreferences.setLockWallpaperActionIconRes( 751 mWallpaperPreferences.getHomeWallpaperActionIconRes()); 752 mWallpaperPreferences.setLockWallpaperCollectionId( 753 mWallpaperPreferences.getHomeWallpaperCollectionId()); 754 755 // Set the lock wallpaper ID to what Android set it to, following its having 756 // copied the system wallpaper over to the lock screen when we changed from 757 // "both" to distinct system and lock screen wallpapers. 758 mWallpaperPreferences.setLockWallpaperId( 759 mWallpaperManagerCompat.getWallpaperId(WallpaperManagerCompat.FLAG_LOCK)); 760 761 } 762 763 /** 764 * Sets the image wallpaper's metadata on SharedPreferences. This method is called after the 765 * set wallpaper operation is successful. 766 * 767 * @param destination Which destination of wallpaper the metadata corresponds to (home 768 * screen, lock screen, or both). 769 * @param wallpaperId The ID of the static wallpaper returned by WallpaperManager, which 770 * on N and later versions of Android uniquely identifies a wallpaper 771 * image. 772 */ setImageWallpaperMetadata(@estination int destination, int wallpaperId)773 private void setImageWallpaperMetadata(@Destination int destination, int wallpaperId) { 774 if (destination == DEST_HOME_SCREEN || destination == DEST_BOTH) { 775 mWallpaperPreferences.clearHomeWallpaperMetadata(); 776 setImageWallpaperHomeMetadata(wallpaperId); 777 778 // Reset presentation mode to STATIC if an individual wallpaper is set to the 779 // home screen 780 // because rotation always affects at least the home screen. 781 mWallpaperPreferences.setWallpaperPresentationMode( 782 WallpaperPreferences.PRESENTATION_MODE_STATIC); 783 } 784 785 if (destination == DEST_LOCK_SCREEN || destination == DEST_BOTH) { 786 mWallpaperPreferences.clearLockWallpaperMetadata(); 787 setImageWallpaperLockMetadata(wallpaperId); 788 } 789 790 mWallpaperPreferences.clearDailyRotations(); 791 } 792 setImageWallpaperHomeMetadata(int homeWallpaperId)793 private void setImageWallpaperHomeMetadata(int homeWallpaperId) { 794 if (BuildCompat.isAtLeastN()) { 795 mWallpaperPreferences.setHomeWallpaperManagerId(homeWallpaperId); 796 } 797 798 // Compute bitmap hash code after setting the wallpaper because JPEG compression has 799 // likely changed many pixels' color values. Forget the previously loaded wallpaper 800 // bitmap so that WallpaperManager doesn't return the old wallpaper drawable. Do this 801 // on N+ devices in addition to saving the wallpaper ID for the purpose of backup & 802 // restore. 803 mWallpaperManager.forgetLoadedWallpaper(); 804 mBitmap = ((BitmapDrawable) mWallpaperManagerCompat.getDrawable()).getBitmap(); 805 long bitmapHash = BitmapUtils.generateHashCode(mBitmap); 806 WallpaperColors colors = WallpaperColors.fromBitmap(mBitmap); 807 808 mWallpaperPreferences.setHomeWallpaperHashCode(bitmapHash); 809 810 mWallpaperPreferences.setHomeWallpaperAttributions( 811 mWallpaper.getAttributions(mAppContext)); 812 mWallpaperPreferences.setHomeWallpaperBaseImageUrl(mWallpaper.getBaseImageUrl()); 813 mWallpaperPreferences.setHomeWallpaperActionUrl(mWallpaper.getActionUrl(mAppContext)); 814 mWallpaperPreferences.setHomeWallpaperActionLabelRes( 815 mWallpaper.getActionLabelRes(mAppContext)); 816 mWallpaperPreferences.setHomeWallpaperActionIconRes( 817 mWallpaper.getActionIconRes(mAppContext)); 818 mWallpaperPreferences.setHomeWallpaperCollectionId( 819 mWallpaper.getCollectionId(mAppContext)); 820 mWallpaperPreferences.setHomeWallpaperRemoteId(mWallpaper.getWallpaperId()); 821 mWallpaperPreferences.storeLatestHomeWallpaper( 822 TextUtils.isEmpty(mWallpaper.getWallpaperId()) ? String.valueOf(bitmapHash) 823 : mWallpaper.getWallpaperId(), 824 mWallpaper, mBitmap, colors); 825 } 826 setImageWallpaperLockMetadata(int lockWallpaperId)827 private void setImageWallpaperLockMetadata(int lockWallpaperId) { 828 mWallpaperPreferences.setLockWallpaperId(lockWallpaperId); 829 mWallpaperPreferences.setLockWallpaperAttributions( 830 mWallpaper.getAttributions(mAppContext)); 831 mWallpaperPreferences.setLockWallpaperActionUrl(mWallpaper.getActionUrl(mAppContext)); 832 mWallpaperPreferences.setLockWallpaperActionLabelRes( 833 mWallpaper.getActionLabelRes(mAppContext)); 834 mWallpaperPreferences.setLockWallpaperActionIconRes( 835 mWallpaper.getActionIconRes(mAppContext)); 836 mWallpaperPreferences.setLockWallpaperCollectionId( 837 mWallpaper.getCollectionId(mAppContext)); 838 mWallpaperPreferences.setLockWallpaperRemoteId(mWallpaper.getWallpaperId()); 839 840 // Save the lock wallpaper image's hash code as well for the sake of backup & restore 841 // because WallpaperManager-generated IDs are specific to a physical device and 842 // cannot be used to identify a wallpaper image on another device after restore is 843 // complete. 844 saveLockWallpaperHashCode(); 845 } 846 saveLockWallpaperHashCode()847 private void saveLockWallpaperHashCode() { 848 Bitmap lockBitmap = null; 849 850 ParcelFileDescriptor parcelFd = mWallpaperManagerCompat.getWallpaperFile( 851 WallpaperManagerCompat.FLAG_LOCK); 852 853 if (parcelFd == null) { 854 return; 855 } 856 857 InputStream fileStream = null; 858 try { 859 fileStream = new FileInputStream(parcelFd.getFileDescriptor()); 860 lockBitmap = BitmapFactory.decodeStream(fileStream); 861 parcelFd.close(); 862 } catch (IOException e) { 863 Log.e(TAG, "IO exception when closing the file descriptor."); 864 } finally { 865 if (fileStream != null) { 866 try { 867 fileStream.close(); 868 } catch (IOException e) { 869 Log.e(TAG, 870 "IO exception when closing the input stream for the lock screen " 871 + "WP."); 872 } 873 } 874 } 875 876 if (lockBitmap != null) { 877 long bitmapHash = BitmapUtils.generateHashCode(lockBitmap); 878 mWallpaperPreferences.setLockWallpaperHashCode(bitmapHash); 879 } 880 } 881 } 882 } 883