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