blob: 732b1aebc5fb80c012dd430e95e137d89e1518f8 [file] [log] [blame]
Santiago Etchebehere635e96f2018-12-04 18:31:34 -08001/*
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 */
16package com.android.wallpaper.picker;
17
18import android.Manifest.permission;
Santiago Etchebeheree5e05842019-02-13 16:58:07 -080019import android.app.Activity;
Santiago Etchebehere635e96f2018-12-04 18:31:34 -080020import android.app.WallpaperManager;
21import android.content.Intent;
22import android.content.pm.PackageManager;
Santiago Etchebeheree5e05842019-02-13 16:58:07 -080023import android.net.Uri;
Santiago Etchebehere635e96f2018-12-04 18:31:34 -080024import android.os.Build.VERSION;
25import android.os.Build.VERSION_CODES;
26import android.service.wallpaper.WallpaperService;
27
28import androidx.annotation.NonNull;
29import androidx.annotation.Nullable;
Santiago Etchebeheree2317de2018-12-04 18:31:34 -080030import androidx.fragment.app.FragmentActivity;
Santiago Etchebehere635e96f2018-12-04 18:31:34 -080031
32import com.android.wallpaper.R;
33import com.android.wallpaper.compat.WallpaperManagerCompat;
34import com.android.wallpaper.model.Category;
35import com.android.wallpaper.model.CategoryProvider;
36import com.android.wallpaper.model.CategoryReceiver;
Santiago Etchebeheree5e05842019-02-13 16:58:07 -080037import com.android.wallpaper.model.ImageWallpaperInfo;
Santiago Etchebehere635e96f2018-12-04 18:31:34 -080038import com.android.wallpaper.model.InlinePreviewIntentFactory;
39import com.android.wallpaper.model.WallpaperInfo;
40import com.android.wallpaper.module.FormFactorChecker;
41import com.android.wallpaper.module.FormFactorChecker.FormFactor;
42import com.android.wallpaper.module.Injector;
43import com.android.wallpaper.module.InjectorProvider;
44import com.android.wallpaper.module.PackageStatusNotifier;
45import com.android.wallpaper.module.PackageStatusNotifier.PackageStatus;
46import com.android.wallpaper.module.WallpaperPreferences;
47import com.android.wallpaper.picker.PreviewActivity.PreviewActivityIntentFactory;
48import com.android.wallpaper.picker.ViewOnlyPreviewActivity.ViewOnlyPreviewActivityIntentFactory;
49import com.android.wallpaper.picker.WallpaperDisabledFragment.WallpaperSupportLevel;
50import com.android.wallpaper.picker.individual.IndividualPickerActivity.IndividualPickerActivityIntentFactory;
51
52import java.util.ArrayList;
53import java.util.List;
54
55/**
56 * Implements all the logic for handling a WallpaperPicker container Activity.
57 * @see TopLevelPickerActivity for usage details.
58 */
Santiago Etchebeherefab49612019-01-15 12:22:42 -080059public class WallpaperPickerDelegate implements MyPhotosStarter {
Santiago Etchebehere635e96f2018-12-04 18:31:34 -080060
Santiago Etchebeheree2317de2018-12-04 18:31:34 -080061 private final FragmentActivity mActivity;
Santiago Etchebehere635e96f2018-12-04 18:31:34 -080062 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;
Santiago Etchebehereb713ead2019-04-04 19:57:03 -030081 private static final String READ_PERMISSION = permission.READ_EXTERNAL_STORAGE;
Santiago Etchebehere635e96f2018-12-04 18:31:34 -080082
Santiago Etchebeheree2317de2018-12-04 18:31:34 -080083 public WallpaperPickerDelegate(WallpapersUiContainer container, FragmentActivity activity,
Santiago Etchebehere635e96f2018-12-04 18:31:34 -080084 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
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
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 */
139 public void requestExternalStoragePermission(PermissionChangedListener listener) {
140 mPermissionChangedListeners.add(listener);
141 mActivity.requestPermissions(
Santiago Etchebeheree5e05842019-02-13 16:58:07 -0800142 new String[]{READ_PERMISSION},
Santiago Etchebehere635e96f2018-12-04 18:31:34 -0800143 READ_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
144 }
145
146 /**
147 * Returns whether READ_EXTERNAL_STORAGE has been granted for the application.
148 */
149 public boolean isReadExternalStoragePermissionGranted() {
150 return mActivity.getPackageManager().checkPermission(
151 permission.READ_EXTERNAL_STORAGE,
152 mActivity.getPackageName()) == PackageManager.PERMISSION_GRANTED;
153 }
154
155 private void showCustomPhotoPicker() {
Wesley.CW Wangcf581882020-01-02 15:47:31 +0800156 Intent intent = new Intent(Intent.ACTION_PICK);
Santiago Etchebehere635e96f2018-12-04 18:31:34 -0800157 intent.setType("image/*");
158 mActivity.startActivityForResult(intent, SHOW_CATEGORY_REQUEST_CODE);
159 }
160
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
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
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 */
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
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
284 public void addCategory(Category category, boolean fetchingAll) {
285 CategoryFragment categoryFragment = getCategoryPickerFragment();
286 if (categoryFragment != null) {
287 categoryFragment.addCategory(category, fetchingAll);
288 }
289 }
290
291 public void removeCategory(Category category) {
292 CategoryFragment categoryFragment = getCategoryPickerFragment();
293 if (categoryFragment != null) {
294 categoryFragment.removeCategory(category);
295 }
296 }
297
298 public void updateCategory(Category category) {
299 CategoryFragment categoryFragment = getCategoryPickerFragment();
300 if (categoryFragment != null) {
301 categoryFragment.updateCategory(category);
302 }
303 }
304
305 @Nullable
306 private CategoryFragment getCategoryPickerFragment() {
307 return mContainer.getCategoryFragment();
308 }
309
310 /**
311 * Shows the view-only preview activity for the given wallpaper.
312 */
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 */
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
331 public Category findCategoryForCollectionId(String collectionId) {
332 return mCategoryProvider.getCategory(collectionId);
333 }
334
335 @WallpaperSupportLevel
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
361 public IndividualPickerActivityIntentFactory getPickerIntentFactory() {
362 return mPickerIntentFactory;
363 }
364
365 public InlinePreviewIntentFactory getPreviewIntentFactory() {
366 return mPreviewIntentFactory;
367 }
368
369 @FormFactor
370 public int getFormFactor() {
371 return mFormFactor;
372 }
373
374 public WallpaperPreferences getPreferences() {
375 return mPreferences;
376 }
377
378 public List<PermissionChangedListener> getPermissionChangedListeners() {
379 return mPermissionChangedListeners;
380 }
381
382 public CategoryProvider getCategoryProvider() {
383 return mCategoryProvider;
384 }
385
386 /**
387 * Call when the owner activity is destroyed to clean up listeners.
388 */
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 */
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
Santiago Etchebeheree5e05842019-02-13 16:58:07 -0800405 && permissions[0].equals(READ_PERMISSION)
Santiago Etchebehere635e96f2018-12-04 18:31:34 -0800406 && grantResults.length > 0) {
407 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
408 for (PermissionChangedListener listener : getPermissionChangedListeners()) {
409 listener.onPermissionsGranted();
410 }
Santiago Etchebeheree5e05842019-02-13 16:58:07 -0800411 } else if (!mActivity.shouldShowRequestPermissionRationale(READ_PERMISSION)) {
Santiago Etchebehere635e96f2018-12-04 18:31:34 -0800412 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 }
Santiago Etchebeheree5e05842019-02-13 16:58:07 -0800423
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 */
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 }
Santiago Etchebeheree2317de2018-12-04 18:31:34 -0800452}