• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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