package com.android.wallpaper.module; import static android.stats.style.StyleEnums.SET_WALLPAPER_ENTRY_POINT_RESTORE; import android.app.Activity; import android.app.ProgressDialog; import android.app.WallpaperColors; import android.app.WallpaperManager; import android.content.ComponentName; import android.content.Context; import android.content.pm.ActivityInfo; import android.graphics.Rect; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.util.Log; 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.model.LiveWallpaperInfo; import com.android.wallpaper.model.WallpaperInfo; import com.android.wallpaper.module.WallpaperPersister.Destination; import com.android.wallpaper.module.WallpaperPersister.SetWallpaperCallback; import com.android.wallpaper.module.logging.UserEventLogger; import com.android.wallpaper.module.logging.UserEventLogger.SetWallpaperEntryPoint; import com.android.wallpaper.picker.SetWallpaperDialogFragment; import com.android.wallpaper.picker.SetWallpaperDialogFragment.Listener; import com.bumptech.glide.Glide; import java.io.IOException; import java.lang.reflect.Method; 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 UserEventLogger mUserEventLogger; private final CurrentWallpaperInfoFactory mCurrentWallpaperInfoFactory; private ProgressDialog mProgressDialog; private Optional mCurrentScreenOrientation = Optional.empty(); public WallpaperSetter(WallpaperPersister wallpaperPersister, WallpaperPreferences preferences, UserEventLogger userEventLogger, CurrentWallpaperInfoFactory currentWallpaperInfoFactory) { mWallpaperPersister = wallpaperPersister; mPreferences = preferences; mUserEventLogger = userEventLogger; mCurrentWallpaperInfoFactory = currentWallpaperInfoFactory; } /** * 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 setWallpaperEntryPoint The entry point where the wallpaper is set. * @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, @SetWallpaperEntryPoint int setWallpaperEntryPoint, @Destination final int destination, float wallpaperScale, @Nullable Rect cropRect, WallpaperColors wallpaperColors, @Nullable SetWallpaperCallback callback) { if (wallpaper instanceof LiveWallpaperInfo) { setCurrentLiveWallpaper(containerActivity, (LiveWallpaperInfo) wallpaper, setWallpaperEntryPoint, 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 (!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, @Destination int destination) { onWallpaperApplied(containerActivity, wallpaper, setWallpaperEntryPoint, destination); if (callback != null) { callback.onSuccess(wallpaper, destination); } } @Override public void onError(Throwable throwable) { onWallpaperApplyError(containerActivity); if (callback != null) { callback.onError(throwable); } } }); mCurrentWallpaperInfoFactory.clearCurrentWallpaperInfos(); } private void setCurrentLiveWallpaper(Activity activity, LiveWallpaperInfo wallpaper, @SetWallpaperEntryPoint int setWallpaperEntryPoint, @Destination final int destination, 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); WallpaperManager wallpaperManager = WallpaperManager.getInstance(activity); LiveWallpaperInfo updatedWallpaperInfo = wallpaper.saveWallpaper( activity.getApplicationContext(), destination); if (updatedWallpaperInfo != null) { wallpaper = updatedWallpaperInfo; } setWallpaperComponent(wallpaperManager, wallpaper, destination); wallpaperManager.setWallpaperOffsetSteps(0.5f /* xStep */, 0.0f /* yStep */); wallpaperManager.setWallpaperOffsets( activity.getWindow().getDecorView().getRootView().getWindowToken(), 0.5f /* xOffset */, 0.0f /* yOffset */); mPreferences.storeLatestWallpaper(WallpaperPersister.destinationToFlags(destination), wallpaper.getWallpaperId(), wallpaper, colors); mCurrentWallpaperInfoFactory.clearCurrentWallpaperInfos(); onWallpaperApplied(activity, wallpaper, setWallpaperEntryPoint, destination); if (callback != null) { callback.onSuccess(wallpaper, destination); } mWallpaperPersister.onLiveWallpaperSet(destination); } catch (RuntimeException | IOException e) { onWallpaperApplyError(activity); if (callback != null) { callback.onError(e); } } } private void setWallpaperComponent(WallpaperManager wallpaperManager, LiveWallpaperInfo wallpaper, int destination) throws IOException { try { Method m = wallpaperManager.getClass().getMethod("setWallpaperComponentWithFlags", ComponentName.class, int.class); wallpaperManager.setWallpaperComponentWithFlags( wallpaper.getWallpaperComponent().getComponent(), WallpaperPersister.destinationToFlags(destination)); } catch (NoSuchMethodException e) { Log.d(TAG, "setWallpaperComponentWithFlags not available, using setWallpaperComponent"); wallpaperManager.setWallpaperComponent( wallpaper.getWallpaperComponent().getComponent()); } } /** * Sets current live wallpaper to the device (restore case) * * @param context The context for initiating wallpaper manager * @param wallpaper Information for the actual wallpaper to set * @param destination The wallpaper destination i.e. home vs. lockscreen vs. both * @param colors The {@link WallpaperColors} for placeholder of quickswitching * @param callback Optional callback to be notified when the wallpaper is set. */ public void setCurrentLiveWallpaperFromRestore(Context context, LiveWallpaperInfo wallpaper, @Destination final int destination, @Nullable WallpaperColors colors, @Nullable SetWallpaperCallback callback) { try { WallpaperManager wallpaperManager = WallpaperManager.getInstance(context); setWallpaperComponent(wallpaperManager, wallpaper, destination); mPreferences.storeLatestWallpaper(WallpaperPersister.destinationToFlags(destination), wallpaper.getWallpaperId(), wallpaper, colors != null ? colors : WallpaperColors.fromBitmap(wallpaper.getThumbAsset(context) .getLowResBitmap(context))); mCurrentWallpaperInfoFactory.clearCurrentWallpaperInfos(); // Not call onWallpaperApplied() as no UI is presented. mUserEventLogger.logWallpaperApplied( wallpaper.getCollectionId(context), wallpaper.getWallpaperId(), wallpaper.getEffectNames(), SET_WALLPAPER_ENTRY_POINT_RESTORE, UserEventLogger.Companion.toWallpaperDestinationForLogging(destination)); if (callback != null) { callback.onSuccess(wallpaper, destination); } mWallpaperPersister.onLiveWallpaperSet(destination); } catch (RuntimeException | IOException e) { // Not call onWallpaperApplyError() as no UI is presented. if (callback != null) { callback.onError(e); } } } private void onWallpaperApplied( Activity containerActivity, WallpaperInfo wallpaper, @SetWallpaperEntryPoint int setWallpaperEntryPoint, @Destination int destination) { mUserEventLogger.logWallpaperApplied( wallpaper.getCollectionId(containerActivity), wallpaper.getWallpaperId(), wallpaper.getEffectNames(), setWallpaperEntryPoint, UserEventLogger.Companion.toWallpaperDestinationForLogging(destination)); mPreferences.setPendingWallpaperSetStatus( WallpaperPreferences.WALLPAPER_SET_NOT_PENDING); cleanUp(); restoreScreenOrientationIfNeeded(containerActivity); } private void onWallpaperApplyError(Activity containerActivity) { mPreferences.setPendingWallpaperSetStatus( WallpaperPreferences.WALLPAPER_SET_NOT_PENDING); 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. * @param isLockOptionAllowed whether the wallpaper we want to set can be set on lockscreen * @param isHomeOptionAllowed whether the wallpaper we want to set can be set on homescreen * @see Destination */ public void requestDestination(Activity activity, FragmentManager fragmentManager, Listener listener, boolean isLiveWallpaper, boolean isHomeOptionAllowed, boolean isLockOptionAllowed) { requestDestination(activity, fragmentManager, R.string.set_wallpaper_dialog_message, listener, isLiveWallpaper, isHomeOptionAllowed, isLockOptionAllowed); } /** * 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 * @param isHomeOption whether the wallpaper we want to set can be set on homescreen * @param isLockOption whether the wallpaper we want to set can be set on lockscreen * @see Destination */ public void requestDestination(Activity activity, FragmentManager fragmentManager, @StringRes int titleResId, Listener listener, boolean isLiveWallpaper, boolean isHomeOption, boolean isLockOption) { 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); } } }; WallpaperManager wallpaperManager = WallpaperManager.getInstance(activity); SetWallpaperDialogFragment setWallpaperDialog = new SetWallpaperDialogFragment(); setWallpaperDialog.setTitleResId(titleResId); setWallpaperDialog.setListener(listenerWrapper); if (isLiveWallpaper) { setWallpaperDialog.setHomeOptionAvailable(isHomeOption); setWallpaperDialog.setLockOptionAvailable(isLockOption); } setWallpaperDialog.show(fragmentManager, TAG_SET_WALLPAPER_DIALOG_FRAGMENT); } 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(); }); } }