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