• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.wallpaper.module;
2 
3 import static android.app.WallpaperManager.FLAG_LOCK;
4 
5 import android.app.Activity;
6 import android.app.ProgressDialog;
7 import android.app.WallpaperColors;
8 import android.app.WallpaperManager;
9 import android.content.ComponentName;
10 import android.content.Context;
11 import android.content.pm.ActivityInfo;
12 import android.graphics.Point;
13 import android.graphics.Rect;
14 import android.os.Build.VERSION;
15 import android.os.Build.VERSION_CODES;
16 import android.util.Log;
17 import android.view.Display;
18 
19 import androidx.annotation.NonNull;
20 import androidx.annotation.Nullable;
21 import androidx.annotation.StringRes;
22 import androidx.fragment.app.FragmentManager;
23 import androidx.lifecycle.Lifecycle.Event;
24 import androidx.lifecycle.LifecycleEventObserver;
25 import androidx.lifecycle.LifecycleOwner;
26 
27 import com.android.wallpaper.R;
28 import com.android.wallpaper.asset.Asset;
29 import com.android.wallpaper.model.LiveWallpaperInfo;
30 import com.android.wallpaper.model.WallpaperInfo;
31 import com.android.wallpaper.module.UserEventLogger.WallpaperSetFailureReason;
32 import com.android.wallpaper.module.WallpaperPersister.Destination;
33 import com.android.wallpaper.module.WallpaperPersister.SetWallpaperCallback;
34 import com.android.wallpaper.picker.SetWallpaperDialogFragment;
35 import com.android.wallpaper.picker.SetWallpaperDialogFragment.Listener;
36 import com.android.wallpaper.util.ScreenSizeCalculator;
37 import com.android.wallpaper.util.ThrowableAnalyzer;
38 import com.android.wallpaper.util.WallpaperCropUtils;
39 
40 import com.bumptech.glide.Glide;
41 
42 import java.io.IOException;
43 import java.lang.reflect.Method;
44 import java.util.Optional;
45 
46 /**
47  * Helper class used to set the current wallpaper. It handles showing the destination request dialog
48  * and actually setting the wallpaper on a given destination.
49  * It is expected to be instantiated within a Fragment or Activity, and {@link #cleanUp()} should
50  * be called from its owner's onDestroy method (or equivalent).
51  */
52 public class WallpaperSetter {
53 
54     private static final String TAG = "WallpaperSetter";
55     private static final String PROGRESS_DIALOG_NO_TITLE = null;
56     private static final boolean PROGRESS_DIALOG_INDETERMINATE = true;
57 
58     private static final int UNUSED_REQUEST_CODE = 1;
59     private static final String TAG_SET_WALLPAPER_DIALOG_FRAGMENT = "set_wallpaper_dialog";
60 
61     private final WallpaperPersister mWallpaperPersister;
62     private final WallpaperPreferences mPreferences;
63     private final boolean mTestingModeEnabled;
64     private final UserEventLogger mUserEventLogger;
65     private final CurrentWallpaperInfoFactory mCurrentWallpaperInfoFactory;
66     private ProgressDialog mProgressDialog;
67     private Optional<Integer> mCurrentScreenOrientation = Optional.empty();
68 
WallpaperSetter(WallpaperPersister wallpaperPersister, WallpaperPreferences preferences, UserEventLogger userEventLogger, CurrentWallpaperInfoFactory currentWallpaperInfoFactory, boolean isTestingModeEnabled)69     public WallpaperSetter(WallpaperPersister wallpaperPersister,
70             WallpaperPreferences preferences, UserEventLogger userEventLogger,
71             CurrentWallpaperInfoFactory currentWallpaperInfoFactory,
72             boolean isTestingModeEnabled) {
73         mTestingModeEnabled = isTestingModeEnabled;
74         mWallpaperPersister = wallpaperPersister;
75         mPreferences = preferences;
76         mUserEventLogger = userEventLogger;
77         mCurrentWallpaperInfoFactory = currentWallpaperInfoFactory;
78     }
79 
80     /**
81      * Sets current wallpaper to the device with the minimum scale to fit the screen size.
82      *
83      * @param containerActivity main Activity that owns the current fragment
84      * @param wallpaper         info for the actual wallpaper to set
85      * @param destination       the wallpaper destination i.e. home vs. lockscreen vs. both.
86      * @param callback          optional callback to be notified when the wallpaper is set.
87      */
setCurrentWallpaper(Activity containerActivity, WallpaperInfo wallpaper, @Destination final int destination, @Nullable SetWallpaperCallback callback)88     public void setCurrentWallpaper(Activity containerActivity, WallpaperInfo wallpaper,
89             @Destination final int destination,
90             @Nullable SetWallpaperCallback callback) {
91         Asset wallpaperAsset = wallpaper.getAsset(containerActivity.getApplicationContext());
92         wallpaperAsset.decodeRawDimensions(containerActivity, dimensions -> {
93             if (dimensions == null) {
94                 Log.e(TAG, "Raw wallpaper's dimensions are null");
95                 return;
96             }
97 
98             Display defaultDisplay = containerActivity.getWindowManager().getDefaultDisplay();
99             Point screenSize = ScreenSizeCalculator.getInstance().getScreenSize(defaultDisplay);
100             Rect visibleRawWallpaperRect =
101                     WallpaperCropUtils.calculateVisibleRect(dimensions, screenSize);
102             float wallpaperScale = WallpaperCropUtils.calculateMinZoom(dimensions, screenSize);
103             Rect cropRect = WallpaperCropUtils.calculateCropRect(
104                     containerActivity.getApplicationContext(), defaultDisplay,
105                     dimensions, visibleRawWallpaperRect, wallpaperScale);
106 
107             setCurrentWallpaper(containerActivity, wallpaper, wallpaperAsset, destination,
108                     wallpaperScale, cropRect, null, callback);
109         });
110     }
111 
112     /**
113      * Sets current wallpaper to the device based on current zoom and scroll state.
114      *
115      * @param containerActivity main Activity that owns the current fragment
116      * @param wallpaper         Info for the actual wallpaper to set
117      * @param wallpaperAsset    Wallpaper asset from which to retrieve image data.
118      * @param destination       The wallpaper destination i.e. home vs. lockscreen vs. both.
119      * @param wallpaperScale    Scaling factor applied to the source image before setting the
120      *                          wallpaper to the device.
121      * @param cropRect          Desired crop area of the wallpaper in post-scale units. If null,
122      *                          then the
123      *                          wallpaper image will be set without any scaling or cropping.
124      * @param callback          Optional callback to be notified when the wallpaper is set.
125      */
setCurrentWallpaper(Activity containerActivity, WallpaperInfo wallpaper, @Nullable Asset wallpaperAsset, @Destination final int destination, float wallpaperScale, @Nullable Rect cropRect, WallpaperColors wallpaperColors, @Nullable SetWallpaperCallback callback)126     public void setCurrentWallpaper(Activity containerActivity, WallpaperInfo wallpaper,
127             @Nullable Asset wallpaperAsset, @Destination final int destination,
128             float wallpaperScale, @Nullable Rect cropRect, WallpaperColors wallpaperColors,
129             @Nullable SetWallpaperCallback callback) {
130         if (wallpaper instanceof LiveWallpaperInfo) {
131             setCurrentLiveWallpaper(containerActivity, (LiveWallpaperInfo) wallpaper, destination,
132                     wallpaperColors, callback);
133             return;
134         }
135         mPreferences.setPendingWallpaperSetStatus(
136                 WallpaperPreferences.WALLPAPER_SET_PENDING);
137 
138         // Save current screen rotation so we can temporarily disable rotation while setting the
139         // wallpaper and restore after setting the wallpaper finishes.
140         saveAndLockScreenOrientationIfNeeded(containerActivity);
141 
142         // Clear MosaicView tiles and Glide's cache and pools to reclaim memory for final cropped
143         // bitmap.
144         Glide.get(containerActivity).clearMemory();
145 
146         // ProgressDialog endlessly updates the UI thread, keeping it from going idle which
147         // therefore causes Espresso to hang once the dialog is shown.
148         if (!mTestingModeEnabled && !containerActivity.isFinishing()) {
149             int themeResId = (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP)
150                     ? R.style.ProgressDialogThemePreL : R.style.LightDialogTheme;
151             mProgressDialog = new ProgressDialog(containerActivity, themeResId);
152 
153             mProgressDialog.setTitle(PROGRESS_DIALOG_NO_TITLE);
154             mProgressDialog.setMessage(containerActivity.getString(
155                     R.string.set_wallpaper_progress_message));
156             mProgressDialog.setIndeterminate(PROGRESS_DIALOG_INDETERMINATE);
157             if (containerActivity instanceof LifecycleOwner) {
158                 ((LifecycleOwner) containerActivity).getLifecycle().addObserver(
159                         new LifecycleEventObserver() {
160                             @Override
161                             public void onStateChanged(@NonNull LifecycleOwner source,
162                                     @NonNull Event event) {
163                                 if (event == Event.ON_DESTROY) {
164                                     if (mProgressDialog != null) {
165                                         mProgressDialog.dismiss();
166                                         mProgressDialog = null;
167                                     }
168                                 }
169                             }
170                         });
171             }
172             mProgressDialog.show();
173         }
174 
175         mWallpaperPersister.setIndividualWallpaper(
176                 wallpaper, wallpaperAsset, cropRect,
177                 wallpaperScale, destination, new SetWallpaperCallback() {
178                     @Override
179                     public void onSuccess(WallpaperInfo wallpaperInfo,
180                             @Destination int destination) {
181                         onWallpaperApplied(wallpaper, containerActivity);
182                         if (callback != null) {
183                             callback.onSuccess(wallpaper, destination);
184                         }
185                     }
186 
187                     @Override
188                     public void onError(Throwable throwable) {
189                         onWallpaperApplyError(throwable, containerActivity);
190                         if (callback != null) {
191                             callback.onError(throwable);
192                         }
193                     }
194                 });
195         mCurrentWallpaperInfoFactory.clearCurrentWallpaperInfos();
196     }
197 
setCurrentLiveWallpaper(Activity activity, LiveWallpaperInfo wallpaper, @Destination final int destination, WallpaperColors colors, @Nullable SetWallpaperCallback callback)198     private void setCurrentLiveWallpaper(Activity activity, LiveWallpaperInfo wallpaper,
199             @Destination final int destination, WallpaperColors colors,
200             @Nullable SetWallpaperCallback callback) {
201         try {
202             // Save current screen rotation so we can temporarily disable rotation while setting the
203             // wallpaper and restore after setting the wallpaper finishes.
204             saveAndLockScreenOrientationIfNeeded(activity);
205 
206             WallpaperManager wallpaperManager = WallpaperManager.getInstance(activity);
207             if (destination == WallpaperPersister.DEST_LOCK_SCREEN
208                     && !wallpaperManager.isLockscreenLiveWallpaperEnabled()) {
209                 throw new IllegalArgumentException(
210                         "Live wallpaper cannot be applied on lock screen only");
211             }
212 
213             LiveWallpaperInfo updatedWallpaperInfo = wallpaper.saveWallpaper(
214                     activity.getApplicationContext(), destination);
215             if (updatedWallpaperInfo != null) {
216                 wallpaper = updatedWallpaperInfo;
217             }
218             setWallpaperComponent(wallpaperManager, wallpaper, destination);
219             wallpaperManager.setWallpaperOffsetSteps(0.5f /* xStep */, 0.0f /* yStep */);
220             wallpaperManager.setWallpaperOffsets(
221                     activity.getWindow().getDecorView().getRootView().getWindowToken(),
222                     0.5f /* xOffset */, 0.0f /* yOffset */);
223             mPreferences.storeLatestWallpaper(WallpaperPersister.destinationToFlags(destination),
224                     wallpaper.getWallpaperId(), wallpaper, colors);
225             mCurrentWallpaperInfoFactory.clearCurrentWallpaperInfos();
226             onWallpaperApplied(wallpaper, activity);
227             if (callback != null) {
228                 callback.onSuccess(wallpaper, destination);
229             }
230             mWallpaperPersister.onLiveWallpaperSet(destination);
231         } catch (RuntimeException | IOException e) {
232             onWallpaperApplyError(e, activity);
233             if (callback != null) {
234                 callback.onError(e);
235             }
236         }
237     }
238 
setWallpaperComponent(WallpaperManager wallpaperManager, LiveWallpaperInfo wallpaper, int destination)239     private void setWallpaperComponent(WallpaperManager wallpaperManager,
240             LiveWallpaperInfo wallpaper, int destination) throws IOException {
241         try {
242             Method m = wallpaperManager.getClass().getMethod("setWallpaperComponentWithFlags",
243                     ComponentName.class, int.class);
244             wallpaperManager.setWallpaperComponentWithFlags(
245                     wallpaper.getWallpaperComponent().getComponent(),
246                     WallpaperPersister.destinationToFlags(destination));
247         } catch (NoSuchMethodException e) {
248             Log.d(TAG, "setWallpaperComponentWithFlags not available, using setWallpaperComponent");
249             wallpaperManager.setWallpaperComponent(
250                     wallpaper.getWallpaperComponent().getComponent());
251         }
252         if (!wallpaperManager.isLockscreenLiveWallpaperEnabled()
253                 && destination == WallpaperPersister.DEST_BOTH) {
254             wallpaperManager.clear(FLAG_LOCK);
255         }
256     }
257 
258     /**
259      * Sets current live wallpaper to the device (restore case)
260      *
261      * @param context     The context for initiating wallpaper manager
262      * @param wallpaper   Information for the actual wallpaper to set
263      * @param destination The wallpaper destination i.e. home vs. lockscreen vs. both
264      * @param colors      The {@link WallpaperColors} for placeholder of quickswitching
265      * @param callback    Optional callback to be notified when the wallpaper is set.
266      */
setCurrentLiveWallpaper(Context context, LiveWallpaperInfo wallpaper, @Destination final int destination, @Nullable WallpaperColors colors, @Nullable SetWallpaperCallback callback)267     public void setCurrentLiveWallpaper(Context context, LiveWallpaperInfo wallpaper,
268             @Destination final int destination, @Nullable WallpaperColors colors,
269             @Nullable SetWallpaperCallback callback) {
270         try {
271             WallpaperManager wallpaperManager = WallpaperManager.getInstance(context);
272             if (destination == WallpaperPersister.DEST_LOCK_SCREEN
273                     && !wallpaperManager.isLockscreenLiveWallpaperEnabled()) {
274                 throw new IllegalArgumentException(
275                         "Live wallpaper cannot be applied on lock screen only");
276             }
277             setWallpaperComponent(wallpaperManager, wallpaper, destination);
278             mPreferences.storeLatestWallpaper(WallpaperPersister.destinationToFlags(destination),
279                     wallpaper.getWallpaperId(),
280                     wallpaper, colors != null ? colors :
281                             WallpaperColors.fromBitmap(wallpaper.getThumbAsset(context)
282                                     .getLowResBitmap(context)));
283             mCurrentWallpaperInfoFactory.clearCurrentWallpaperInfos();
284             // Not call onWallpaperApplied() as no UI is presented.
285             if (callback != null) {
286                 callback.onSuccess(wallpaper, destination);
287             }
288             mWallpaperPersister.onLiveWallpaperSet(destination);
289         } catch (RuntimeException | IOException e) {
290             // Not call onWallpaperApplyError() as no UI is presented.
291             if (callback != null) {
292                 callback.onError(e);
293             }
294         }
295     }
296 
onWallpaperApplied(WallpaperInfo wallpaper, Activity containerActivity)297     private void onWallpaperApplied(WallpaperInfo wallpaper, Activity containerActivity) {
298         mUserEventLogger.logWallpaperSet(
299                 wallpaper.getCollectionId(containerActivity),
300                 wallpaper.getWallpaperId(), wallpaper.getEffectNames());
301         mPreferences.setPendingWallpaperSetStatus(
302                 WallpaperPreferences.WALLPAPER_SET_NOT_PENDING);
303         mUserEventLogger.logWallpaperSetResult(
304                 UserEventLogger.WALLPAPER_SET_RESULT_SUCCESS);
305         cleanUp();
306         restoreScreenOrientationIfNeeded(containerActivity);
307     }
308 
onWallpaperApplyError(Throwable throwable, Activity containerActivity)309     private void onWallpaperApplyError(Throwable throwable, Activity containerActivity) {
310         mPreferences.setPendingWallpaperSetStatus(
311                 WallpaperPreferences.WALLPAPER_SET_NOT_PENDING);
312         mUserEventLogger.logWallpaperSetResult(
313                 UserEventLogger.WALLPAPER_SET_RESULT_FAILURE);
314         @WallpaperSetFailureReason int failureReason = ThrowableAnalyzer.isOOM(
315                 throwable)
316                 ? UserEventLogger.WALLPAPER_SET_FAILURE_REASON_OOM
317                 : UserEventLogger.WALLPAPER_SET_FAILURE_REASON_OTHER;
318         mUserEventLogger.logWallpaperSetFailureReason(failureReason);
319 
320         cleanUp();
321         restoreScreenOrientationIfNeeded(containerActivity);
322     }
323 
324     /**
325      * Call this method to clean up this instance's state.
326      */
cleanUp()327     public void cleanUp() {
328         if (mProgressDialog != null) {
329             mProgressDialog.dismiss();
330             mProgressDialog = null;
331         }
332     }
333 
334     /**
335      * Show a dialog asking the user for the Wallpaper's destination
336      * (eg, "Home screen", "Lock Screen")
337      *
338      * @param isLiveWallpaper whether the wallpaper that we want to set is a live wallpaper.
339      * @param listener        {@link SetWallpaperDialogFragment.Listener} that will receive the
340      *                        response.
341      * @param isLockOptionAllowed whether the wallpaper we want to set can be set on lockscreen
342      * @param isHomeOptionAllowed whether the wallpaper we want to set can be set on homescreen
343      * @see Destination
344      */
requestDestination(Activity activity, FragmentManager fragmentManager, Listener listener, boolean isLiveWallpaper, boolean isHomeOptionAllowed, boolean isLockOptionAllowed)345     public void requestDestination(Activity activity, FragmentManager fragmentManager,
346             Listener listener, boolean isLiveWallpaper, boolean isHomeOptionAllowed,
347             boolean isLockOptionAllowed) {
348         requestDestination(activity, fragmentManager, R.string.set_wallpaper_dialog_message,
349                 listener, isLiveWallpaper, isHomeOptionAllowed, isLockOptionAllowed);
350     }
351 
352     /**
353      * Show a dialog asking the user for the Wallpaper's destination
354      * (eg, "Home screen", "Lock Screen")
355      *
356      * @param isLiveWallpaper whether the wallpaper that we want to set is a live wallpaper.
357      * @param listener        {@link SetWallpaperDialogFragment.Listener} that will receive the
358      *                        response.
359      * @param titleResId      title for the dialog
360      * @param isHomeOption    whether the wallpaper we want to set can be set on homescreen
361      * @param isLockOption    whether the wallpaper we want to set can be set on lockscreen
362      * @see Destination
363      */
requestDestination(Activity activity, FragmentManager fragmentManager, @StringRes int titleResId, Listener listener, boolean isLiveWallpaper, boolean isHomeOption, boolean isLockOption)364     public void requestDestination(Activity activity, FragmentManager fragmentManager,
365             @StringRes int titleResId, Listener listener, boolean isLiveWallpaper,
366             boolean isHomeOption, boolean isLockOption) {
367         saveAndLockScreenOrientationIfNeeded(activity);
368         Listener listenerWrapper = new Listener() {
369             @Override
370             public void onSet(int destination) {
371                 if (listener != null) {
372                     listener.onSet(destination);
373                 }
374             }
375 
376             @Override
377             public void onDialogDismissed(boolean withItemSelected) {
378                 if (!withItemSelected) {
379                     restoreScreenOrientationIfNeeded(activity);
380                 }
381                 if (listener != null) {
382                     listener.onDialogDismissed(withItemSelected);
383                 }
384             }
385         };
386 
387         WallpaperManager wallpaperManager = WallpaperManager.getInstance(activity);
388         SetWallpaperDialogFragment setWallpaperDialog = new SetWallpaperDialogFragment();
389         setWallpaperDialog.setTitleResId(titleResId);
390         setWallpaperDialog.setListener(listenerWrapper);
391         if (isLiveWallpaper) {
392             setWallpaperDialog.setHomeOptionAvailable(isHomeOption);
393             setWallpaperDialog.setLockOptionAvailable(isLockOption);
394         }
395         if (wallpaperManager.isLockscreenLiveWallpaperEnabled()) {
396             setWallpaperDialog.show(fragmentManager, TAG_SET_WALLPAPER_DIALOG_FRAGMENT);
397             return;
398         }
399 
400         WallpaperStatusChecker wallpaperStatusChecker = InjectorProvider.getInjector()
401                 .getWallpaperStatusChecker(activity.getApplicationContext());
402         boolean isLiveWallpaperSet =
403                 WallpaperManager.getInstance(activity).getWallpaperInfo() != null;
404         // Alternative of ag/15567276
405         boolean isBuiltIn = !isLiveWallpaperSet
406                 && !wallpaperStatusChecker.isHomeStaticWallpaperSet();
407 
408         if ((isLiveWallpaperSet || isBuiltIn)
409                 && !wallpaperStatusChecker.isLockWallpaperSet()) {
410             if (isLiveWallpaper) {
411                 // If lock wallpaper is live and we're setting a live wallpaper, we can only
412                 // set it to both, so bypass the dialog.
413                 listener.onSet(WallpaperPersister.DEST_BOTH);
414                 restoreScreenOrientationIfNeeded(activity);
415                 return;
416             }
417             // if the lock wallpaper is a live wallpaper, we cannot set a home-only static one
418             setWallpaperDialog.setHomeOptionAvailable(false);
419         }
420         if (isLiveWallpaper) {
421             setWallpaperDialog.setLockOptionAvailable(false);
422         }
423         setWallpaperDialog.show(fragmentManager, TAG_SET_WALLPAPER_DIALOG_FRAGMENT);
424     }
425 
saveAndLockScreenOrientationIfNeeded(Activity activity)426     private void saveAndLockScreenOrientationIfNeeded(Activity activity) {
427         if (!mCurrentScreenOrientation.isPresent()) {
428             mCurrentScreenOrientation = Optional.of(activity.getRequestedOrientation());
429             activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
430         }
431     }
432 
restoreScreenOrientationIfNeeded(Activity activity)433     private void restoreScreenOrientationIfNeeded(Activity activity) {
434         mCurrentScreenOrientation.ifPresent(orientation -> {
435             if (activity.getRequestedOrientation() != orientation) {
436                 activity.setRequestedOrientation(orientation);
437             }
438             mCurrentScreenOrientation = Optional.empty();
439         });
440     }
441 }