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 PackageStatusNotifier.Listener mDownloadableWallpaperStatusListener; 83 private String mDownloadableIntentAction; 84 private CategoryProvider mCategoryProvider; 85 private WallpaperPersister mWallpaperPersister; 86 private static final String READ_PERMISSION = permission.READ_EXTERNAL_STORAGE; 87 WallpaperPickerDelegate(WallpapersUiContainer container, FragmentActivity activity, Injector injector)88 public WallpaperPickerDelegate(WallpapersUiContainer container, FragmentActivity activity, 89 Injector injector) { 90 mContainer = container; 91 mActivity = activity; 92 mPickerIntentFactory = new IndividualPickerActivityIntentFactory(); 93 mPreviewIntentFactory = new PreviewActivityIntentFactory(); 94 mViewOnlyPreviewIntentFactory = 95 new ViewOnlyPreviewActivityIntentFactory(); 96 97 mCategoryProvider = injector.getCategoryProvider(activity); 98 mPreferences = injector.getPreferences(activity); 99 100 mPackageStatusNotifier = injector.getPackageStatusNotifier(activity); 101 mWallpaperPersister = injector.getWallpaperPersister(activity); 102 final FormFactorChecker formFactorChecker = injector.getFormFactorChecker(activity); 103 mFormFactor = formFactorChecker.getFormFactor(); 104 105 mPermissionChangedListeners = new ArrayList<>(); 106 mDownloadableIntentAction = injector.getDownloadableIntentAction(); 107 } 108 initialize(boolean forceCategoryRefresh)109 public void initialize(boolean forceCategoryRefresh) { 110 populateCategories(forceCategoryRefresh); 111 mLiveWallpaperStatusListener = this::updateLiveWallpapersCategories; 112 mThirdPartyStatusListener = this::updateThirdPartyCategories; 113 mPackageStatusNotifier.addListener( 114 mLiveWallpaperStatusListener, 115 WallpaperService.SERVICE_INTERFACE); 116 mPackageStatusNotifier.addListener(mThirdPartyStatusListener, Intent.ACTION_SET_WALLPAPER); 117 if (mDownloadableIntentAction != null) { 118 mDownloadableWallpaperStatusListener = (packageName, status) -> { 119 if (status != PackageStatusNotifier.PackageStatus.REMOVED) { 120 populateCategories(/* forceRefresh= */ true); 121 } 122 }; 123 mPackageStatusNotifier.addListener( 124 mDownloadableWallpaperStatusListener, mDownloadableIntentAction); 125 } 126 } 127 128 @Override requestCustomPhotoPicker(PermissionChangedListener listener)129 public void requestCustomPhotoPicker(PermissionChangedListener listener) { 130 if (!isReadExternalStoragePermissionGranted()) { 131 PermissionChangedListener wrappedListener = new PermissionChangedListener() { 132 @Override 133 public void onPermissionsGranted() { 134 listener.onPermissionsGranted(); 135 showCustomPhotoPicker(); 136 } 137 138 @Override 139 public void onPermissionsDenied(boolean dontAskAgain) { 140 listener.onPermissionsDenied(dontAskAgain); 141 } 142 }; 143 requestExternalStoragePermission(wrappedListener); 144 145 return; 146 } 147 148 showCustomPhotoPicker(); 149 } 150 151 /** 152 * Requests to show the Android custom photo picker for the sake of picking a 153 * photo to set as the device's wallpaper. 154 */ requestExternalStoragePermission(PermissionChangedListener listener)155 public void requestExternalStoragePermission(PermissionChangedListener listener) { 156 mPermissionChangedListeners.add(listener); 157 mActivity.requestPermissions( 158 new String[]{READ_PERMISSION}, 159 READ_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE); 160 } 161 162 /** 163 * Returns whether READ_EXTERNAL_STORAGE has been granted for the application. 164 */ isReadExternalStoragePermissionGranted()165 public boolean isReadExternalStoragePermissionGranted() { 166 return mActivity.getPackageManager().checkPermission( 167 permission.READ_EXTERNAL_STORAGE, 168 mActivity.getPackageName()) == PackageManager.PERMISSION_GRANTED; 169 } 170 showCustomPhotoPicker()171 private void showCustomPhotoPicker() { 172 Intent intent = new Intent(Intent.ACTION_PICK); 173 intent.setType("image/*"); 174 mActivity.startActivityForResult(intent, SHOW_CATEGORY_REQUEST_CODE); 175 } 176 updateThirdPartyCategories(String packageName, @PackageStatus int status)177 private void updateThirdPartyCategories(String packageName, @PackageStatus int status) { 178 if (status == PackageStatus.ADDED) { 179 mCategoryProvider.fetchCategories(new CategoryReceiver() { 180 @Override 181 public void onCategoryReceived(Category category) { 182 if (category.supportsThirdParty() && category.containsThirdParty(packageName)) { 183 addCategory(category, false); 184 } 185 } 186 187 @Override 188 public void doneFetchingCategories() { 189 // Do nothing here. 190 } 191 }, true); 192 } else if (status == PackageStatus.REMOVED) { 193 Category oldCategory = findThirdPartyCategory(packageName); 194 if (oldCategory != null) { 195 mCategoryProvider.fetchCategories(new CategoryReceiver() { 196 @Override 197 public void onCategoryReceived(Category category) { 198 // Do nothing here 199 } 200 201 @Override 202 public void doneFetchingCategories() { 203 removeCategory(oldCategory); 204 } 205 }, true); 206 } 207 } else { 208 // CHANGED package, let's reload all categories as we could have more or fewer now 209 populateCategories(/* forceRefresh= */ true); 210 } 211 } 212 findThirdPartyCategory(String packageName)213 private Category findThirdPartyCategory(String packageName) { 214 int size = mCategoryProvider.getSize(); 215 for (int i = 0; i < size; i++) { 216 Category category = mCategoryProvider.getCategory(i); 217 if (category.supportsThirdParty() && category.containsThirdParty(packageName)) { 218 return category; 219 } 220 } 221 return null; 222 } 223 updateLiveWallpapersCategories(String packageName, @PackageStatus int status)224 private void updateLiveWallpapersCategories(String packageName, 225 @PackageStatus int status) { 226 String liveWallpaperCollectionId = mActivity.getString( 227 R.string.live_wallpaper_collection_id); 228 Category oldLiveWallpapersCategory = mCategoryProvider.getCategory( 229 liveWallpaperCollectionId); 230 if (status == PackageStatus.REMOVED 231 && (oldLiveWallpapersCategory == null 232 || !oldLiveWallpapersCategory.containsThirdParty(packageName))) { 233 // If we're removing a wallpaper and the live category didn't contain it already, 234 // there's nothing to do. 235 return; 236 } 237 mCategoryProvider.fetchCategories(new CategoryReceiver() { 238 @Override 239 public void onCategoryReceived(Category category) { 240 // Do nothing here 241 } 242 243 @Override 244 public void doneFetchingCategories() { 245 Category liveWallpapersCategory = 246 mCategoryProvider.getCategory(liveWallpaperCollectionId); 247 if (liveWallpapersCategory == null) { 248 // There are no more 3rd party live wallpapers, so the Category is gone. 249 removeCategory(oldLiveWallpapersCategory); 250 } else { 251 if (oldLiveWallpapersCategory != null) { 252 updateCategory(liveWallpapersCategory); 253 } else { 254 addCategory(liveWallpapersCategory, false); 255 } 256 } 257 } 258 }, true); 259 } 260 261 /** 262 * Fetch the wallpaper categories but don't call any callbacks on the result, just so that 263 * they're cached when loading later. 264 */ prefetchCategories()265 public void prefetchCategories() { 266 boolean forceRefresh = mCategoryProvider.resetIfNeeded(); 267 mCategoryProvider.fetchCategories(new CategoryReceiver() { 268 @Override 269 public void onCategoryReceived(Category category) { 270 // Do nothing 271 } 272 273 @Override 274 public void doneFetchingCategories() { 275 // Do nothing 276 } 277 }, forceRefresh); 278 } 279 280 /** 281 * Populates the categories appropriately depending on the device form factor. 282 * 283 * @param forceRefresh Whether to force a refresh of categories from the 284 * CategoryProvider. True if 285 * on first launch. 286 */ populateCategories(boolean forceRefresh)287 public void populateCategories(boolean forceRefresh) { 288 289 final CategorySelectorFragment categorySelectorFragment = getCategorySelectorFragment(); 290 291 if (forceRefresh && categorySelectorFragment != null) { 292 categorySelectorFragment.clearCategories(); 293 } 294 295 mCategoryProvider.fetchCategories(new CategoryReceiver() { 296 @Override 297 public void onCategoryReceived(Category category) { 298 addCategory(category, true); 299 } 300 301 @Override 302 public void doneFetchingCategories() { 303 notifyDoneFetchingCategories(); 304 } 305 }, forceRefresh); 306 } 307 notifyDoneFetchingCategories()308 private void notifyDoneFetchingCategories() { 309 if (mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) { 310 CategorySelectorFragment categorySelectorFragment = getCategorySelectorFragment(); 311 if (categorySelectorFragment != null) { 312 categorySelectorFragment.doneFetchingCategories(); 313 } 314 } else { 315 mContainer.doneFetchingCategories(); 316 } 317 } 318 addCategory(Category category, boolean fetchingAll)319 public void addCategory(Category category, boolean fetchingAll) { 320 CategorySelectorFragment categorySelectorFragment = getCategorySelectorFragment(); 321 if (categorySelectorFragment != null) { 322 categorySelectorFragment.addCategory(category, fetchingAll); 323 } 324 } 325 removeCategory(Category category)326 public void removeCategory(Category category) { 327 CategorySelectorFragment categorySelectorFragment = getCategorySelectorFragment(); 328 if (categorySelectorFragment != null) { 329 categorySelectorFragment.removeCategory(category); 330 } 331 } 332 updateCategory(Category category)333 public void updateCategory(Category category) { 334 CategorySelectorFragment categorySelectorFragment = getCategorySelectorFragment(); 335 if (categorySelectorFragment != null) { 336 categorySelectorFragment.updateCategory(category); 337 } 338 } 339 340 @Nullable getCategorySelectorFragment()341 private CategorySelectorFragment getCategorySelectorFragment() { 342 return mContainer.getCategorySelectorFragment(); 343 } 344 345 /** 346 * Shows the view-only preview activity for the given wallpaper. 347 */ showViewOnlyPreview(WallpaperInfo wallpaperInfo, boolean isViewAsHome)348 public void showViewOnlyPreview(WallpaperInfo wallpaperInfo, boolean isViewAsHome) { 349 ((ViewOnlyPreviewActivityIntentFactory) mViewOnlyPreviewIntentFactory).setAsHomePreview( 350 /* isHomeAndLockPreviews= */ true, isViewAsHome); 351 wallpaperInfo.showPreview( 352 mActivity, mViewOnlyPreviewIntentFactory, 353 VIEW_ONLY_PREVIEW_WALLPAPER_REQUEST_CODE); 354 } 355 356 /** 357 * Shows the picker activity for the given category. 358 */ show(String collectionId)359 public void show(String collectionId) { 360 Category category = findCategoryForCollectionId(collectionId); 361 if (category == null) { 362 return; 363 } 364 category.show(mActivity, mPickerIntentFactory, SHOW_CATEGORY_REQUEST_CODE); 365 } 366 367 @Nullable findCategoryForCollectionId(String collectionId)368 public Category findCategoryForCollectionId(String collectionId) { 369 return mCategoryProvider.getCategory(collectionId); 370 } 371 372 @WallpaperSupportLevel getWallpaperSupportLevel()373 public int getWallpaperSupportLevel() { 374 WallpaperManager wallpaperManager = WallpaperManager.getInstance(mActivity); 375 376 if (VERSION.SDK_INT >= VERSION_CODES.N) { 377 if (wallpaperManager.isWallpaperSupported()) { 378 return wallpaperManager.isSetWallpaperAllowed() 379 ? WallpaperDisabledFragment.SUPPORTED_CAN_SET 380 : WallpaperDisabledFragment.NOT_SUPPORTED_BLOCKED_BY_ADMIN; 381 } 382 return WallpaperDisabledFragment.NOT_SUPPORTED_BY_DEVICE; 383 } else if (VERSION.SDK_INT >= VERSION_CODES.M) { 384 return wallpaperManager.isWallpaperSupported() 385 ? WallpaperDisabledFragment.SUPPORTED_CAN_SET 386 : WallpaperDisabledFragment.NOT_SUPPORTED_BY_DEVICE; 387 } else { 388 WallpaperManagerCompat wallpaperManagerCompat = 389 InjectorProvider.getInjector().getWallpaperManagerCompat( 390 mActivity); 391 boolean isSupported = wallpaperManagerCompat.getDrawable() != null; 392 wallpaperManager.forgetLoadedWallpaper(); 393 return isSupported ? WallpaperDisabledFragment.SUPPORTED_CAN_SET 394 : WallpaperDisabledFragment.NOT_SUPPORTED_BY_DEVICE; 395 } 396 } 397 getPickerIntentFactory()398 public IndividualPickerActivityIntentFactory getPickerIntentFactory() { 399 return mPickerIntentFactory; 400 } 401 getPreviewIntentFactory()402 public InlinePreviewIntentFactory getPreviewIntentFactory() { 403 return mPreviewIntentFactory; 404 } 405 406 @FormFactor getFormFactor()407 public int getFormFactor() { 408 return mFormFactor; 409 } 410 getPreferences()411 public WallpaperPreferences getPreferences() { 412 return mPreferences; 413 } 414 getPermissionChangedListeners()415 public List<PermissionChangedListener> getPermissionChangedListeners() { 416 return mPermissionChangedListeners; 417 } 418 getCategoryProvider()419 public CategoryProvider getCategoryProvider() { 420 return mCategoryProvider; 421 } 422 423 /** 424 * Call when the owner activity is destroyed to clean up listeners. 425 */ cleanUp()426 public void cleanUp() { 427 if (mPackageStatusNotifier != null) { 428 mPackageStatusNotifier.removeListener(mLiveWallpaperStatusListener); 429 mPackageStatusNotifier.removeListener(mThirdPartyStatusListener); 430 mPackageStatusNotifier.removeListener(mDownloadableWallpaperStatusListener); 431 } 432 } 433 434 /** 435 * Call from the Activity's onRequestPermissionsResult callback to handle permission request 436 * relevant to wallpapers (ie, READ_EXTERNAL_STORAGE) 437 * @see androidx.fragment.app.FragmentActivity#onRequestPermissionsResult(int, String[], int[]) 438 */ onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)439 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, 440 @NonNull int[] grantResults) { 441 if (requestCode == WallpaperPickerDelegate.READ_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE 442 && permissions.length > 0 443 && permissions[0].equals(READ_PERMISSION) 444 && grantResults.length > 0) { 445 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { 446 for (PermissionChangedListener listener : getPermissionChangedListeners()) { 447 listener.onPermissionsGranted(); 448 } 449 } else if (!mActivity.shouldShowRequestPermissionRationale(READ_PERMISSION)) { 450 for (PermissionChangedListener listener : getPermissionChangedListeners()) { 451 listener.onPermissionsDenied(true /* dontAskAgain */); 452 } 453 } else { 454 for (PermissionChangedListener listener :getPermissionChangedListeners()) { 455 listener.onPermissionsDenied(false /* dontAskAgain */); 456 } 457 } 458 } 459 getPermissionChangedListeners().clear(); 460 } 461 462 /** 463 * To be called from an Activity's onActivityResult method. 464 * Checks the result for ones that are handled by this delegate 465 * @return true if the intent was handled and calling Activity needs to finish with result 466 * OK, false otherwise. 467 */ handleActivityResult(int requestCode, int resultCode, Intent data)468 public boolean handleActivityResult(int requestCode, int resultCode, Intent data) { 469 if (resultCode != Activity.RESULT_OK) { 470 return false; 471 } 472 473 switch (requestCode) { 474 case SHOW_CATEGORY_REQUEST_CODE: 475 Uri imageUri = (data == null) ? null : data.getData(); 476 if (imageUri == null) { 477 // User finished viewing a category without any data, which implies that the 478 // user previewed and selected a wallpaper in-app, so finish this activity. 479 return true; 480 } 481 482 // User selected an image from the system picker, so launch the preview for that 483 // image. 484 ImageWallpaperInfo imageWallpaper = new ImageWallpaperInfo(imageUri); 485 486 mWallpaperPersister.setWallpaperInfoInPreview(imageWallpaper); 487 imageWallpaper.showPreview(mActivity, getPreviewIntentFactory(), 488 PREVIEW_WALLPAPER_REQUEST_CODE); 489 return false; 490 case PREVIEW_LIVE_WALLPAPER_REQUEST_CODE: 491 mWallpaperPersister.onLiveWallpaperSet(); 492 populateCategories(/* forceRefresh= */ true); 493 // Fall through. 494 case VIEW_ONLY_PREVIEW_WALLPAPER_REQUEST_CODE: 495 // Fall through. 496 case PREVIEW_WALLPAPER_REQUEST_CODE: 497 // User previewed and selected a wallpaper, so finish this activity. 498 return true; 499 default: 500 return false; 501 } 502 } 503 } 504