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