blob: b71990d2d1d27d770f88218dc5f0b6977d7d3744 [file] [log] [blame]
/*
* Copyright (C) 2018 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.picker;
import android.Manifest.permission;
import android.app.Activity;
import android.app.WallpaperManager;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.service.wallpaper.WallpaperService;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import com.android.wallpaper.R;
import com.android.wallpaper.model.Category;
import com.android.wallpaper.model.CategoryProvider;
import com.android.wallpaper.model.CategoryReceiver;
import com.android.wallpaper.model.ImageWallpaperInfo;
import com.android.wallpaper.model.WallpaperInfo;
import com.android.wallpaper.module.Injector;
import com.android.wallpaper.module.InjectorProvider;
import com.android.wallpaper.module.PackageStatusNotifier;
import com.android.wallpaper.module.PackageStatusNotifier.PackageStatus;
import com.android.wallpaper.module.WallpaperPersister;
import com.android.wallpaper.module.WallpaperPreferences;
import com.android.wallpaper.picker.WallpaperDisabledFragment.WallpaperSupportLevel;
import java.util.ArrayList;
import java.util.List;
/**
* Implements all the logic for handling a WallpaperPicker container Activity.
* @see CustomizationPickerActivity for usage details.
*/
public class WallpaperPickerDelegate implements MyPhotosStarter {
private static final String TAG = "WallpaperPickerDelegate";
private final FragmentActivity mActivity;
private final WallpapersUiContainer mContainer;
public static boolean DISABLE_MY_PHOTOS_BLOCK_PREVIEW = false;
public static final int SHOW_CATEGORY_REQUEST_CODE = 0;
public static final int PREVIEW_WALLPAPER_REQUEST_CODE = 1;
public static final int VIEW_ONLY_PREVIEW_WALLPAPER_REQUEST_CODE = 2;
public static final int READ_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE = 3;
public static final int PREVIEW_LIVE_WALLPAPER_REQUEST_CODE = 4;
public static final String IS_LIVE_WALLPAPER = "isLiveWallpaper";
private final MyPhotosIntentProvider mMyPhotosIntentProvider;
private WallpaperPreferences mPreferences;
private PackageStatusNotifier mPackageStatusNotifier;
private List<PermissionChangedListener> mPermissionChangedListeners;
private PackageStatusNotifier.Listener mLiveWallpaperStatusListener;
private PackageStatusNotifier.Listener mThirdPartyStatusListener;
private PackageStatusNotifier.Listener mDownloadableWallpaperStatusListener;
private String mDownloadableIntentAction;
private CategoryProvider mCategoryProvider;
private WallpaperPersister mWallpaperPersister;
private static final String READ_IMAGE_PERMISSION = permission.READ_MEDIA_IMAGES;
public WallpaperPickerDelegate(WallpapersUiContainer container, FragmentActivity activity,
Injector injector) {
mContainer = container;
mActivity = activity;
mCategoryProvider = injector.getCategoryProvider(activity);
mPreferences = injector.getPreferences(activity);
mPackageStatusNotifier = injector.getPackageStatusNotifier(activity);
mWallpaperPersister = injector.getWallpaperPersister(activity);
mPermissionChangedListeners = new ArrayList<>();
mDownloadableIntentAction = injector.getDownloadableIntentAction();
mMyPhotosIntentProvider = injector.getMyPhotosIntentProvider();
}
public void initialize(boolean forceCategoryRefresh) {
populateCategories(forceCategoryRefresh);
mLiveWallpaperStatusListener = this::updateLiveWallpapersCategories;
mThirdPartyStatusListener = this::updateThirdPartyCategories;
mPackageStatusNotifier.addListener(
mLiveWallpaperStatusListener,
WallpaperService.SERVICE_INTERFACE);
mPackageStatusNotifier.addListener(mThirdPartyStatusListener, Intent.ACTION_SET_WALLPAPER);
if (mDownloadableIntentAction != null) {
mDownloadableWallpaperStatusListener = (packageName, status) -> {
if (status != PackageStatusNotifier.PackageStatus.REMOVED) {
populateCategories(/* forceRefresh= */ true);
}
};
mPackageStatusNotifier.addListener(
mDownloadableWallpaperStatusListener, mDownloadableIntentAction);
}
}
@Override
public void requestCustomPhotoPicker(PermissionChangedListener listener) {
//TODO (b/282073506): Figure out a better way to have better photos experience
if (DISABLE_MY_PHOTOS_BLOCK_PREVIEW) {
if (!isReadExternalStoragePermissionGranted()) {
PermissionChangedListener wrappedListener = new PermissionChangedListener() {
@Override
public void onPermissionsGranted() {
listener.onPermissionsGranted();
showCustomPhotoPicker();
}
@Override
public void onPermissionsDenied(boolean dontAskAgain) {
listener.onPermissionsDenied(dontAskAgain);
}
};
requestExternalStoragePermission(wrappedListener);
return;
}
}
showCustomPhotoPicker();
}
/**
* Requests to show the Android custom photo picker for the sake of picking a
* photo to set as the device's wallpaper.
*/
public void requestExternalStoragePermission(PermissionChangedListener listener) {
mPermissionChangedListeners.add(listener);
mActivity.requestPermissions(
new String[]{READ_IMAGE_PERMISSION},
READ_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
}
/**
* Returns whether READ_MEDIA_IMAGES has been granted for the application.
*/
public boolean isReadExternalStoragePermissionGranted() {
return mActivity.getPackageManager().checkPermission(
permission.READ_MEDIA_IMAGES,
mActivity.getPackageName()) == PackageManager.PERMISSION_GRANTED;
}
private void showCustomPhotoPicker() {
try {
Intent intent = mMyPhotosIntentProvider.getMyPhotosIntent(mActivity);
mActivity.startActivityForResult(intent, SHOW_CATEGORY_REQUEST_CODE);
} catch (ActivityNotFoundException e) {
Intent fallback = mMyPhotosIntentProvider.getFallbackIntent(mActivity);
if (fallback != null) {
Log.i(TAG, "Couldn't launch photo picker with main intent, trying with fallback");
mActivity.startActivityForResult(fallback, SHOW_CATEGORY_REQUEST_CODE);
} else {
Log.e(TAG,
"Couldn't launch photo picker with main intent and no fallback is "
+ "available");
throw e;
}
}
}
private void updateThirdPartyCategories(String packageName, @PackageStatus int status) {
if (status == PackageStatus.ADDED) {
mCategoryProvider.fetchCategories(new CategoryReceiver() {
@Override
public void onCategoryReceived(Category category) {
if (category.supportsThirdParty() && category.containsThirdParty(packageName)) {
addCategory(category, false);
}
}
@Override
public void doneFetchingCategories() {
// Do nothing here.
}
}, true);
} else if (status == PackageStatus.REMOVED) {
Category oldCategory = findThirdPartyCategory(packageName);
if (oldCategory != null) {
mCategoryProvider.fetchCategories(new CategoryReceiver() {
@Override
public void onCategoryReceived(Category category) {
// Do nothing here
}
@Override
public void doneFetchingCategories() {
removeCategory(oldCategory);
}
}, true);
}
} else {
// CHANGED package, let's reload all categories as we could have more or fewer now
populateCategories(/* forceRefresh= */ true);
}
}
private Category findThirdPartyCategory(String packageName) {
int size = mCategoryProvider.getSize();
for (int i = 0; i < size; i++) {
Category category = mCategoryProvider.getCategory(i);
if (category.supportsThirdParty() && category.containsThirdParty(packageName)) {
return category;
}
}
return null;
}
private void updateLiveWallpapersCategories(String packageName,
@PackageStatus int status) {
String liveWallpaperCollectionId = mActivity.getString(
R.string.live_wallpaper_collection_id);
Category oldLiveWallpapersCategory = mCategoryProvider.getCategory(
liveWallpaperCollectionId);
if (status == PackageStatus.REMOVED
&& (oldLiveWallpapersCategory == null
|| !oldLiveWallpapersCategory.containsThirdParty(packageName))) {
// If we're removing a wallpaper and the live category didn't contain it already,
// there's nothing to do.
return;
}
mCategoryProvider.fetchCategories(new CategoryReceiver() {
@Override
public void onCategoryReceived(Category category) {
// Do nothing here
}
@Override
public void doneFetchingCategories() {
Category liveWallpapersCategory =
mCategoryProvider.getCategory(liveWallpaperCollectionId);
if (liveWallpapersCategory == null) {
// There are no more 3rd party live wallpapers, so the Category is gone.
removeCategory(oldLiveWallpapersCategory);
} else {
if (oldLiveWallpapersCategory != null) {
updateCategory(liveWallpapersCategory);
} else {
addCategory(liveWallpapersCategory, false);
}
}
}
}, true);
}
/**
* Fetch the wallpaper categories but don't call any callbacks on the result, just so that
* they're cached when loading later.
*/
public void prefetchCategories() {
boolean forceRefresh = mCategoryProvider.resetIfNeeded();
mCategoryProvider.fetchCategories(new CategoryReceiver() {
@Override
public void onCategoryReceived(Category category) {
// Do nothing
}
@Override
public void doneFetchingCategories() {
// Do nothing
}
}, forceRefresh);
}
/**
* Populates the categories appropriately.
*
* @param forceRefresh Whether to force a refresh of categories from the
* CategoryProvider. True if
* on first launch.
*/
public void populateCategories(boolean forceRefresh) {
final CategorySelectorFragment categorySelectorFragment = getCategorySelectorFragment();
if (forceRefresh && categorySelectorFragment != null) {
categorySelectorFragment.clearCategories();
}
mCategoryProvider.fetchCategories(new CategoryReceiver() {
@Override
public void onCategoryReceived(Category category) {
addCategory(category, true);
}
@Override
public void doneFetchingCategories() {
notifyDoneFetchingCategories();
}
}, forceRefresh);
}
private void notifyDoneFetchingCategories() {
CategorySelectorFragment categorySelectorFragment = getCategorySelectorFragment();
if (categorySelectorFragment != null) {
categorySelectorFragment.doneFetchingCategories();
}
}
public void addCategory(Category category, boolean fetchingAll) {
CategorySelectorFragment categorySelectorFragment = getCategorySelectorFragment();
if (categorySelectorFragment != null) {
categorySelectorFragment.addCategory(category, fetchingAll);
}
}
public void removeCategory(Category category) {
CategorySelectorFragment categorySelectorFragment = getCategorySelectorFragment();
if (categorySelectorFragment != null) {
categorySelectorFragment.removeCategory(category);
}
}
public void updateCategory(Category category) {
CategorySelectorFragment categorySelectorFragment = getCategorySelectorFragment();
if (categorySelectorFragment != null) {
categorySelectorFragment.updateCategory(category);
}
}
@Nullable
private CategorySelectorFragment getCategorySelectorFragment() {
return mContainer.getCategorySelectorFragment();
}
/**
* Shows the view-only preview activity for the given wallpaper.
*/
public void showViewOnlyPreview(WallpaperInfo wallpaperInfo, boolean isAssetIdPresent) {
wallpaperInfo.showPreview(
mActivity, InjectorProvider.getInjector().getViewOnlyPreviewActivityIntentFactory(),
VIEW_ONLY_PREVIEW_WALLPAPER_REQUEST_CODE, isAssetIdPresent);
}
/**
* Shows the picker activity for the given category.
*/
public void show(String collectionId) {
Category category = findCategoryForCollectionId(collectionId);
if (category == null) {
return;
}
category.show(mActivity, SHOW_CATEGORY_REQUEST_CODE);
}
@Nullable
public Category findCategoryForCollectionId(String collectionId) {
return mCategoryProvider.getCategory(collectionId);
}
@WallpaperSupportLevel
public int getWallpaperSupportLevel() {
WallpaperManager wallpaperManager = WallpaperManager.getInstance(mActivity);
if (VERSION.SDK_INT >= VERSION_CODES.N) {
if (wallpaperManager.isWallpaperSupported()) {
return wallpaperManager.isSetWallpaperAllowed()
? WallpaperDisabledFragment.SUPPORTED_CAN_SET
: WallpaperDisabledFragment.NOT_SUPPORTED_BLOCKED_BY_ADMIN;
}
return WallpaperDisabledFragment.NOT_SUPPORTED_BY_DEVICE;
} else if (VERSION.SDK_INT >= VERSION_CODES.M) {
return wallpaperManager.isWallpaperSupported()
? WallpaperDisabledFragment.SUPPORTED_CAN_SET
: WallpaperDisabledFragment.NOT_SUPPORTED_BY_DEVICE;
} else {
boolean isSupported = WallpaperManager.getInstance(mActivity.getApplicationContext())
.getDrawable() != null;
wallpaperManager.forgetLoadedWallpaper();
return isSupported ? WallpaperDisabledFragment.SUPPORTED_CAN_SET
: WallpaperDisabledFragment.NOT_SUPPORTED_BY_DEVICE;
}
}
public WallpaperPreferences getPreferences() {
return mPreferences;
}
public List<PermissionChangedListener> getPermissionChangedListeners() {
return mPermissionChangedListeners;
}
public CategoryProvider getCategoryProvider() {
return mCategoryProvider;
}
/**
* Call when the owner activity is destroyed to clean up listeners.
*/
public void cleanUp() {
if (mPackageStatusNotifier != null) {
mPackageStatusNotifier.removeListener(mLiveWallpaperStatusListener);
mPackageStatusNotifier.removeListener(mThirdPartyStatusListener);
mPackageStatusNotifier.removeListener(mDownloadableWallpaperStatusListener);
}
}
/**
* Call from the Activity's onRequestPermissionsResult callback to handle permission request
* relevant to wallpapers (ie, READ_MEDIA_IMAGES)
* @see androidx.fragment.app.FragmentActivity#onRequestPermissionsResult(int, String[], int[])
*/
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
if (requestCode == WallpaperPickerDelegate.READ_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE
&& permissions.length > 0
&& permissions[0].equals(READ_IMAGE_PERMISSION)
&& grantResults.length > 0) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
for (PermissionChangedListener listener : getPermissionChangedListeners()) {
listener.onPermissionsGranted();
}
} else if (!mActivity.shouldShowRequestPermissionRationale(READ_IMAGE_PERMISSION)) {
for (PermissionChangedListener listener : getPermissionChangedListeners()) {
listener.onPermissionsDenied(true /* dontAskAgain */);
}
} else {
for (PermissionChangedListener listener :getPermissionChangedListeners()) {
listener.onPermissionsDenied(false /* dontAskAgain */);
}
}
}
getPermissionChangedListeners().clear();
}
/**
* To be called from an Activity's onActivityResult method.
* Checks the result for ones that are handled by this delegate
* @return true if the intent was handled and calling Activity needs to finish with result
* OK, false otherwise.
*/
public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != Activity.RESULT_OK) {
return false;
}
switch (requestCode) {
case SHOW_CATEGORY_REQUEST_CODE:
Uri imageUri = (data == null) ? null : data.getData();
if (imageUri == null) {
// User finished viewing a category without any data, which implies that the
// user previewed and selected a wallpaper in-app, so finish this activity.
return true;
}
// User selected an image from the system picker, so launch the preview for that
// image.
ImageWallpaperInfo imageWallpaper = new ImageWallpaperInfo(imageUri);
mWallpaperPersister.setWallpaperInfoInPreview(imageWallpaper);
imageWallpaper.showPreview(mActivity,
InjectorProvider.getInjector().getPreviewActivityIntentFactory(),
PREVIEW_WALLPAPER_REQUEST_CODE, true);
return false;
case PREVIEW_LIVE_WALLPAPER_REQUEST_CODE:
populateCategories(/* forceRefresh= */ true);
return true;
case VIEW_ONLY_PREVIEW_WALLPAPER_REQUEST_CODE:
return true;
case PREVIEW_WALLPAPER_REQUEST_CODE:
// User previewed and selected a wallpaper, so finish this activity.
if (data != null && data.getBooleanExtra(IS_LIVE_WALLPAPER, false)) {
populateCategories(/* forceRefresh= */ true);
}
return true;
default:
return false;
}
}
}