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