• 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.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