blob: f8a75d7a86d4958d55e6ea7ede81aa21512ebb77 [file] [log] [blame]
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<Integer> 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();
});
}
}