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