/* * 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 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 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; } } }