blob: 60527d020052a1e6398d10a1410e031835fc4fbe [file] [log] [blame]
Jon Miranda16ea1b12017-12-12 14:52:48 -08001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.wallpaper.module;
17
18import android.annotation.SuppressLint;
19import android.app.Activity;
20import android.app.WallpaperManager;
21import android.content.Context;
Jon Miranda16ea1b12017-12-12 14:52:48 -080022import android.graphics.Bitmap;
23import android.graphics.Bitmap.CompressFormat;
24import android.graphics.BitmapFactory;
25import android.graphics.Point;
26import android.graphics.Rect;
27import android.graphics.drawable.BitmapDrawable;
28import android.os.AsyncTask;
29import android.os.ParcelFileDescriptor;
Jon Miranda16ea1b12017-12-12 14:52:48 -080030import android.util.Log;
31import android.view.Display;
32import android.view.WindowManager;
33
Samuel Fufa03bc6ac2019-09-19 12:01:50 -070034import androidx.annotation.Nullable;
35
Jon Miranda16ea1b12017-12-12 14:52:48 -080036import com.android.wallpaper.asset.Asset;
37import com.android.wallpaper.asset.Asset.BitmapReceiver;
38import com.android.wallpaper.asset.Asset.DimensionsReceiver;
39import com.android.wallpaper.asset.BitmapUtils;
40import com.android.wallpaper.asset.StreamableAsset;
41import com.android.wallpaper.asset.StreamableAsset.StreamReceiver;
42import com.android.wallpaper.compat.BuildCompat;
43import com.android.wallpaper.compat.WallpaperManagerCompat;
44import com.android.wallpaper.model.WallpaperInfo;
45import com.android.wallpaper.module.BitmapCropper.Callback;
Jon Miranda16ea1b12017-12-12 14:52:48 -080046import com.android.wallpaper.util.BitmapTransformer;
Jon Miranda16ea1b12017-12-12 14:52:48 -080047import com.android.wallpaper.util.ScreenSizeCalculator;
48
49import java.io.ByteArrayInputStream;
50import java.io.ByteArrayOutputStream;
Jon Miranda16ea1b12017-12-12 14:52:48 -080051import java.io.FileInputStream;
Jon Miranda16ea1b12017-12-12 14:52:48 -080052import java.io.IOException;
53import java.io.InputStream;
54import java.util.List;
55
56/**
57 * Concrete implementation of WallpaperPersister which actually sets wallpapers to the system via
58 * the WallpaperManager.
59 */
60public 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.
Santiago Etchebehere5a7f1dd2018-04-03 15:01:29 -070066 // Context that accesses files in device protected storage
Jon Miranda16ea1b12017-12-12 14:52:48 -080067 private final WallpaperManager mWallpaperManager;
68 private final WallpaperManagerCompat mWallpaperManagerCompat;
69 private final WallpaperPreferences mWallpaperPreferences;
Jon Miranda16ea1b12017-12-12 14:52:48 -080070 private final WallpaperChangedNotifier mWallpaperChangedNotifier;
71
72 private WallpaperInfo mWallpaperInfoInPreview;
73
74 @SuppressLint("ServiceCast")
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);
Jon Miranda16ea1b12017-12-12 14:52:48 -080083 mWallpaperChangedNotifier = WallpaperChangedNotifier.getInstance();
84 }
85
86 @Override
87 public void setIndividualWallpaper(final WallpaperInfo wallpaper, Asset asset,
Samuel Fufa03bc6ac2019-09-19 12:01:50 -070088 @Nullable Rect cropRect, float scale, @Destination final int destination,
89 final SetWallpaperCallback callback) {
Jon Miranda16ea1b12017-12-12 14:52:48 -080090 // 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
140 public void setIndividualWallpaperWithPosition(Activity activity, WallpaperInfo wallpaper,
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700141 @WallpaperPosition int wallpaperPosition, SetWallpaperCallback callback) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800142 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) {
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700156 // 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.
Jon Miranda16ea1b12017-12-12 14:52:48 -0800160 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
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700171 // Decode full bitmap sized for screen and stretch it to fill the screen
172 // dimensions.
Jon Miranda16ea1b12017-12-12 14:52:48 -0800173 case WALLPAPER_POSITION_STRETCH:
174 asset.decodeBitmap(screenSize.x, screenSize.y, new BitmapReceiver() {
175 @Override
176 public void onBitmapDecoded(@Nullable Bitmap bitmap) {
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700177 setIndividualWallpaperStretch(wallpaper, bitmap,
178 screenSize /* stretchSize */,
Jon Miranda16ea1b12017-12-12 14:52:48 -0800179 WallpaperPersister.DEST_BOTH, callback);
180 }
181 });
182 break;
183
184 default:
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700185 Log.e(TAG, "Unsupported wallpaper position option specified: "
186 + wallpaperPosition);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800187 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 */
203 private void setIndividualWallpaperWithCenterPosition(WallpaperInfo wallpaper, Asset asset,
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700204 Point dimensions, Point screenSize, SetWallpaperCallback callback) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800205 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) {
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700214 setIndividualWallpaper(wallpaper, bitmap, WallpaperPersister.DEST_BOTH,
215 callback);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800216 }
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 */
245 private void setIndividualWallpaperWithCenterCropPosition(WallpaperInfo wallpaper, Asset asset,
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700246 Point dimensions, Point screenSize, SetWallpaperCallback callback) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800247 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 */
272 private void setIndividualWallpaper(WallpaperInfo wallpaper, Bitmap croppedBitmap,
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700273 @Destination int destination, SetWallpaperCallback callback) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800274 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.
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700284 * @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.
Jon Miranda16ea1b12017-12-12 14:52:48 -0800289 * @param destination The destination - where to set the wallpaper to.
290 * @param callback Called once the wallpaper was set or if an error occurred.
291 */
292 private void setIndividualWallpaperFill(WallpaperInfo wallpaper, Bitmap croppedBitmap,
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700293 Point fillSize, @Destination int destination, SetWallpaperCallback callback) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800294 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.
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700306 * @param stretchSize Specifies the final size to which the bitmap should be stretched
307 * prior
Jon Miranda16ea1b12017-12-12 14:52:48 -0800308 * 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 */
312 private void setIndividualWallpaperStretch(WallpaperInfo wallpaper, Bitmap croppedBitmap,
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700313 Point stretchSize, @Destination int destination, SetWallpaperCallback callback) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800314 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 */
328 private void setIndividualWallpaper(WallpaperInfo wallpaper, InputStream inputStream,
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700329 @Destination int destination, SetWallpaperCallback callback) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800330 SetWallpaperTask setWallpaperTask =
331 new SetWallpaperTask(wallpaper, inputStream, destination, callback);
332 setWallpaperTask.execute();
333 }
334
335 @Override
336 public boolean setWallpaperInRotation(Bitmap wallpaperBitmap, List<String> attributions,
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700337 int actionLabelRes, int actionIconRes, String actionUrl, String collectionId) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800338
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700339 return setWallpaperInRotationStatic(wallpaperBitmap, attributions, actionUrl,
340 actionLabelRes, actionIconRes, collectionId);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800341 }
342
343 @Override
344 public int setWallpaperBitmapInNextRotation(Bitmap wallpaperBitmap) {
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700345 return setWallpaperBitmapInRotationStatic(wallpaperBitmap);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800346 }
347
348 @Override
349 public boolean finalizeWallpaperForNextRotation(List<String> attributions, String actionUrl,
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700350 int actionLabelRes, int actionIconRes, String collectionId, int wallpaperId) {
Santiago Etchebehered1bd5092018-04-18 16:03:30 -0700351 return finalizeWallpaperForRotatingComponent(attributions, actionUrl, actionLabelRes,
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700352 actionIconRes, collectionId, wallpaperId);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800353 }
354
355 /**
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700356 * Sets wallpaper image and attributions when a static wallpaper is responsible for presenting
357 * the
Jon Miranda16ea1b12017-12-12 14:52:48 -0800358 * current "daily wallpaper".
359 */
360 private boolean setWallpaperInRotationStatic(Bitmap wallpaperBitmap, List<String> attributions,
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700361 String actionUrl, int actionLabelRes, int actionIconRes, String collectionId) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800362 final int wallpaperId = setWallpaperBitmapInRotationStatic(wallpaperBitmap);
363
364 if (wallpaperId == 0) {
365 return false;
366 }
367
Santiago Etchebehered1bd5092018-04-18 16:03:30 -0700368 return finalizeWallpaperForRotatingComponent(attributions, actionUrl, actionLabelRes,
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700369 actionIconRes, collectionId, wallpaperId);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800370 }
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 */
379 private boolean finalizeWallpaperForRotatingComponent(List<String> attributions,
Santiago Etchebehered1bd5092018-04-18 16:03:30 -0700380 String actionUrl,
381 int actionLabelRes,
382 int actionIconRes,
383 String collectionId,
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700384 int wallpaperId) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800385 mWallpaperPreferences.clearHomeWallpaperMetadata();
386
387 boolean isLockWallpaperSet = isSeparateLockScreenWallpaperSet();
388
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700389 // Persist wallpaper IDs if the rotating wallpaper component
390 mWallpaperPreferences.setHomeWallpaperManagerId(wallpaperId);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800391
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700392 // 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);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800396 }
397
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700398
Jon Miranda16ea1b12017-12-12 14:52:48 -0800399 mWallpaperPreferences.setHomeWallpaperAttributions(attributions);
400 mWallpaperPreferences.setHomeWallpaperActionUrl(actionUrl);
Santiago Etchebehered1bd5092018-04-18 16:03:30 -0700401 mWallpaperPreferences.setHomeWallpaperActionLabelRes(actionLabelRes);
402 mWallpaperPreferences.setHomeWallpaperActionIconRes(actionIconRes);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800403 // Only set base image URL for static Backdrop images, not for rotation.
404 mWallpaperPreferences.setHomeWallpaperBaseImageUrl(null);
405 mWallpaperPreferences.setHomeWallpaperCollectionId(collectionId);
406
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700407 // 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) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800410 mWallpaperPreferences.setLockWallpaperAttributions(attributions);
411 mWallpaperPreferences.setLockWallpaperActionUrl(actionUrl);
Santiago Etchebehered1bd5092018-04-18 16:03:30 -0700412 mWallpaperPreferences.setLockWallpaperActionLabelRes(actionLabelRes);
413 mWallpaperPreferences.setLockWallpaperActionIconRes(actionIconRes);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800414 mWallpaperPreferences.setLockWallpaperCollectionId(collectionId);
415 }
416
417 return true;
418 }
419
420 /**
Jon Miranda16ea1b12017-12-12 14:52:48 -0800421 * 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 */
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 /**
Jon Miranda16ea1b12017-12-12 14:52:48 -0800440 * 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 */
446 private int setBitmapToWallpaperManagerCompat(Bitmap wallpaperBitmap, boolean allowBackup,
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700447 int whichWallpaper) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800448 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
476 private int setStreamToWallpaperManagerCompat(InputStream inputStream, boolean allowBackup,
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700477 int whichWallpaper) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800478 try {
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700479 return mWallpaperManagerCompat.setStream(inputStream, null, allowBackup,
480 whichWallpaper);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800481 } catch (IOException e) {
482 return 0;
483 }
484 }
485
486 @Override
487 public void setWallpaperInfoInPreview(WallpaperInfo wallpaper) {
488 mWallpaperInfoInPreview = wallpaper;
489 }
490
491 @Override
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
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700498 // WallpaperInfo which was last previewed, then do nothing and nullify last previewed
499 // wallpaper.
Jon Miranda16ea1b12017-12-12 14:52:48 -0800500 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 */
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 */
535 private void setLiveWallpaperMetadata() {
536 android.app.WallpaperInfo previewedWallpaperComponent =
537 mWallpaperInfoInPreview.getWallpaperComponent();
538
539 mWallpaperPreferences.clearHomeWallpaperMetadata();
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700540 // 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
Jon Miranda16ea1b12017-12-12 14:52:48 -0800543 // 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
Jon Miranda16ea1b12017-12-12 14:52:48 -0800555 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
573 SetWallpaperTask(WallpaperInfo wallpaper, Bitmap bitmap, @Destination int destination,
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700574 WallpaperPersister.SetWallpaperCallback callback) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800575 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 */
586 SetWallpaperTask(WallpaperInfo wallpaper, InputStream stream,
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700587 @Destination int destination, WallpaperPersister.SetWallpaperCallback callback) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800588 mWallpaper = wallpaper;
589 mInputStream = stream;
590 mDestination = destination;
591 mCallback = callback;
592 }
593
594 void setFillSize(Point fillSize) {
595 if (mStretchSize != null) {
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700596 throw new IllegalArgumentException(
597 "Can't pass a fill size option if a stretch size is "
598 + "already set.");
Jon Miranda16ea1b12017-12-12 14:52:48 -0800599 }
600 mFillSize = fillSize;
601 }
602
603 void setStretchSize(Point stretchSize) {
604 if (mFillSize != null) {
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700605 throw new IllegalArgumentException(
606 "Can't pass a stretch size option if a fill size is "
607 + "already set.");
Jon Miranda16ea1b12017-12-12 14:52:48 -0800608 }
609 mStretchSize = stretchSize;
610 }
611
612 @Override
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
Jon Miranda16ea1b12017-12-12 14:52:48 -0800624
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700625 boolean wasLockWallpaperSet = LockWallpaperStatusChecker.isLockWallpaperSet(
626 mAppContext);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800627
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) {
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700636 mBitmap = Bitmap.createScaledBitmap(mBitmap, mStretchSize.x, mStretchSize.y,
637 true);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800638 }
639
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700640 wallpaperId = setBitmapToWallpaperManagerCompat(mBitmap, allowBackup,
641 whichWallpaper);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800642 } else if (mInputStream != null) {
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700643 wallpaperId = setStreamToWallpaperManagerCompat(mInputStream, allowBackup,
644 whichWallpaper);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800645 } else {
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700646 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.");
Jon Miranda16ea1b12017-12-12 14:52:48 -0800650 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()) {
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700659 copyRotatingWallpaperToLock();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800660 }
661 setImageWallpaperMetadata(mDestination, wallpaperId);
662 return true;
663 } else {
664 return false;
665 }
666 }
667
668 @Override
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) {
“Chuckffd832c2020-03-22 02:15:58 +0800681 mCallback.onSuccess(mWallpaper);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800682 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.
Jon Miranda16ea1b12017-12-12 14:52:48 -0800695 */
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700696 private void copyRotatingWallpaperToLock() {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800697
698 mWallpaperPreferences.setLockWallpaperAttributions(
699 mWallpaperPreferences.getHomeWallpaperAttributions());
700 mWallpaperPreferences.setLockWallpaperActionUrl(
701 mWallpaperPreferences.getHomeWallpaperActionUrl());
Santiago Etchebehered1bd5092018-04-18 16:03:30 -0700702 mWallpaperPreferences.setLockWallpaperActionLabelRes(
703 mWallpaperPreferences.getHomeWallpaperActionLabelRes());
704 mWallpaperPreferences.setLockWallpaperActionIconRes(
705 mWallpaperPreferences.getHomeWallpaperActionIconRes());
Jon Miranda16ea1b12017-12-12 14:52:48 -0800706 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.
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700712 mWallpaperPreferences.setLockWallpaperId(
713 mWallpaperManagerCompat.getWallpaperId(WallpaperManagerCompat.FLAG_LOCK));
714
Jon Miranda16ea1b12017-12-12 14:52:48 -0800715 }
716
717 /**
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700718 * Sets the image wallpaper's metadata on SharedPreferences. This method is called after the
719 * set wallpaper operation is successful.
Jon Miranda16ea1b12017-12-12 14:52:48 -0800720 *
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700721 * @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.
Jon Miranda16ea1b12017-12-12 14:52:48 -0800726 */
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
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700732 // Reset presentation mode to STATIC if an individual wallpaper is set to the
733 // home screen
Jon Miranda16ea1b12017-12-12 14:52:48 -0800734 // 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
747 private void setImageWallpaperHomeMetadata(int homeWallpaperId) {
748 if (BuildCompat.isAtLeastN()) {
749 mWallpaperPreferences.setHomeWallpaperManagerId(homeWallpaperId);
750 }
751
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700752 // 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.
Jon Miranda16ea1b12017-12-12 14:52:48 -0800757 mWallpaperManager.forgetLoadedWallpaper();
758 mBitmap = ((BitmapDrawable) mWallpaperManagerCompat.getDrawable()).getBitmap();
759 long bitmapHash = BitmapUtils.generateHashCode(mBitmap);
760
761 mWallpaperPreferences.setHomeWallpaperHashCode(bitmapHash);
762
Santiago Etchebehered1bd5092018-04-18 16:03:30 -0700763 mWallpaperPreferences.setHomeWallpaperAttributions(
764 mWallpaper.getAttributions(mAppContext));
Jon Miranda16ea1b12017-12-12 14:52:48 -0800765 mWallpaperPreferences.setHomeWallpaperBaseImageUrl(mWallpaper.getBaseImageUrl());
766 mWallpaperPreferences.setHomeWallpaperActionUrl(mWallpaper.getActionUrl(mAppContext));
Santiago Etchebeheree0810d02018-05-10 17:39:40 -0700767 mWallpaperPreferences.setHomeWallpaperActionLabelRes(
768 mWallpaper.getActionLabelRes(mAppContext));
769 mWallpaperPreferences.setHomeWallpaperActionIconRes(
770 mWallpaper.getActionIconRes(mAppContext));
Santiago Etchebehered1bd5092018-04-18 16:03:30 -0700771 mWallpaperPreferences.setHomeWallpaperCollectionId(
772 mWallpaper.getCollectionId(mAppContext));
Jon Miranda16ea1b12017-12-12 14:52:48 -0800773 mWallpaperPreferences.setHomeWallpaperRemoteId(mWallpaper.getWallpaperId());
774 }
775
776 private void setImageWallpaperLockMetadata(int lockWallpaperId) {
777 mWallpaperPreferences.setLockWallpaperId(lockWallpaperId);
Santiago Etchebehered1bd5092018-04-18 16:03:30 -0700778 mWallpaperPreferences.setLockWallpaperAttributions(
779 mWallpaper.getAttributions(mAppContext));
Jon Miranda16ea1b12017-12-12 14:52:48 -0800780 mWallpaperPreferences.setLockWallpaperActionUrl(mWallpaper.getActionUrl(mAppContext));
Santiago Etchebehere1ab75482018-06-15 15:05:25 -0700781 mWallpaperPreferences.setLockWallpaperActionLabelRes(
Santiago Etchebeheree0810d02018-05-10 17:39:40 -0700782 mWallpaper.getActionLabelRes(mAppContext));
Santiago Etchebehere1ab75482018-06-15 15:05:25 -0700783 mWallpaperPreferences.setLockWallpaperActionIconRes(
Santiago Etchebeheree0810d02018-05-10 17:39:40 -0700784 mWallpaper.getActionIconRes(mAppContext));
Santiago Etchebehered1bd5092018-04-18 16:03:30 -0700785 mWallpaperPreferences.setLockWallpaperCollectionId(
786 mWallpaper.getCollectionId(mAppContext));
“Chuck2c8626f2020-03-22 03:47:59 +0800787 mWallpaperPreferences.setLockWallpaperRemoteId(mWallpaper.getWallpaperId());
Jon Miranda16ea1b12017-12-12 14:52:48 -0800788
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700789 // Save the lock wallpaper image's hash code as well for the sake of backup & restore
790 // because WallpaperManager-generated IDs are specific to a physical device and
791 // cannot be used to identify a wallpaper image on another device after restore is
792 // complete.
Jon Miranda16ea1b12017-12-12 14:52:48 -0800793 saveLockWallpaperHashCode();
794 }
795
796 private void saveLockWallpaperHashCode() {
797 Bitmap lockBitmap = null;
798
799 ParcelFileDescriptor parcelFd = mWallpaperManagerCompat.getWallpaperFile(
800 WallpaperManagerCompat.FLAG_LOCK);
801
802 if (parcelFd == null) {
803 return;
804 }
805
806 InputStream fileStream = null;
807 try {
808 fileStream = new FileInputStream(parcelFd.getFileDescriptor());
809 lockBitmap = BitmapFactory.decodeStream(fileStream);
810 parcelFd.close();
811 } catch (IOException e) {
812 Log.e(TAG, "IO exception when closing the file descriptor.");
813 } finally {
814 if (fileStream != null) {
815 try {
816 fileStream.close();
817 } catch (IOException e) {
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700818 Log.e(TAG,
819 "IO exception when closing the input stream for the lock screen "
820 + "WP.");
Jon Miranda16ea1b12017-12-12 14:52:48 -0800821 }
822 }
823 }
824
825 if (lockBitmap != null) {
826 long bitmapHash = BitmapUtils.generateHashCode(lockBitmap);
827 mWallpaperPreferences.setLockWallpaperHashCode(bitmapHash);
828 }
829 }
830 }
831}