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