blob: 428ff86c28dfc840b7fb7ce2f9a5972bf9195722 [file] [log] [blame]
package com.android.wallpaper.module;
import static android.app.WallpaperManager.FLAG_LOCK;
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.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(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) {
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);
}
}
};
WallpaperStatusChecker wallpaperStatusChecker =
InjectorProvider.getInjector().getWallpaperStatusChecker();
boolean isLiveWallpaperSet =
WallpaperManager.getInstance(activity).getWallpaperInfo() != null;
// Alternative of ag/15567276
boolean isBuiltIn = !isLiveWallpaperSet
&& !wallpaperStatusChecker.isHomeStaticWallpaperSet(activity);
SetWallpaperDialogFragment setWallpaperDialog = new SetWallpaperDialogFragment();
setWallpaperDialog.setTitleResId(titleResId);
setWallpaperDialog.setListener(listenerWrapper);
if ((isLiveWallpaperSet || isBuiltIn)
&& !wallpaperStatusChecker.isLockWallpaperSet(activity)) {
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);
}
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();
});
}
}