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