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