blob: 0dc4f95ba15d7b971ec4783f382181e748c70d9e [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wallpaper.model;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import android.animation.Animator;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.service.wallpaper.WallpaperService;
import android.view.LayoutInflater;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.cardview.widget.CardView;
import androidx.core.widget.ContentLoadingProgressBar;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.OnLifecycleEvent;
import com.android.wallpaper.R;
import com.android.wallpaper.asset.Asset;
import com.android.wallpaper.asset.BitmapCachingAsset;
import com.android.wallpaper.module.CurrentWallpaperInfoFactory;
import com.android.wallpaper.module.InjectorProvider;
import com.android.wallpaper.module.UserEventLogger;
import com.android.wallpaper.picker.CategorySelectorFragment;
import com.android.wallpaper.picker.MyPhotosStarter;
import com.android.wallpaper.picker.WallpaperSectionView;
import com.android.wallpaper.picker.WorkspaceSurfaceHolderCallback;
import com.android.wallpaper.util.ResourceUtils;
import com.android.wallpaper.util.WallpaperConnection;
import com.android.wallpaper.util.WallpaperSurfaceCallback;
import com.android.wallpaper.widget.LockScreenPreviewer;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
/** The class to control the wallpaper section view. */
public class WallpaperSectionController implements
CustomizationSectionController<WallpaperSectionView>,
LifecycleObserver {
private static final String PERMISSION_READ_WALLPAPER_INTERNAL =
"android.permission.READ_WALLPAPER_INTERNAL";
private static final int SETTINGS_APP_INFO_REQUEST_CODE = 1;
private CardView mHomePreviewCard;
private ContentLoadingProgressBar mHomePreviewProgress;
private SurfaceView mWorkspaceSurface;
private WorkspaceSurfaceHolderCallback mWorkspaceSurfaceCallback;
private SurfaceView mHomeWallpaperSurface;
private WallpaperSurfaceCallback mHomeWallpaperSurfaceCallback;
private SurfaceView mLockWallpaperSurface;
private WallpaperSurfaceCallback mLockWallpaperSurfaceCallback;
private CardView mLockscreenPreviewCard;
private ViewGroup mLockPreviewContainer;
private ContentLoadingProgressBar mLockscreenPreviewProgress;
private WallpaperConnection mWallpaperConnection;
// The wallpaper information which is currently shown on the home preview.
private WallpaperInfo mHomePreviewWallpaperInfo;
// The wallpaper information which is currently shown on the lock preview.
private WallpaperInfo mLockPreviewWallpaperInfo;
private LockScreenPreviewer mLockScreenPreviewer;
private final Activity mActivity;
private final Context mAppContext;
private final LifecycleOwner mLifecycleOwner;
private final PermissionRequester mPermissionRequester;
private final WallpaperColorsViewModel mWallpaperColorsViewModel;
private final WorkspaceViewModel mWorkspaceViewModel;
private final CustomizationSectionNavigationController mSectionNavigationController;
private final WallpaperPreviewNavigator mWallpaperPreviewNavigator;
private final Bundle mSavedInstanceState;
public WallpaperSectionController(Activity activity, LifecycleOwner lifecycleOwner,
PermissionRequester permissionRequester, WallpaperColorsViewModel colorsViewModel,
WorkspaceViewModel workspaceViewModel,
CustomizationSectionNavigationController sectionNavigationController,
WallpaperPreviewNavigator wallpaperPreviewNavigator,
Bundle savedInstanceState) {
mActivity = activity;
mLifecycleOwner = lifecycleOwner;
mPermissionRequester = permissionRequester;
mAppContext = mActivity.getApplicationContext();
mWallpaperColorsViewModel = colorsViewModel;
mWorkspaceViewModel = workspaceViewModel;
mSectionNavigationController = sectionNavigationController;
mWallpaperPreviewNavigator = wallpaperPreviewNavigator;
mSavedInstanceState = savedInstanceState;
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
@MainThread
public void onResume() {
refreshCurrentWallpapers(/* forceRefresh= */ mSavedInstanceState == null);
if (mWallpaperConnection != null) {
mWallpaperConnection.setVisibility(true);
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
@MainThread
public void onPause() {
if (mWallpaperConnection != null) {
mWallpaperConnection.setVisibility(false);
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
@MainThread
public void onStop() {
if (mWallpaperConnection != null) {
mWallpaperConnection.disconnect();
mWallpaperConnection = null;
}
}
@Override
public boolean isAvailable(Context context) {
return true;
}
@Override
public WallpaperSectionView createView(Context context) {
WallpaperSectionView wallpaperSectionView = (WallpaperSectionView) LayoutInflater.from(
context).inflate(R.layout.wallpaper_section_view, /* root= */ null);
mHomePreviewCard = wallpaperSectionView.findViewById(R.id.home_preview);
mHomePreviewCard.setContentDescription(mAppContext.getString(
R.string.wallpaper_preview_card_content_description));
mWorkspaceSurface = mHomePreviewCard.findViewById(R.id.workspace_surface);
mHomePreviewProgress = mHomePreviewCard.findViewById(R.id.wallpaper_preview_spinner);
mWorkspaceSurfaceCallback = new WorkspaceSurfaceHolderCallback(
mWorkspaceSurface, mAppContext);
mHomeWallpaperSurface = mHomePreviewCard.findViewById(R.id.wallpaper_surface);
Future<Integer> placeholderColor = CompletableFuture.completedFuture(
ResourceUtils.getColorAttr(mActivity, android.R.attr.colorSecondary));
mHomeWallpaperSurfaceCallback = new WallpaperSurfaceCallback(mActivity, mHomePreviewCard,
mHomeWallpaperSurface, placeholderColor, () -> {
if (mHomePreviewWallpaperInfo != null) {
maybeLoadThumbnail(mHomePreviewWallpaperInfo, mHomeWallpaperSurfaceCallback);
}
});
mLockscreenPreviewCard = wallpaperSectionView.findViewById(R.id.lock_preview);
mLockscreenPreviewCard.setContentDescription(mAppContext.getString(
R.string.lockscreen_wallpaper_preview_card_content_description));
mLockscreenPreviewProgress = mLockscreenPreviewCard.findViewById(
R.id.wallpaper_preview_spinner);
mLockscreenPreviewCard.findViewById(R.id.workspace_surface).setVisibility(View.GONE);
mLockWallpaperSurface = mLockscreenPreviewCard.findViewById(R.id.wallpaper_surface);
mLockWallpaperSurfaceCallback = new WallpaperSurfaceCallback(mActivity,
mLockscreenPreviewCard, mLockWallpaperSurface, placeholderColor, () -> {
if (mLockPreviewWallpaperInfo != null) {
maybeLoadThumbnail(mLockPreviewWallpaperInfo, mLockWallpaperSurfaceCallback);
}
});
mLockPreviewContainer = mLockscreenPreviewCard.findViewById(
R.id.lock_screen_preview_container);
mLockPreviewContainer.setVisibility(View.INVISIBLE);
mLockScreenPreviewer = new LockScreenPreviewer(mLifecycleOwner.getLifecycle(), context,
mLockPreviewContainer);
setupCurrentWallpaperPreview(wallpaperSectionView);
final int shortDuration = mAppContext.getResources().getInteger(
android.R.integer.config_shortAnimTime);
fadeWallpaperPreview(true, shortDuration);
mLifecycleOwner.getLifecycle().addObserver(this);
updateWallpaperSurface();
updateWorkspaceSurface();
wallpaperSectionView.findViewById(R.id.wallpaper_picker_entry).setOnClickListener(
v -> mSectionNavigationController.navigateTo(new CategorySelectorFragment()));
mWorkspaceViewModel.getUpdateWorkspace().observe(mLifecycleOwner, update ->
updateWorkspacePreview(mWorkspaceSurface, mWorkspaceSurfaceCallback,
mWallpaperColorsViewModel.getHomeWallpaperColors().getValue())
);
return wallpaperSectionView;
}
private void updateWorkspacePreview(SurfaceView workspaceSurface,
WorkspaceSurfaceHolderCallback callback, @Nullable WallpaperColors colors) {
// Reattach SurfaceView to trigger #surfaceCreated to update preview for different option.
ViewGroup parent = (ViewGroup) workspaceSurface.getParent();
int viewIndex = parent.indexOfChild(workspaceSurface);
parent.removeView(workspaceSurface);
if (callback != null) {
callback.resetLastSurface();
callback.setWallpaperColors(colors);
}
parent.addView(workspaceSurface, viewIndex);
}
@Override
public void release() {
if (mLockScreenPreviewer != null) {
mLockScreenPreviewer.release();
mLockScreenPreviewer = null;
}
if (mHomeWallpaperSurfaceCallback != null) {
mHomeWallpaperSurfaceCallback.cleanUp();
}
if (mLockWallpaperSurfaceCallback != null) {
mLockWallpaperSurfaceCallback.cleanUp();
}
if (mWorkspaceSurfaceCallback != null) {
mWorkspaceSurfaceCallback.cleanUp();
}
mLifecycleOwner.getLifecycle().removeObserver(this);
}
private void setupCurrentWallpaperPreview(View rootView) {
if (canShowCurrentWallpaper()) {
showCurrentWallpaper(rootView, true);
} else {
showCurrentWallpaper(rootView, false);
Button mAllowAccessButton = rootView
.findViewById(R.id.permission_needed_allow_access_button);
mAllowAccessButton.setOnClickListener(view ->
mPermissionRequester.requestExternalStoragePermission(
new MyPhotosStarter.PermissionChangedListener() {
@Override
public void onPermissionsGranted() {
showCurrentWallpaper(rootView, true);
}
@Override
public void onPermissionsDenied(boolean dontAskAgain) {
if (!dontAskAgain) {
return;
}
showPermissionNeededDialog();
}
})
);
// Replace explanation text with text containing the Wallpapers app name which replaces
// the placeholder.
Resources resources = mAppContext.getResources();
String appName = resources.getString(R.string.app_name);
String explanation = resources.getString(R.string.permission_needed_explanation,
appName);
TextView explanationView = rootView.findViewById(R.id.permission_needed_explanation);
explanationView.setText(explanation);
}
}
private boolean canShowCurrentWallpaper() {
return isPermissionGranted(mAppContext, PERMISSION_READ_WALLPAPER_INTERNAL)
|| isPermissionGranted(mAppContext, READ_EXTERNAL_STORAGE);
}
private boolean isPermissionGranted(Context context, String permission) {
return context.getPackageManager().checkPermission(permission,
context.getPackageName()) == PackageManager.PERMISSION_GRANTED;
}
private void showCurrentWallpaper(View rootView, boolean show) {
rootView.findViewById(R.id.home_preview)
.setVisibility(show ? View.VISIBLE : View.GONE);
rootView.findViewById(R.id.lock_preview)
.setVisibility(show ? View.VISIBLE : View.GONE);
rootView.findViewById(R.id.permission_needed)
.setVisibility(show ? View.GONE : View.VISIBLE);
}
private void showPermissionNeededDialog() {
String permissionNeededMessage = mAppContext.getResources().getString(
R.string.permission_needed_explanation_go_to_settings);
AlertDialog dialog = new AlertDialog.Builder(mAppContext, R.style.LightDialogTheme)
.setMessage(permissionNeededMessage)
.setPositiveButton(android.R.string.ok, /* onClickListener= */ null)
.setNegativeButton(
R.string.settings_button_label,
(dialogInterface, i) -> {
Intent appInfoIntent = new Intent();
appInfoIntent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package",
mAppContext.getPackageName(), /* fragment= */ null);
appInfoIntent.setData(uri);
mActivity.startActivityForResult(appInfoIntent,
SETTINGS_APP_INFO_REQUEST_CODE);
})
.create();
dialog.show();
}
/**
* Obtains the {@link WallpaperInfo} object(s) representing the wallpaper(s) currently set to
* the device from the {@link CurrentWallpaperInfoFactory}.
*/
private void refreshCurrentWallpapers(boolean forceRefresh) {
CurrentWallpaperInfoFactory factory = InjectorProvider.getInjector()
.getCurrentWallpaperFactory(mAppContext);
factory.createCurrentWallpaperInfos(
(homeWallpaper, lockWallpaper, presentationMode) -> {
// A config change may have destroyed the activity since the refresh
// started, so check for that.
if (!isActivityAlive()) {
return;
}
mHomePreviewWallpaperInfo = homeWallpaper;
mLockPreviewWallpaperInfo =
lockWallpaper == null ? homeWallpaper : lockWallpaper;
mHomePreviewWallpaperInfo.computePlaceholderColor(mAppContext);
if (lockWallpaper != null) {
lockWallpaper.computePlaceholderColor(mAppContext);
}
updatePreview(mHomePreviewWallpaperInfo, true);
updatePreview(mLockPreviewWallpaperInfo, false);
WallpaperManager manager = WallpaperManager.getInstance(mAppContext);
WallpaperColors homeColors =
manager.getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
onHomeWallpaperColorsChanged(homeColors);
WallpaperColors lockColors = homeColors;
if (lockWallpaper != null) {
lockColors = manager.getWallpaperColors(WallpaperManager.FLAG_LOCK);
}
onLockWallpaperColorsChanged(lockColors);
}, forceRefresh);
}
private void updatePreview(WallpaperInfo wallpaperInfo, boolean isHomeWallpaper) {
if (wallpaperInfo == null) {
return;
}
if (!isActivityAlive()) {
return;
}
UserEventLogger eventLogger = InjectorProvider.getInjector().getUserEventLogger(
mAppContext);
WallpaperSurfaceCallback surfaceCallback = isHomeWallpaper
? mHomeWallpaperSurfaceCallback : mLockWallpaperSurfaceCallback;
// Load thumb regardless of live wallpaper to make sure we have a placeholder while
// the live wallpaper initializes in that case.
maybeLoadThumbnail(wallpaperInfo, surfaceCallback);
if (isHomeWallpaper) {
if (mWallpaperConnection != null) {
mWallpaperConnection.disconnect();
mWallpaperConnection = null;
}
if (wallpaperInfo instanceof LiveWallpaperInfo) {
setUpLiveWallpaperPreview(wallpaperInfo);
}
}
View preview = isHomeWallpaper ? mHomePreviewCard : mLockscreenPreviewCard;
preview.setOnClickListener(view -> {
mWallpaperPreviewNavigator.showViewOnlyPreview(wallpaperInfo, isHomeWallpaper);
eventLogger.logCurrentWallpaperPreviewed();
});
}
@NonNull
private Asset maybeLoadThumbnail(WallpaperInfo wallpaperInfo,
WallpaperSurfaceCallback surfaceCallback) {
ImageView imageView = surfaceCallback.getHomeImageWallpaper();
Asset thumbAsset = new BitmapCachingAsset(mAppContext,
wallpaperInfo.getThumbAsset(mAppContext));
if (imageView != null && imageView.getDrawable() == null) {
thumbAsset.loadPreviewImage(mActivity, imageView,
ResourceUtils.getColorAttr(mActivity, android.R.attr.colorSecondary));
}
return thumbAsset;
}
private void onHomeWallpaperColorsChanged(WallpaperColors wallpaperColors) {
if (wallpaperColors != null && wallpaperColors.equals(
mWallpaperColorsViewModel.getHomeWallpaperColors().getValue())) {
return;
}
mWallpaperColorsViewModel.getHomeWallpaperColors().setValue(wallpaperColors);
}
private void onLockWallpaperColorsChanged(WallpaperColors wallpaperColors) {
if (wallpaperColors != null && wallpaperColors.equals(
mWallpaperColorsViewModel.getLockWallpaperColors().getValue())) {
return;
}
mWallpaperColorsViewModel.getLockWallpaperColors().setValue(wallpaperColors);
if (mLockScreenPreviewer != null) {
mLockScreenPreviewer.setColor(wallpaperColors);
}
}
private void setUpLiveWallpaperPreview(WallpaperInfo homeWallpaper) {
if (!isActivityAlive()) {
return;
}
if (WallpaperConnection.isPreviewAvailable()) {
final boolean isLockLive = mLockPreviewWallpaperInfo instanceof LiveWallpaperInfo;
mWallpaperConnection = new WallpaperConnection(
getWallpaperIntent(homeWallpaper.getWallpaperComponent()), mActivity,
new WallpaperConnection.WallpaperConnectionListener() {
@Override
public void onWallpaperColorsChanged(WallpaperColors colors,
int displayId) {
if (isLockLive && mLockScreenPreviewer != null) {
mLockScreenPreviewer.setColor(colors);
onLockWallpaperColorsChanged(colors);
}
onHomeWallpaperColorsChanged(colors);
}
},
mHomeWallpaperSurface, isLockLive ? mLockWallpaperSurface : null);
mWallpaperConnection.setVisibility(true);
mHomeWallpaperSurface.post(() -> {
if (mWallpaperConnection != null && !mWallpaperConnection.connect()) {
mWallpaperConnection = null;
}
});
}
}
private Intent getWallpaperIntent(android.app.WallpaperInfo info) {
return new Intent(WallpaperService.SERVICE_INTERFACE)
.setClassName(info.getPackageName(), info.getServiceName());
}
private void updateWallpaperSurface() {
mHomeWallpaperSurface.getHolder().addCallback(mHomeWallpaperSurfaceCallback);
mHomeWallpaperSurface.setZOrderMediaOverlay(true);
mLockWallpaperSurface.getHolder().addCallback(mLockWallpaperSurfaceCallback);
mLockWallpaperSurface.setZOrderMediaOverlay(true);
}
private void updateWorkspaceSurface() {
mWorkspaceSurface.setZOrderMediaOverlay(true);
mWorkspaceSurface.getHolder().addCallback(mWorkspaceSurfaceCallback);
}
private boolean isActivityAlive() {
return !mActivity.isDestroyed() && !mActivity.isFinishing();
}
private void fadeWallpaperPreview(boolean isFadeIn, int duration) {
setupFade(mHomePreviewCard, mHomePreviewProgress, duration, isFadeIn);
setupFade(mLockscreenPreviewCard, mLockscreenPreviewProgress, duration, isFadeIn);
}
private void setupFade(CardView cardView, ContentLoadingProgressBar progressBar, int duration,
boolean fadeIn) {
cardView.setAlpha(fadeIn ? 0.0f : 1.0f);
cardView.animate()
.alpha(fadeIn ? 1.0f : 0.0f)
.setDuration(duration)
.setListener(new Animator.AnimatorListener() {
@Override
public void onAnimationCancel(Animator animator) {
progressBar.hide();
setWallpaperPreviewsVisibility(View.VISIBLE);
}
@Override
public void onAnimationEnd(Animator animator) {
progressBar.hide();
setWallpaperPreviewsVisibility(View.VISIBLE);
}
@Override
public void onAnimationRepeat(Animator animator) {
}
@Override
public void onAnimationStart(Animator animator) {
setWallpaperPreviewsVisibility(View.INVISIBLE);
}
});
progressBar.animate()
.alpha(fadeIn ? 1.0f : 0.0f)
.setDuration(duration * 2)
.setStartDelay(duration)
.withStartAction(progressBar::show)
.withEndAction(progressBar::hide);
}
private void setWallpaperPreviewsVisibility(int visibility) {
if (mHomeWallpaperSurface != null) {
mHomeWallpaperSurface.setVisibility(visibility);
}
if (mLockWallpaperSurface != null) {
mLockWallpaperSurface.setVisibility(visibility);
}
if (mWorkspaceSurface != null) {
mWorkspaceSurface.setVisibility(visibility);
}
if (mLockPreviewContainer != null) {
mLockPreviewContainer.setVisibility(visibility);
}
}
@Override
public void onTransitionOut() {
if (mHomeWallpaperSurface != null) {
mHomeWallpaperSurface.setUseAlpha();
mHomeWallpaperSurface.setAlpha(0f);
}
if (mLockWallpaperSurface != null) {
mLockWallpaperSurface.setUseAlpha();
mLockWallpaperSurface.setAlpha(0f);
}
if (mWorkspaceSurface != null) {
mWorkspaceSurface.setUseAlpha();
mWorkspaceSurface.setAlpha(0f);
}
if (mLockPreviewContainer != null) {
mLockPreviewContainer.setAlpha(0f);
}
}
}