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