| package com.android.wallpaper.module; |
| |
| import android.app.Activity; |
| import android.app.ProgressDialog; |
| import android.app.WallpaperColors; |
| import android.app.WallpaperManager; |
| import android.content.pm.ActivityInfo; |
| import android.graphics.Point; |
| import android.graphics.Rect; |
| import android.os.Build.VERSION; |
| import android.os.Build.VERSION_CODES; |
| import android.util.Log; |
| import android.view.Display; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.annotation.StringRes; |
| import androidx.fragment.app.FragmentManager; |
| import androidx.lifecycle.Lifecycle.Event; |
| import androidx.lifecycle.LifecycleEventObserver; |
| import androidx.lifecycle.LifecycleOwner; |
| |
| import com.android.wallpaper.R; |
| import com.android.wallpaper.asset.Asset; |
| import com.android.wallpaper.asset.BuiltInWallpaperAsset; |
| import com.android.wallpaper.model.LiveWallpaperInfo; |
| import com.android.wallpaper.model.WallpaperInfo; |
| import com.android.wallpaper.module.UserEventLogger.WallpaperSetFailureReason; |
| import com.android.wallpaper.module.WallpaperPersister.Destination; |
| import com.android.wallpaper.module.WallpaperPersister.SetWallpaperCallback; |
| import com.android.wallpaper.picker.SetWallpaperDialogFragment; |
| import com.android.wallpaper.picker.SetWallpaperDialogFragment.Listener; |
| import com.android.wallpaper.util.ScreenSizeCalculator; |
| import com.android.wallpaper.util.ThrowableAnalyzer; |
| import com.android.wallpaper.util.WallpaperCropUtils; |
| |
| import com.bumptech.glide.Glide; |
| |
| import java.io.IOException; |
| import java.util.Optional; |
| |
| /** |
| * Helper class used to set the current wallpaper. It handles showing the destination request dialog |
| * and actually setting the wallpaper on a given destination. |
| * It is expected to be instantiated within a Fragment or Activity, and {@link #cleanUp()} should |
| * be called from its owner's onDestroy method (or equivalent). |
| */ |
| public class WallpaperSetter { |
| |
| private static final String TAG = "WallpaperSetter"; |
| private static final String PROGRESS_DIALOG_NO_TITLE = null; |
| private static final boolean PROGRESS_DIALOG_INDETERMINATE = true; |
| |
| private static final int UNUSED_REQUEST_CODE = 1; |
| private static final String TAG_SET_WALLPAPER_DIALOG_FRAGMENT = "set_wallpaper_dialog"; |
| |
| private final WallpaperPersister mWallpaperPersister; |
| private final WallpaperPreferences mPreferences; |
| private final boolean mTestingModeEnabled; |
| private final UserEventLogger mUserEventLogger; |
| private ProgressDialog mProgressDialog; |
| private Optional<Integer> mCurrentScreenOrientation = Optional.empty(); |
| |
| public WallpaperSetter(WallpaperPersister wallpaperPersister, |
| WallpaperPreferences preferences, UserEventLogger userEventLogger, |
| boolean isTestingModeEnabled) { |
| mTestingModeEnabled = isTestingModeEnabled; |
| mWallpaperPersister = wallpaperPersister; |
| mPreferences = preferences; |
| mUserEventLogger = userEventLogger; |
| } |
| |
| /** |
| * Sets current wallpaper to the device with the minimum scale to fit the screen size. |
| * |
| * @param containerActivity main Activity that owns the current fragment |
| * @param wallpaper info for the actual wallpaper to set |
| * @param destination the wallpaper destination i.e. home vs. lockscreen vs. both. |
| * @param callback optional callback to be notified when the wallpaper is set. |
| */ |
| public void setCurrentWallpaper(Activity containerActivity, WallpaperInfo wallpaper, |
| @Destination final int destination, |
| @Nullable SetWallpaperCallback callback) { |
| Asset wallpaperAsset = wallpaper.getAsset(containerActivity.getApplicationContext()); |
| wallpaperAsset.decodeRawDimensions(containerActivity, dimensions -> { |
| if (dimensions == null) { |
| Log.e(TAG, "Raw wallpaper's dimensions are null"); |
| return; |
| } |
| |
| Display defaultDisplay = containerActivity.getWindowManager().getDefaultDisplay(); |
| Point screenSize = ScreenSizeCalculator.getInstance().getScreenSize(defaultDisplay); |
| Rect visibleRawWallpaperRect = |
| WallpaperCropUtils.calculateVisibleRect(dimensions, screenSize); |
| float wallpaperScale = WallpaperCropUtils.calculateMinZoom(dimensions, screenSize); |
| Rect cropRect = WallpaperCropUtils.calculateCropRect( |
| containerActivity.getApplicationContext(), defaultDisplay, |
| dimensions, visibleRawWallpaperRect, wallpaperScale); |
| |
| setCurrentWallpaper(containerActivity, wallpaper, wallpaperAsset, destination, |
| wallpaperScale, cropRect, null, callback); |
| }); |
| } |
| |
| /** |
| * Sets current wallpaper to the device based on current zoom and scroll state. |
| * |
| * @param containerActivity main Activity that owns the current fragment |
| * @param wallpaper info for the actual wallpaper to set |
| * @param wallpaperAsset Wallpaper asset from which to retrieve image data. |
| * @param destination The wallpaper destination i.e. home vs. lockscreen vs. both. |
| * @param wallpaperScale Scaling factor applied to the source image before setting the |
| * wallpaper to the device. |
| * @param cropRect Desired crop area of the wallpaper in post-scale units. If null, then the |
| * wallpaper image will be set without any scaling or cropping. |
| * @param callback optional callback to be notified when the wallpaper is set. |
| */ |
| public void setCurrentWallpaper(Activity containerActivity, WallpaperInfo wallpaper, |
| @Nullable Asset wallpaperAsset, @Destination final int destination, |
| float wallpaperScale, @Nullable Rect cropRect, |
| @Nullable WallpaperColors wallpaperColors, @Nullable SetWallpaperCallback callback) { |
| if (wallpaper instanceof LiveWallpaperInfo) { |
| setCurrentLiveWallpaper(containerActivity, (LiveWallpaperInfo) wallpaper, destination, |
| wallpaperColors, callback); |
| return; |
| } |
| mPreferences.setPendingWallpaperSetStatus( |
| WallpaperPreferences.WALLPAPER_SET_PENDING); |
| |
| // Save current screen rotation so we can temporarily disable rotation while setting the |
| // wallpaper and restore after setting the wallpaper finishes. |
| saveAndLockScreenOrientationIfNeeded(containerActivity); |
| |
| // Clear MosaicView tiles and Glide's cache and pools to reclaim memory for final cropped |
| // bitmap. |
| Glide.get(containerActivity).clearMemory(); |
| |
| // ProgressDialog endlessly updates the UI thread, keeping it from going idle which therefore |
| // causes Espresso to hang once the dialog is shown. |
| if (!mTestingModeEnabled && !containerActivity.isFinishing()) { |
| int themeResId = (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) |
| ? R.style.ProgressDialogThemePreL : R.style.LightDialogTheme; |
| mProgressDialog = new ProgressDialog(containerActivity, themeResId); |
| |
| mProgressDialog.setTitle(PROGRESS_DIALOG_NO_TITLE); |
| mProgressDialog.setMessage(containerActivity.getString( |
| R.string.set_wallpaper_progress_message)); |
| mProgressDialog.setIndeterminate(PROGRESS_DIALOG_INDETERMINATE); |
| if (containerActivity instanceof LifecycleOwner) { |
| ((LifecycleOwner) containerActivity).getLifecycle().addObserver( |
| new LifecycleEventObserver() { |
| @Override |
| public void onStateChanged(@NonNull LifecycleOwner source, |
| @NonNull Event event) { |
| if (event == Event.ON_DESTROY) { |
| if (mProgressDialog != null) { |
| mProgressDialog.dismiss(); |
| mProgressDialog = null; |
| } |
| } |
| } |
| }); |
| } |
| mProgressDialog.show(); |
| } |
| |
| mWallpaperPersister.setIndividualWallpaper( |
| wallpaper, wallpaperAsset, cropRect, |
| wallpaperScale, destination, new SetWallpaperCallback() { |
| @Override |
| public void onSuccess(WallpaperInfo wallpaperInfo) { |
| onWallpaperApplied(wallpaper, containerActivity); |
| if (callback != null) { |
| callback.onSuccess(wallpaper); |
| } |
| } |
| |
| @Override |
| public void onError(Throwable throwable) { |
| onWallpaperApplyError(throwable, containerActivity); |
| if (callback != null) { |
| callback.onError(throwable); |
| } |
| } |
| }); |
| } |
| |
| public void setCurrentLiveWallpaper(Activity activity, LiveWallpaperInfo wallpaper, |
| @Destination final int destination, @Nullable WallpaperColors colors, |
| @Nullable SetWallpaperCallback callback) { |
| try { |
| // Save current screen rotation so we can temporarily disable rotation while setting the |
| // wallpaper and restore after setting the wallpaper finishes. |
| saveAndLockScreenOrientationIfNeeded(activity); |
| |
| if (destination == WallpaperPersister.DEST_LOCK_SCREEN) { |
| throw new IllegalArgumentException( |
| "Live wallpaper cannot be applied on lock screen only"); |
| } |
| WallpaperManager wallpaperManager = WallpaperManager.getInstance(activity); |
| wallpaperManager.setWallpaperComponent( |
| wallpaper.getWallpaperComponent().getComponent()); |
| wallpaperManager.setWallpaperOffsetSteps(0.5f /* xStep */, 0.0f /* yStep */); |
| wallpaperManager.setWallpaperOffsets( |
| activity.getWindow().getDecorView().getRootView().getWindowToken(), |
| 0.5f /* xOffset */, 0.0f /* yOffset */); |
| if (destination == WallpaperPersister.DEST_BOTH) { |
| wallpaperManager.clear(WallpaperManager.FLAG_LOCK); |
| } |
| mPreferences.storeLatestHomeWallpaper(wallpaper.getWallpaperId(), wallpaper, |
| colors != null ? colors : |
| WallpaperColors.fromBitmap(wallpaper.getThumbAsset(activity) |
| .getLowResBitmap(activity))); |
| onWallpaperApplied(wallpaper, activity); |
| if (callback != null) { |
| callback.onSuccess(wallpaper); |
| } |
| } catch (RuntimeException | IOException e) { |
| onWallpaperApplyError(e, activity); |
| if (callback != null) { |
| callback.onError(e); |
| } |
| } |
| |
| } |
| |
| private void onWallpaperApplied(WallpaperInfo wallpaper, Activity containerActivity) { |
| mUserEventLogger.logWallpaperSet( |
| wallpaper.getCollectionId(containerActivity), |
| wallpaper.getWallpaperId()); |
| mPreferences.setPendingWallpaperSetStatus( |
| WallpaperPreferences.WALLPAPER_SET_NOT_PENDING); |
| mUserEventLogger.logWallpaperSetResult( |
| UserEventLogger.WALLPAPER_SET_RESULT_SUCCESS); |
| cleanUp(); |
| restoreScreenOrientationIfNeeded(containerActivity); |
| } |
| |
| private void onWallpaperApplyError(Throwable throwable, Activity containerActivity) { |
| mPreferences.setPendingWallpaperSetStatus( |
| WallpaperPreferences.WALLPAPER_SET_NOT_PENDING); |
| mUserEventLogger.logWallpaperSetResult( |
| UserEventLogger.WALLPAPER_SET_RESULT_FAILURE); |
| @WallpaperSetFailureReason int failureReason = ThrowableAnalyzer.isOOM( |
| throwable) |
| ? UserEventLogger.WALLPAPER_SET_FAILURE_REASON_OOM |
| : UserEventLogger.WALLPAPER_SET_FAILURE_REASON_OTHER; |
| mUserEventLogger.logWallpaperSetFailureReason(failureReason); |
| |
| cleanUp(); |
| restoreScreenOrientationIfNeeded(containerActivity); |
| } |
| |
| /** |
| * Call this method to clean up this instance's state. |
| */ |
| public void cleanUp() { |
| if (mProgressDialog != null) { |
| mProgressDialog.dismiss(); |
| mProgressDialog = null; |
| } |
| } |
| |
| /** |
| * Show a dialog asking the user for the Wallpaper's destination |
| * (eg, "Home screen", "Lock Screen") |
| * @param isLiveWallpaper whether the wallpaper that we want to set is a live wallpaper. |
| * @param listener {@link SetWallpaperDialogFragment.Listener} that will receive the response. |
| * @see Destination |
| */ |
| public void requestDestination(Activity activity, FragmentManager fragmentManager, |
| Listener listener, boolean isLiveWallpaper) { |
| requestDestination(activity, fragmentManager, R.string.set_wallpaper_dialog_message, |
| listener, isLiveWallpaper); |
| } |
| |
| /** |
| * Show a dialog asking the user for the Wallpaper's destination |
| * (eg, "Home screen", "Lock Screen") |
| * @param isLiveWallpaper whether the wallpaper that we want to set is a live wallpaper. |
| * @param listener {@link SetWallpaperDialogFragment.Listener} that will receive the response. |
| * @param titleResId title for the dialog |
| * @see Destination |
| */ |
| public void requestDestination(Activity activity, FragmentManager fragmentManager, |
| @StringRes int titleResId, Listener listener, boolean isLiveWallpaper) { |
| CurrentWallpaperInfoFactory factory = InjectorProvider.getInjector() |
| .getCurrentWallpaperFactory(activity); |
| saveAndLockScreenOrientationIfNeeded(activity); |
| Listener listenerWrapper = new Listener() { |
| @Override |
| public void onSet(int destination) { |
| if (listener != null) { |
| listener.onSet(destination); |
| } |
| } |
| |
| @Override |
| public void onDialogDismissed(boolean withItemSelected) { |
| if (!withItemSelected) { |
| restoreScreenOrientationIfNeeded(activity); |
| } |
| if (listener != null) { |
| listener.onDialogDismissed(withItemSelected); |
| } |
| } |
| }; |
| factory.createCurrentWallpaperInfos((homeWallpaper, lockWallpaper, presentationMode) -> { |
| SetWallpaperDialogFragment setWallpaperDialog = new SetWallpaperDialogFragment(); |
| setWallpaperDialog.setTitleResId(titleResId); |
| setWallpaperDialog.setListener(listenerWrapper); |
| boolean isBuiltIn = homeWallpaper.getAsset(activity) instanceof BuiltInWallpaperAsset; |
| if ((homeWallpaper instanceof LiveWallpaperInfo || isBuiltIn) |
| && lockWallpaper == null) { |
| if (isLiveWallpaper) { |
| // If lock wallpaper is live and we're setting a live wallpaper, we can only |
| // set it to both, so bypass the dialog. |
| listener.onSet(WallpaperPersister.DEST_BOTH); |
| restoreScreenOrientationIfNeeded(activity); |
| return; |
| } |
| // if the lock wallpaper is a live wallpaper, we cannot set a home-only static one |
| setWallpaperDialog.setHomeOptionAvailable(false); |
| } |
| if (isLiveWallpaper) { |
| setWallpaperDialog.setLockOptionAvailable(false); |
| } |
| setWallpaperDialog.show(fragmentManager, TAG_SET_WALLPAPER_DIALOG_FRAGMENT); |
| }, true); // Force refresh as the wallpaper may have been set while this fragment was paused |
| } |
| |
| private void saveAndLockScreenOrientationIfNeeded(Activity activity) { |
| if (!mCurrentScreenOrientation.isPresent()) { |
| mCurrentScreenOrientation = Optional.of(activity.getRequestedOrientation()); |
| activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED); |
| } |
| } |
| |
| private void restoreScreenOrientationIfNeeded(Activity activity) { |
| mCurrentScreenOrientation.ifPresent(orientation -> { |
| if (activity.getRequestedOrientation() != orientation) { |
| activity.setRequestedOrientation(orientation); |
| } |
| mCurrentScreenOrientation = Optional.empty(); |
| }); |
| } |
| } |