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