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