1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.wallpaper.picker; 17 18 import android.Manifest.permission; 19 import android.app.Activity; 20 import android.app.WallpaperManager; 21 import android.content.Intent; 22 import android.content.pm.PackageManager; 23 import android.net.Uri; 24 import android.os.Build.VERSION; 25 import android.os.Build.VERSION_CODES; 26 import android.service.wallpaper.WallpaperService; 27 28 import androidx.annotation.NonNull; 29 import androidx.annotation.Nullable; 30 import androidx.fragment.app.FragmentActivity; 31 32 import com.android.wallpaper.R; 33 import com.android.wallpaper.compat.WallpaperManagerCompat; 34 import com.android.wallpaper.model.Category; 35 import com.android.wallpaper.model.CategoryProvider; 36 import com.android.wallpaper.model.CategoryReceiver; 37 import com.android.wallpaper.model.ImageWallpaperInfo; 38 import com.android.wallpaper.model.InlinePreviewIntentFactory; 39 import com.android.wallpaper.model.WallpaperInfo; 40 import com.android.wallpaper.module.FormFactorChecker; 41 import com.android.wallpaper.module.FormFactorChecker.FormFactor; 42 import com.android.wallpaper.module.Injector; 43 import com.android.wallpaper.module.InjectorProvider; 44 import com.android.wallpaper.module.PackageStatusNotifier; 45 import com.android.wallpaper.module.PackageStatusNotifier.PackageStatus; 46 import com.android.wallpaper.module.WallpaperPersister; 47 import com.android.wallpaper.module.WallpaperPreferences; 48 import com.android.wallpaper.picker.PreviewActivity.PreviewActivityIntentFactory; 49 import com.android.wallpaper.picker.ViewOnlyPreviewActivity.ViewOnlyPreviewActivityIntentFactory; 50 import com.android.wallpaper.picker.WallpaperDisabledFragment.WallpaperSupportLevel; 51 import com.android.wallpaper.picker.individual.IndividualPickerActivity.IndividualPickerActivityIntentFactory; 52 53 import java.util.ArrayList; 54 import java.util.List; 55 56 /** 57 * Implements all the logic for handling a WallpaperPicker container Activity. 58 * @see TopLevelPickerActivity for usage details. 59 */ 60 public class WallpaperPickerDelegate implements MyPhotosStarter { 61 62 private final FragmentActivity mActivity; 63 private final WallpapersUiContainer mContainer; 64 public static final int SHOW_CATEGORY_REQUEST_CODE = 0; 65 public static final int PREVIEW_WALLPAPER_REQUEST_CODE = 1; 66 public static final int VIEW_ONLY_PREVIEW_WALLPAPER_REQUEST_CODE = 2; 67 public static final int READ_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE = 3; 68 public static final int PREVIEW_LIVE_WALLPAPER_REQUEST_CODE = 4; 69 70 private IndividualPickerActivityIntentFactory mPickerIntentFactory; 71 72 private InlinePreviewIntentFactory mPreviewIntentFactory; 73 private InlinePreviewIntentFactory mViewOnlyPreviewIntentFactory; 74 75 @FormFactor private int mFormFactor; 76 private WallpaperPreferences mPreferences; 77 private PackageStatusNotifier mPackageStatusNotifier; 78 79 private List<PermissionChangedListener> mPermissionChangedListeners; 80 private PackageStatusNotifier.Listener mLiveWallpaperStatusListener; 81 private PackageStatusNotifier.Listener mThirdPartyStatusListener; 82 private CategoryProvider mCategoryProvider; 83 private WallpaperPersister mWallpaperPersister; 84 private static final String READ_PERMISSION = permission.READ_EXTERNAL_STORAGE; 85 WallpaperPickerDelegate(WallpapersUiContainer container, FragmentActivity activity, Injector injector)86 public WallpaperPickerDelegate(WallpapersUiContainer container, FragmentActivity activity, 87 Injector injector) { 88 mContainer = container; 89 mActivity = activity; 90 mPickerIntentFactory = new IndividualPickerActivityIntentFactory(); 91 mPreviewIntentFactory = new PreviewActivityIntentFactory(); 92 mViewOnlyPreviewIntentFactory = 93 new ViewOnlyPreviewActivityIntentFactory(); 94 95 mCategoryProvider = injector.getCategoryProvider(activity); 96 mPreferences = injector.getPreferences(activity); 97 98 mPackageStatusNotifier = injector.getPackageStatusNotifier(activity); 99 mWallpaperPersister = injector.getWallpaperPersister(activity); 100 final FormFactorChecker formFactorChecker = injector.getFormFactorChecker(activity); 101 mFormFactor = formFactorChecker.getFormFactor(); 102 103 mPermissionChangedListeners = new ArrayList<>(); 104 } 105 initialize(boolean forceCategoryRefresh)106 public void initialize(boolean forceCategoryRefresh) { 107 populateCategories(forceCategoryRefresh); 108 mLiveWallpaperStatusListener = this::updateLiveWallpapersCategories; 109 mThirdPartyStatusListener = this::updateThirdPartyCategories; 110 mPackageStatusNotifier.addListener( 111 mLiveWallpaperStatusListener, 112 WallpaperService.SERVICE_INTERFACE); 113 mPackageStatusNotifier.addListener(mThirdPartyStatusListener, Intent.ACTION_SET_WALLPAPER); 114 } 115 116 @Override requestCustomPhotoPicker(PermissionChangedListener listener)117 public void requestCustomPhotoPicker(PermissionChangedListener listener) { 118 if (!isReadExternalStoragePermissionGranted()) { 119 PermissionChangedListener wrappedListener = new PermissionChangedListener() { 120 @Override 121 public void onPermissionsGranted() { 122 listener.onPermissionsGranted(); 123 showCustomPhotoPicker(); 124 } 125 126 @Override 127 public void onPermissionsDenied(boolean dontAskAgain) { 128 listener.onPermissionsDenied(dontAskAgain); 129 } 130 }; 131 requestExternalStoragePermission(wrappedListener); 132 133 return; 134 } 135 136 showCustomPhotoPicker(); 137 } 138 139 /** 140 * Requests to show the Android custom photo picker for the sake of picking a 141 * photo to set as the device's wallpaper. 142 */ requestExternalStoragePermission(PermissionChangedListener listener)143 public void requestExternalStoragePermission(PermissionChangedListener listener) { 144 mPermissionChangedListeners.add(listener); 145 mActivity.requestPermissions( 146 new String[]{READ_PERMISSION}, 147 READ_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE); 148 } 149 150 /** 151 * Returns whether READ_EXTERNAL_STORAGE has been granted for the application. 152 */ isReadExternalStoragePermissionGranted()153 public boolean isReadExternalStoragePermissionGranted() { 154 return mActivity.getPackageManager().checkPermission( 155 permission.READ_EXTERNAL_STORAGE, 156 mActivity.getPackageName()) == PackageManager.PERMISSION_GRANTED; 157 } 158 showCustomPhotoPicker()159 private void showCustomPhotoPicker() { 160 Intent intent = new Intent(Intent.ACTION_PICK); 161 intent.setType("image/*"); 162 mActivity.startActivityForResult(intent, SHOW_CATEGORY_REQUEST_CODE); 163 } 164 updateThirdPartyCategories(String packageName, @PackageStatus int status)165 private void updateThirdPartyCategories(String packageName, @PackageStatus int status) { 166 if (status == PackageStatus.ADDED) { 167 mCategoryProvider.fetchCategories(new CategoryReceiver() { 168 @Override 169 public void onCategoryReceived(Category category) { 170 if (category.supportsThirdParty() && category.containsThirdParty(packageName)) { 171 addCategory(category, false); 172 } 173 } 174 175 @Override 176 public void doneFetchingCategories() { 177 // Do nothing here. 178 } 179 }, true); 180 } else if (status == PackageStatus.REMOVED) { 181 Category oldCategory = findThirdPartyCategory(packageName); 182 if (oldCategory != null) { 183 mCategoryProvider.fetchCategories(new CategoryReceiver() { 184 @Override 185 public void onCategoryReceived(Category category) { 186 // Do nothing here 187 } 188 189 @Override 190 public void doneFetchingCategories() { 191 removeCategory(oldCategory); 192 } 193 }, true); 194 } 195 } else { 196 // CHANGED package, let's reload all categories as we could have more or fewer now 197 populateCategories(true); 198 } 199 } 200 findThirdPartyCategory(String packageName)201 private Category findThirdPartyCategory(String packageName) { 202 int size = mCategoryProvider.getSize(); 203 for (int i = 0; i < size; i++) { 204 Category category = mCategoryProvider.getCategory(i); 205 if (category.supportsThirdParty() && category.containsThirdParty(packageName)) { 206 return category; 207 } 208 } 209 return null; 210 } 211 updateLiveWallpapersCategories(String packageName, @PackageStatus int status)212 private void updateLiveWallpapersCategories(String packageName, 213 @PackageStatus int status) { 214 String liveWallpaperCollectionId = mActivity.getString( 215 R.string.live_wallpaper_collection_id); 216 Category oldLiveWallpapersCategory = mCategoryProvider.getCategory( 217 liveWallpaperCollectionId); 218 if (status == PackageStatus.REMOVED 219 && (oldLiveWallpapersCategory == null 220 || !oldLiveWallpapersCategory.containsThirdParty(packageName))) { 221 // If we're removing a wallpaper and the live category didn't contain it already, 222 // there's nothing to do. 223 return; 224 } 225 mCategoryProvider.fetchCategories(new CategoryReceiver() { 226 @Override 227 public void onCategoryReceived(Category category) { 228 // Do nothing here 229 } 230 231 @Override 232 public void doneFetchingCategories() { 233 Category liveWallpapersCategory = 234 mCategoryProvider.getCategory(liveWallpaperCollectionId); 235 if (liveWallpapersCategory == null) { 236 // There are no more 3rd party live wallpapers, so the Category is gone. 237 removeCategory(oldLiveWallpapersCategory); 238 } else { 239 if (oldLiveWallpapersCategory != null) { 240 updateCategory(liveWallpapersCategory); 241 } else { 242 addCategory(liveWallpapersCategory, false); 243 } 244 } 245 } 246 }, true); 247 } 248 249 /** 250 * Populates the categories appropriately depending on the device form factor. 251 * 252 * @param forceRefresh Whether to force a refresh of categories from the 253 * CategoryProvider. True if 254 * on first launch. 255 */ populateCategories(boolean forceRefresh)256 public void populateCategories(boolean forceRefresh) { 257 258 final CategoryFragment categoryFragment = getCategoryPickerFragment(); 259 260 if (forceRefresh && categoryFragment != null) { 261 categoryFragment.clearCategories(); 262 } 263 264 mCategoryProvider.fetchCategories(new CategoryReceiver() { 265 @Override 266 public void onCategoryReceived(Category category) { 267 addCategory(category, true); 268 } 269 270 @Override 271 public void doneFetchingCategories() { 272 notifyDoneFetchingCategories(); 273 } 274 }, forceRefresh); 275 } 276 notifyDoneFetchingCategories()277 private void notifyDoneFetchingCategories() { 278 if (mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) { 279 CategoryFragment categoryFragment = getCategoryPickerFragment(); 280 if (categoryFragment != null) { 281 categoryFragment.doneFetchingCategories(); 282 } 283 } else { 284 mContainer.doneFetchingCategories(); 285 } 286 } 287 addCategory(Category category, boolean fetchingAll)288 public void addCategory(Category category, boolean fetchingAll) { 289 CategoryFragment categoryFragment = getCategoryPickerFragment(); 290 if (categoryFragment != null) { 291 categoryFragment.addCategory(category, fetchingAll); 292 } 293 } 294 removeCategory(Category category)295 public void removeCategory(Category category) { 296 CategoryFragment categoryFragment = getCategoryPickerFragment(); 297 if (categoryFragment != null) { 298 categoryFragment.removeCategory(category); 299 } 300 } 301 updateCategory(Category category)302 public void updateCategory(Category category) { 303 CategoryFragment categoryFragment = getCategoryPickerFragment(); 304 if (categoryFragment != null) { 305 categoryFragment.updateCategory(category); 306 } 307 } 308 309 @Nullable getCategoryPickerFragment()310 private CategoryFragment getCategoryPickerFragment() { 311 return mContainer.getCategoryFragment(); 312 } 313 314 /** 315 * Shows the view-only preview activity for the given wallpaper. 316 */ showViewOnlyPreview(WallpaperInfo wallpaperInfo)317 public void showViewOnlyPreview(WallpaperInfo wallpaperInfo) { 318 wallpaperInfo.showPreview( 319 mActivity, mViewOnlyPreviewIntentFactory, 320 VIEW_ONLY_PREVIEW_WALLPAPER_REQUEST_CODE); 321 } 322 323 /** 324 * Shows the picker activity for the given category. 325 */ show(String collectionId)326 public void show(String collectionId) { 327 Category category = findCategoryForCollectionId(collectionId); 328 if (category == null) { 329 return; 330 } 331 category.show(mActivity, mPickerIntentFactory, SHOW_CATEGORY_REQUEST_CODE); 332 } 333 334 @Nullable findCategoryForCollectionId(String collectionId)335 public Category findCategoryForCollectionId(String collectionId) { 336 return mCategoryProvider.getCategory(collectionId); 337 } 338 339 @WallpaperSupportLevel getWallpaperSupportLevel()340 public int getWallpaperSupportLevel() { 341 WallpaperManager wallpaperManager = WallpaperManager.getInstance(mActivity); 342 343 if (VERSION.SDK_INT >= VERSION_CODES.N) { 344 if (wallpaperManager.isWallpaperSupported()) { 345 return wallpaperManager.isSetWallpaperAllowed() 346 ? WallpaperDisabledFragment.SUPPORTED_CAN_SET 347 : WallpaperDisabledFragment.NOT_SUPPORTED_BLOCKED_BY_ADMIN; 348 } 349 return WallpaperDisabledFragment.NOT_SUPPORTED_BY_DEVICE; 350 } else if (VERSION.SDK_INT >= VERSION_CODES.M) { 351 return wallpaperManager.isWallpaperSupported() 352 ? WallpaperDisabledFragment.SUPPORTED_CAN_SET 353 : WallpaperDisabledFragment.NOT_SUPPORTED_BY_DEVICE; 354 } else { 355 WallpaperManagerCompat wallpaperManagerCompat = 356 InjectorProvider.getInjector().getWallpaperManagerCompat( 357 mActivity); 358 boolean isSupported = wallpaperManagerCompat.getDrawable() != null; 359 wallpaperManager.forgetLoadedWallpaper(); 360 return isSupported ? WallpaperDisabledFragment.SUPPORTED_CAN_SET 361 : WallpaperDisabledFragment.NOT_SUPPORTED_BY_DEVICE; 362 } 363 } 364 getPickerIntentFactory()365 public IndividualPickerActivityIntentFactory getPickerIntentFactory() { 366 return mPickerIntentFactory; 367 } 368 getPreviewIntentFactory()369 public InlinePreviewIntentFactory getPreviewIntentFactory() { 370 return mPreviewIntentFactory; 371 } 372 373 @FormFactor getFormFactor()374 public int getFormFactor() { 375 return mFormFactor; 376 } 377 getPreferences()378 public WallpaperPreferences getPreferences() { 379 return mPreferences; 380 } 381 getPermissionChangedListeners()382 public List<PermissionChangedListener> getPermissionChangedListeners() { 383 return mPermissionChangedListeners; 384 } 385 getCategoryProvider()386 public CategoryProvider getCategoryProvider() { 387 return mCategoryProvider; 388 } 389 390 /** 391 * Call when the owner activity is destroyed to clean up listeners. 392 */ cleanUp()393 public void cleanUp() { 394 if (mPackageStatusNotifier != null) { 395 mPackageStatusNotifier.removeListener(mLiveWallpaperStatusListener); 396 mPackageStatusNotifier.removeListener(mThirdPartyStatusListener); 397 } 398 } 399 400 /** 401 * Call from the Activity's onRequestPermissionsResult callback to handle permission request 402 * relevant to wallpapers (ie, READ_EXTERNAL_STORAGE) 403 * @see androidx.fragment.app.FragmentActivity#onRequestPermissionsResult(int, String[], int[]) 404 */ onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)405 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, 406 @NonNull int[] grantResults) { 407 if (requestCode == WallpaperPickerDelegate.READ_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE 408 && permissions.length > 0 409 && permissions[0].equals(READ_PERMISSION) 410 && grantResults.length > 0) { 411 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { 412 for (PermissionChangedListener listener : getPermissionChangedListeners()) { 413 listener.onPermissionsGranted(); 414 } 415 } else if (!mActivity.shouldShowRequestPermissionRationale(READ_PERMISSION)) { 416 for (PermissionChangedListener listener : getPermissionChangedListeners()) { 417 listener.onPermissionsDenied(true /* dontAskAgain */); 418 } 419 } else { 420 for (PermissionChangedListener listener :getPermissionChangedListeners()) { 421 listener.onPermissionsDenied(false /* dontAskAgain */); 422 } 423 } 424 } 425 getPermissionChangedListeners().clear(); 426 } 427 428 /** 429 * To be called from an Activity's onActivityResult method. 430 * Checks the result for ones that are handled by this delegate 431 * @return true if the intent was handled and calling Activity needs to finish with result 432 * OK, false otherwise. 433 */ handleActivityResult(int requestCode, int resultCode, Intent data)434 public boolean handleActivityResult(int requestCode, int resultCode, Intent data) { 435 if (resultCode != Activity.RESULT_OK) { 436 return false; 437 } 438 439 switch (requestCode) { 440 case SHOW_CATEGORY_REQUEST_CODE: 441 Uri imageUri = (data == null) ? null : data.getData(); 442 if (imageUri == null) { 443 // User finished viewing a category without any data, which implies that the 444 // user previewed and selected a wallpaper in-app, so finish this activity. 445 return true; 446 } 447 448 // User selected an image from the system picker, so launch the preview for that 449 // image. 450 ImageWallpaperInfo imageWallpaper = new ImageWallpaperInfo(imageUri); 451 452 imageWallpaper.showPreview(mActivity, getPreviewIntentFactory(), 453 PREVIEW_WALLPAPER_REQUEST_CODE); 454 return false; 455 case PREVIEW_WALLPAPER_REQUEST_CODE: 456 case PREVIEW_LIVE_WALLPAPER_REQUEST_CODE: 457 // User previewed and selected a wallpaper, so finish this activity. 458 mWallpaperPersister.onLiveWallpaperSet(); 459 return true; 460 default: 461 return false; 462 } 463 } 464 } 465