1 /*
2  * Copyright (C) 2017 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 static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED;
19 import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED;
20 
21 import android.app.Activity;
22 import android.app.AlertDialog;
23 import android.app.WallpaperColors;
24 import android.content.Intent;
25 import android.content.pm.PackageManager;
26 import android.graphics.Rect;
27 import android.net.Uri;
28 import android.os.Bundle;
29 import android.provider.Settings;
30 import android.service.wallpaper.WallpaperService;
31 import android.text.TextUtils;
32 import android.view.LayoutInflater;
33 import android.view.MenuItem;
34 import android.view.SurfaceView;
35 import android.view.View;
36 import android.view.ViewGroup;
37 import android.widget.Button;
38 import android.widget.ImageView;
39 import android.widget.TextView;
40 
41 import androidx.annotation.NonNull;
42 import androidx.annotation.Nullable;
43 import androidx.cardview.widget.CardView;
44 import androidx.viewpager.widget.PagerAdapter;
45 import androidx.viewpager.widget.ViewPager;
46 
47 import com.android.wallpaper.R;
48 import com.android.wallpaper.model.Category;
49 import com.android.wallpaper.model.LiveWallpaperInfo;
50 import com.android.wallpaper.model.WallpaperCategory;
51 import com.android.wallpaper.model.WallpaperInfo;
52 import com.android.wallpaper.module.CurrentWallpaperInfoFactory;
53 import com.android.wallpaper.module.CurrentWallpaperInfoFactory.WallpaperInfoCallback;
54 import com.android.wallpaper.module.InjectorProvider;
55 import com.android.wallpaper.module.UserEventLogger;
56 import com.android.wallpaper.module.WallpaperPersister;
57 import com.android.wallpaper.module.WallpaperPreferences;
58 import com.android.wallpaper.module.WallpaperPreferences.PresentationMode;
59 import com.android.wallpaper.picker.CategorySelectorFragment.CategorySelectorFragmentHost;
60 import com.android.wallpaper.picker.MyPhotosStarter.MyPhotosStarterProvider;
61 import com.android.wallpaper.picker.MyPhotosStarter.PermissionChangedListener;
62 import com.android.wallpaper.picker.individual.IndividualPickerFragment;
63 import com.android.wallpaper.picker.individual.IndividualPickerFragment.ThumbnailUpdater;
64 import com.android.wallpaper.picker.individual.IndividualPickerFragment.WallpaperDestinationCallback;
65 import com.android.wallpaper.util.DeepLinkUtils;
66 import com.android.wallpaper.util.ResourceUtils;
67 import com.android.wallpaper.util.SizeCalculator;
68 import com.android.wallpaper.util.WallpaperConnection;
69 import com.android.wallpaper.util.WallpaperConnection.WallpaperConnectionListener;
70 import com.android.wallpaper.util.WallpaperSurfaceCallback;
71 import com.android.wallpaper.widget.LockScreenPreviewer;
72 import com.android.wallpaper.widget.PreviewPager;
73 import com.android.wallpaper.widget.WallpaperColorsLoader;
74 import com.android.wallpaper.widget.WallpaperPickerRecyclerViewAccessibilityDelegate;
75 
76 import com.bumptech.glide.Glide;
77 import com.bumptech.glide.MemoryCategory;
78 import com.google.android.material.bottomsheet.BottomSheetBehavior;
79 
80 import java.util.ArrayList;
81 import java.util.Collections;
82 import java.util.Date;
83 import java.util.List;
84 
85 /**
86  * Displays the Main UI for picking a category of wallpapers to choose from.
87  */
88 public class CategoryFragment extends AppbarFragment
89         implements CategorySelectorFragmentHost, ThumbnailUpdater, WallpaperDestinationCallback,
90         WallpaperPickerRecyclerViewAccessibilityDelegate.BottomSheetHost,
91         IndividualPickerFragment.IndividualPickerFragmentHost {
92 
93     /**
94      * Interface to be implemented by an Activity hosting a {@link CategoryFragment}
95      */
96     public interface CategoryFragmentHost extends MyPhotosStarterProvider {
97 
requestExternalStoragePermission(PermissionChangedListener listener)98         void requestExternalStoragePermission(PermissionChangedListener listener);
99 
isReadExternalStoragePermissionGranted()100         boolean isReadExternalStoragePermissionGranted();
101 
showViewOnlyPreview(WallpaperInfo wallpaperInfo, boolean isViewAsHome)102         void showViewOnlyPreview(WallpaperInfo wallpaperInfo, boolean isViewAsHome);
103 
show(String collectionId)104         void show(String collectionId);
105 
fetchCategories()106         void fetchCategories();
107 
cleanUp()108         void cleanUp();
109     }
110 
newInstance(CharSequence title)111     public static CategoryFragment newInstance(CharSequence title) {
112         CategoryFragment fragment = new CategoryFragment();
113         fragment.setArguments(AppbarFragment.createArguments(title));
114         return fragment;
115     }
116 
117     private static final String TAG = "CategoryFragment";
118 
119     private static final int SETTINGS_APP_INFO_REQUEST_CODE = 1;
120 
121     private static final String PERMISSION_READ_WALLPAPER_INTERNAL =
122             "android.permission.READ_WALLPAPER_INTERNAL";
123 
124     private SurfaceView mWorkspaceSurface;
125     private WorkspaceSurfaceHolderCallback mWorkspaceSurfaceCallback;
126     private SurfaceView mWallpaperSurface;
127     private WallpaperSurfaceCallback mWallpaperSurfaceCallback;
128     private PreviewPager mPreviewPager;
129     private List<View> mWallPaperPreviews;
130     private WallpaperConnection mWallpaperConnection;
131     private CategorySelectorFragment mCategorySelectorFragment;
132     private IndividualPickerFragment mIndividualPickerFragment;
133     private boolean mShowSelectedWallpaper;
134     private BottomSheetBehavior<View> mBottomSheetBehavior;
135     // The index of Destination#DEST_HOME_SCREEN or Destination#DEST_LOCK_SCREEN
136     private int mWallpaperIndex;
137 
138     // The wallpaper information which is currently shown on the home preview.
139     private WallpaperInfo mHomePreviewWallpaperInfo;
140     // The wallpaper information which is currently shown on the lock preview.
141     private WallpaperInfo mLockPreviewWallpaperInfo;
142 
143     private LockScreenPreviewer mLockScreenPreviewer;
144     private View mRootContainer;
145 
146     private final Rect mPreviewLocalRect = new Rect();
147     private final Rect mPreviewGlobalRect = new Rect();
148     private final int[] mLivePreviewLocation = new int[2];
149 
CategoryFragment()150     public CategoryFragment() {
151         mCategorySelectorFragment = new CategorySelectorFragment();
152     }
153 
154     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)155     public View onCreateView(LayoutInflater inflater, ViewGroup container,
156                              Bundle savedInstanceState) {
157         View view = inflater.inflate(
158                 R.layout.fragment_category_picker, container, /* attachToRoot= */ false);
159 
160         mWallPaperPreviews = new ArrayList<>();
161         CardView homePreviewCard = (CardView) inflater.inflate(
162                 R.layout.wallpaper_preview_card, null);
163         mWorkspaceSurface = homePreviewCard.findViewById(R.id.workspace_surface);
164         mWorkspaceSurfaceCallback = new WorkspaceSurfaceHolderCallback(
165                 mWorkspaceSurface, getContext());
166         mWallpaperSurface = homePreviewCard.findViewById(R.id.wallpaper_surface);
167         mWallpaperSurfaceCallback = new WallpaperSurfaceCallback(getContext(), homePreviewCard,
168                 mWallpaperSurface);
169         mWallPaperPreviews.add(homePreviewCard);
170 
171         CardView lockscreenPreviewCard = (CardView) inflater.inflate(
172                 R.layout.wallpaper_preview_card, null);
173         lockscreenPreviewCard.findViewById(R.id.workspace_surface).setVisibility(View.GONE);
174         lockscreenPreviewCard.findViewById(R.id.wallpaper_surface).setVisibility(View.GONE);
175         ViewGroup lockPreviewContainer = lockscreenPreviewCard.findViewById(
176                 R.id.lock_screen_preview_container);
177         lockPreviewContainer.setVisibility(View.VISIBLE);
178         mLockScreenPreviewer = new LockScreenPreviewer(getLifecycle(), getContext(),
179                 lockPreviewContainer);
180         mWallPaperPreviews.add(lockscreenPreviewCard);
181 
182         mPreviewPager = view.findViewById(R.id.wallpaper_preview_pager);
183         if (mPreviewPager.isRtl()) {
184             Collections.reverse(mWallPaperPreviews);
185         }
186         mPreviewPager.setAdapter(new PreviewPagerAdapter(mWallPaperPreviews));
187         mPreviewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
188             @Override
189             public void onPageScrolled(int position, float positionOffset,
190                     int positionOffsetPixels) {
191                 // For live wallpaper, show its thumbnail when scrolling.
192                 if (mWallpaperConnection != null && mWallpaperConnection.isEngineReady()
193                         && mHomePreviewWallpaperInfo instanceof LiveWallpaperInfo) {
194                     if (positionOffset == 0.0f || positionOffset == 1.0f
195                             || positionOffsetPixels == 0) {
196                         // The page is not moved. Show live wallpaper.
197                         mWallpaperSurface.setZOrderMediaOverlay(false);
198                     } else {
199                         // The page is moving. Show live wallpaper's thumbnail.
200                         mWallpaperSurface.setZOrderMediaOverlay(true);
201                     }
202                 }
203             }
204 
205             @Override
206             public void onPageSelected(int i) {
207                 mWallpaperIndex = mPreviewPager.isRtl()
208                         ? (mWallPaperPreviews.size() - 1) - i
209                         : i;
210                 if (mIndividualPickerFragment != null && mIndividualPickerFragment.isVisible()) {
211                     mIndividualPickerFragment.highlightAppliedWallpaper(mWallpaperIndex);
212                 }
213             }
214 
215             @Override
216             public void onPageScrollStateChanged(int i) {
217             }
218         });
219         setupCurrentWallpaperPreview(view);
220 
221         ViewGroup fragmentContainer = view.findViewById(R.id.category_fragment_container);
222         mBottomSheetBehavior = BottomSheetBehavior.from(fragmentContainer);
223         mBottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
224             @Override
225             public void onStateChanged(@NonNull View bottomSheet, int newState) {
226                 // Update preview pager's accessibility param since it will be blocked by the
227                 // bottom sheet when expanded.
228                 mPreviewPager.setImportantForAccessibility(newState == STATE_EXPANDED
229                         ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
230                         : View.IMPORTANT_FOR_ACCESSIBILITY_YES);
231             }
232 
233             @Override
234             public void onSlide(@NonNull View bottomSheet, float slideOffset) {}
235         });
236         mRootContainer = view.findViewById(R.id.root_container);
237         fragmentContainer.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
238             @Override
239             public void onLayoutChange(View containerView, int left, int top, int right,
240                     int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
241                 int minimumHeight = mRootContainer.getHeight() - mPreviewPager.getMeasuredHeight();
242                 mBottomSheetBehavior.setPeekHeight(minimumHeight);
243                 containerView.setMinimumHeight(minimumHeight);
244                 homePreviewCard.setRadius(SizeCalculator.getPreviewCornerRadius(
245                                 getActivity(), homePreviewCard.getMeasuredWidth()));
246                 if (lockscreenPreviewCard != null) {
247                     lockscreenPreviewCard
248                             .setRadius(SizeCalculator.getPreviewCornerRadius(
249                                     getActivity(), lockPreviewContainer.getMeasuredWidth()));
250                 }
251             }});
252         fragmentContainer.setOnApplyWindowInsetsListener((v, windowInsets) -> {
253             v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight(),
254                     windowInsets.getSystemWindowInsetBottom());
255             return windowInsets;
256         });
257 
258         setUpToolbar(view);
259 
260         getChildFragmentManager()
261                 .beginTransaction()
262                 .replace(R.id.category_fragment_container, mCategorySelectorFragment)
263                 .commitNow();
264 
265         // Deep link case
266         Intent intent = getActivity().getIntent();
267         String deepLinkCollectionId = DeepLinkUtils.getCollectionId(intent);
268         if (!TextUtils.isEmpty(deepLinkCollectionId)) {
269             mIndividualPickerFragment = InjectorProvider.getInjector()
270                     .getIndividualPickerFragment(deepLinkCollectionId);
271             mIndividualPickerFragment.highlightAppliedWallpaper(mWallpaperIndex);
272             getChildFragmentManager()
273                     .beginTransaction()
274                     .replace(R.id.category_fragment_container, mIndividualPickerFragment)
275                     .addToBackStack(null)
276                     .commit();
277             getChildFragmentManager().executePendingTransactions();
278             intent.setData(null);
279         }
280         return view;
281     }
282 
283     @Override
onViewCreated(@onNull View view, @Nullable Bundle savedInstanceState)284     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
285         super.onViewCreated(view, savedInstanceState);
286         updateWallpaperSurface();
287         updateWorkspaceSurface();
288     }
289 
290     @Override
getDefaultTitle()291     public CharSequence getDefaultTitle() {
292         return getContext().getString(R.string.app_name);
293     }
294 
295     @Override
onResume()296     public void onResume() {
297         super.onResume();
298 
299         WallpaperPreferences preferences = InjectorProvider.getInjector().getPreferences(getActivity());
300         preferences.setLastAppActiveTimestamp(new Date().getTime());
301 
302         // Reset Glide memory settings to a "normal" level of usage since it may have been lowered in
303         // PreviewFragment.
304         Glide.get(getActivity()).setMemoryCategory(MemoryCategory.NORMAL);
305 
306         // The wallpaper may have been set while this fragment was paused, so force refresh the current
307         // wallpapers and presentation mode.
308         if (!mShowSelectedWallpaper) {
309             refreshCurrentWallpapers(/* forceRefresh= */ true);
310         }
311         if (mWallpaperConnection != null) {
312             mWallpaperConnection.setVisibility(true);
313         }
314     }
315 
316     @Override
onPause()317     public void onPause() {
318         super.onPause();
319         if (mWallpaperConnection != null) {
320             mWallpaperConnection.setVisibility(false);
321         }
322     }
323 
324     @Override
onStop()325     public void onStop() {
326         super.onStop();
327         if (mWallpaperConnection != null) {
328             mWallpaperConnection.disconnect();
329             mWallpaperConnection = null;
330         }
331     }
332 
333     @Override
onDestroyView()334     public void onDestroyView() {
335         super.onDestroyView();
336         mWallpaperSurfaceCallback.cleanUp();
337         mWorkspaceSurfaceCallback.cleanUp();
338         if (mWallpaperConnection != null) {
339             mWallpaperConnection.disconnect();
340             mWallpaperConnection = null;
341         }
342         mPreviewPager.setAdapter(null);
343         mWallPaperPreviews.forEach(view -> ((ViewGroup) view).removeAllViews());
344         mWallPaperPreviews.clear();
345     }
346 
347     @Override
onDestroy()348     public void onDestroy() {
349         super.onDestroy();
350         if (mWallpaperConnection != null) {
351             mWallpaperConnection.disconnect();
352             mWallpaperConnection = null;
353         }
354     }
355 
356     @Override
onActivityResult(int requestCode, int resultCode, Intent data)357     public void onActivityResult(int requestCode, int resultCode, Intent data) {
358         if (requestCode == SETTINGS_APP_INFO_REQUEST_CODE) {
359             mCategorySelectorFragment.notifyDataSetChanged();
360         }
361     }
362 
363     @Override
requestCustomPhotoPicker(PermissionChangedListener listener)364     public void requestCustomPhotoPicker(PermissionChangedListener listener) {
365         getFragmentHost().getMyPhotosStarter().requestCustomPhotoPicker(listener);
366     }
367 
368     @Override
show(Category category)369     public void show(Category category) {
370         if (!(category instanceof WallpaperCategory)) {
371             getFragmentHost().show(category.getCollectionId());
372             return;
373         }
374         mIndividualPickerFragment = InjectorProvider.getInjector()
375                 .getIndividualPickerFragment(category.getCollectionId());
376         mIndividualPickerFragment.highlightAppliedWallpaper(mWallpaperIndex);
377         mIndividualPickerFragment.setOnWallpaperSelectedListener(position -> {
378             // Scroll to the selected wallpaper and collapse the sheet if needed.
379             // Resize and scroll here because we want to let the RecyclerView's scrolling and
380             // BottomSheet's collapsing can be executed together instead of scrolling
381             // the RecyclerView after the BottomSheet is collapsed.
382             mIndividualPickerFragment.resizeLayout(mBottomSheetBehavior.getPeekHeight());
383             mIndividualPickerFragment.scrollToPosition(position);
384             if (mBottomSheetBehavior.getState() != STATE_COLLAPSED) {
385                 mBottomSheetBehavior.setState(STATE_COLLAPSED);
386             }
387         });
388         getChildFragmentManager()
389                 .beginTransaction()
390                 .replace(R.id.category_fragment_container, mIndividualPickerFragment)
391                 .addToBackStack(null)
392                 .commit();
393         getChildFragmentManager().executePendingTransactions();
394     }
395 
396     @Override
isHostToolbarShown()397     public boolean isHostToolbarShown() {
398         return true;
399     }
400 
401     @Override
setToolbarTitle(CharSequence title)402     public void setToolbarTitle(CharSequence title) {
403         setTitle(title);
404     }
405 
406     @Override
setToolbarMenu(int menuResId)407     public void setToolbarMenu(int menuResId) {
408         setUpToolbarMenu(menuResId);
409     }
410 
411     @Override
removeToolbarMenu()412     public void removeToolbarMenu() {
413         mToolbar.getMenu().clear();
414     }
415 
416     @Override
moveToPreviousFragment()417     public void moveToPreviousFragment() {
418         getChildFragmentManager().popBackStack();
419     }
420 
421     @Override
fetchCategories()422     public void fetchCategories() {
423         getFragmentHost().fetchCategories();
424     }
425 
426     @Override
cleanUp()427     public void cleanUp() {
428         getFragmentHost().cleanUp();
429     }
430 
431     @Override
expandBottomSheet()432     public void expandBottomSheet() {
433         if (mBottomSheetBehavior.getState() != BottomSheetBehavior.STATE_EXPANDED) {
434             mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
435         }
436     }
437 
438     @Override
getBottomSheetState()439     public int getBottomSheetState() {
440         return mBottomSheetBehavior.getState();
441     }
442 
443     @Override
updateThumbnail(WallpaperInfo wallpaperInfo)444     public void updateThumbnail(WallpaperInfo wallpaperInfo) {
445         new android.os.Handler().post(() -> {
446             // A config change may have destroyed the activity since the refresh started, so check
447             // for that.
448             if (getActivity() == null) {
449                 return;
450             }
451 
452             mHomePreviewWallpaperInfo = wallpaperInfo;
453             mLockPreviewWallpaperInfo = wallpaperInfo;
454             updateThumbnail(mHomePreviewWallpaperInfo,
455                     mWallpaperSurfaceCallback.getHomeImageWallpaper(), true);
456             mShowSelectedWallpaper = true;
457         });
458     }
459 
460     @Override
restoreThumbnails()461     public void restoreThumbnails() {
462         refreshCurrentWallpapers(/* forceRefresh= */ true);
463         mShowSelectedWallpaper = false;
464     }
465 
466     @Override
onDestinationSet(@allpaperPersister.Destination int destination)467     public void onDestinationSet(@WallpaperPersister.Destination int destination) {
468         if (destination == WallpaperPersister.DEST_BOTH) {
469             return;
470         }
471         mPreviewPager.switchPreviewPage(destination);
472     }
473 
474     @Override
onMenuItemClick(MenuItem item)475     public boolean onMenuItemClick(MenuItem item) {
476         if (item.getItemId() == R.id.daily_rotation) {
477             if (mIndividualPickerFragment != null && mIndividualPickerFragment.isVisible()) {
478                 mIndividualPickerFragment.showRotationDialog();
479             }
480             return true;
481         }
482         return super.onMenuItemClick(item);
483     }
484 
485     /**
486      * Gets the {@link CategorySelectorFragment} which is attached to {@link CategoryFragment}.
487      */
getCategorySelectorFragment()488     public CategorySelectorFragment getCategorySelectorFragment() {
489         return mCategorySelectorFragment;
490     }
491 
492     /**
493      * Pops the child fragment from the stack if {@link CategoryFragment} is visible to the users.
494      *
495      * @return {@code true} if the child fragment is popped, {@code false} otherwise.
496      */
popChildFragment()497     public boolean popChildFragment() {
498         return isVisible() && getChildFragmentManager().popBackStackImmediate();
499     }
500 
canShowCurrentWallpaper()501     private boolean canShowCurrentWallpaper() {
502         Activity activity = getActivity();
503         CategoryFragmentHost host = getFragmentHost();
504         PackageManager packageManager = activity.getPackageManager();
505         String packageName = activity.getPackageName();
506 
507         boolean hasReadWallpaperInternal = packageManager.checkPermission(
508                 PERMISSION_READ_WALLPAPER_INTERNAL, packageName) == PackageManager.PERMISSION_GRANTED;
509         return hasReadWallpaperInternal || host.isReadExternalStoragePermissionGranted();
510     }
511 
showCurrentWallpaper(View rootView, boolean show)512     private void showCurrentWallpaper(View rootView, boolean show) {
513         // The category/wallpaper tiles page depends on the height of the preview pager.
514         // So if we want to hide the preview pager, we should use INVISIBLE instead of GONE.
515         rootView.findViewById(R.id.wallpaper_preview_pager)
516                 .setVisibility(show ? View.VISIBLE : View.INVISIBLE);
517         rootView.findViewById(R.id.permission_needed)
518                 .setVisibility(show ? View.GONE : View.VISIBLE);
519     }
520 
setupCurrentWallpaperPreview(View rootView)521     private void setupCurrentWallpaperPreview(View rootView) {
522         if (canShowCurrentWallpaper()) {
523             showCurrentWallpaper(rootView, true);
524         } else {
525             showCurrentWallpaper(rootView, false);
526 
527             Button mAllowAccessButton = rootView
528                     .findViewById(R.id.permission_needed_allow_access_button);
529             mAllowAccessButton.setOnClickListener(view ->
530                     getFragmentHost().requestExternalStoragePermission(
531                             new PermissionChangedListener() {
532 
533                                 @Override
534                                 public void onPermissionsGranted() {
535                                     showCurrentWallpaper(rootView, true);
536                                     mCategorySelectorFragment.notifyDataSetChanged();
537                                 }
538 
539                                 @Override
540                                 public void onPermissionsDenied(boolean dontAskAgain) {
541                                     if (!dontAskAgain) {
542                                         return;
543                                     }
544                                     showPermissionNeededDialog();
545                                 }
546                             })
547             );
548 
549             // Replace explanation text with text containing the Wallpapers app name which replaces
550             // the placeholder.
551             String appName = getString(R.string.app_name);
552             String explanation = getString(R.string.permission_needed_explanation, appName);
553             TextView explanationView = rootView.findViewById(R.id.permission_needed_explanation);
554             explanationView.setText(explanation);
555         }
556     }
557 
showPermissionNeededDialog()558     private void showPermissionNeededDialog() {
559         String permissionNeededMessage = getString(
560                 R.string.permission_needed_explanation_go_to_settings);
561         AlertDialog dialog = new AlertDialog.Builder(getActivity(), R.style.LightDialogTheme)
562                 .setMessage(permissionNeededMessage)
563                 .setPositiveButton(android.R.string.ok, /* onClickListener= */ null)
564                 .setNegativeButton(
565                         R.string.settings_button_label,
566                         (dialogInterface, i) -> {
567                             Intent appInfoIntent = new Intent();
568                             appInfoIntent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
569                             Uri uri = Uri.fromParts("package",
570                                     getActivity().getPackageName(), /* fragment= */ null);
571                             appInfoIntent.setData(uri);
572                             startActivityForResult(appInfoIntent, SETTINGS_APP_INFO_REQUEST_CODE);
573                         })
574                 .create();
575         dialog.show();
576     }
577 
getFragmentHost()578     private CategoryFragmentHost getFragmentHost() {
579         return (CategoryFragmentHost) getActivity();
580     }
581 
getWallpaperIntent(android.app.WallpaperInfo info)582     private Intent getWallpaperIntent(android.app.WallpaperInfo info) {
583         return new Intent(WallpaperService.SERVICE_INTERFACE)
584                 .setClassName(info.getPackageName(), info.getServiceName());
585     }
586 
587     /**
588      * Obtains the {@link WallpaperInfo} object(s) representing the wallpaper(s) currently set to
589      * the device from the {@link CurrentWallpaperInfoFactory}.
590      */
refreshCurrentWallpapers(boolean forceRefresh)591     private void refreshCurrentWallpapers(boolean forceRefresh) {
592         CurrentWallpaperInfoFactory factory = InjectorProvider.getInjector()
593                 .getCurrentWallpaperFactory(getActivity().getApplicationContext());
594 
595         factory.createCurrentWallpaperInfos(new WallpaperInfoCallback() {
596             @Override
597             public void onWallpaperInfoCreated(
598                     final WallpaperInfo homeWallpaper,
599                     @Nullable final WallpaperInfo lockWallpaper,
600                     @PresentationMode final int presentationMode) {
601 
602                 // Update the metadata displayed on screen. Do this in a Handler so it is scheduled at the
603                 // end of the message queue. This is necessary to ensure we do not remove or add data from
604                 // the adapter while the layout is being computed. RecyclerView documentation therefore
605                 // recommends performing such changes in a Handler.
606                 new android.os.Handler().post(new Runnable() {
607                     @Override
608                     public void run() {
609                         final Activity activity = getActivity();
610                         // A config change may have destroyed the activity since the refresh
611                         // started, so check for that.
612                         if (activity == null) {
613                             return;
614                         }
615 
616                         mHomePreviewWallpaperInfo = homeWallpaper;
617                         mLockPreviewWallpaperInfo =
618                                 lockWallpaper == null ? homeWallpaper : lockWallpaper;
619                         updateThumbnail(mHomePreviewWallpaperInfo,
620                                 mWallpaperSurfaceCallback.getHomeImageWallpaper(), true);
621                     }
622                 });
623             }
624         }, forceRefresh);
625     }
626 
setUpLiveWallpaperPreview(WallpaperInfo homeWallpaper)627     private void setUpLiveWallpaperPreview(WallpaperInfo homeWallpaper) {
628         Activity activity = getActivity();
629         if (activity == null) {
630             return;
631         }
632         if (mWallpaperConnection != null) {
633             mWallpaperConnection.disconnect();
634         }
635 
636         if (WallpaperConnection.isPreviewAvailable()) {
637             ImageView previewView = mWallpaperSurfaceCallback.getHomeImageWallpaper();
638             mWallpaperConnection = new WallpaperConnection(
639                     getWallpaperIntent(homeWallpaper.getWallpaperComponent()), activity,
640                     new WallpaperConnectionListener() {
641                         @Override
642                         public void onWallpaperColorsChanged(WallpaperColors colors,
643                                 int displayId) {
644                             if (mLockPreviewWallpaperInfo instanceof LiveWallpaperInfo) {
645                                 mLockScreenPreviewer.setColor(colors);
646                             }
647                         }
648                     }, mWallpaperSurface);
649 
650 
651             mWallpaperConnection.setVisibility(true);
652             previewView.post(() -> {
653                 if (mWallpaperConnection != null && !mWallpaperConnection.connect()) {
654                     mWallpaperConnection = null;
655                 }
656             });
657         }
658     }
659 
updateThumbnail(WallpaperInfo wallpaperInfo, ImageView thumbnailView, boolean isHomeWallpaper)660     private void updateThumbnail(WallpaperInfo wallpaperInfo, ImageView thumbnailView,
661                                  boolean isHomeWallpaper) {
662         if (wallpaperInfo == null) {
663             return;
664         }
665 
666         if (thumbnailView == null) {
667             return;
668         }
669 
670         Activity activity = getActivity();
671         if (activity == null) {
672             return;
673         }
674 
675         UserEventLogger eventLogger = InjectorProvider.getInjector().getUserEventLogger(activity);
676 
677         boolean renderInImageWallpaperSurface =
678                 !(wallpaperInfo instanceof LiveWallpaperInfo) && isHomeWallpaper;
679         ImageView imageView = renderInImageWallpaperSurface
680                 ? mWallpaperSurfaceCallback.getHomeImageWallpaper() : thumbnailView;
681         if (imageView != null) {
682             wallpaperInfo.getThumbAsset(activity.getApplicationContext())
683                     .loadPreviewImage(activity, imageView,
684                             ResourceUtils.getColorAttr(
685                                     getActivity(), android.R.attr.colorSecondary));
686         }
687 
688         if (isHomeWallpaper) {
689             if (wallpaperInfo instanceof LiveWallpaperInfo) {
690                 if (mWallpaperSurfaceCallback.getHomeImageWallpaper() != null) {
691                     wallpaperInfo.getThumbAsset(activity.getApplicationContext()).loadPreviewImage(
692                             activity, mWallpaperSurfaceCallback.getHomeImageWallpaper(),
693                             ResourceUtils.getColorAttr(
694                                     getActivity(), android.R.attr.colorSecondary));
695                 }
696                 setUpLiveWallpaperPreview(wallpaperInfo);
697             } else {
698                 if (mWallpaperConnection != null) {
699                     mWallpaperConnection.disconnect();
700                     mWallpaperConnection = null;
701                 }
702             }
703         } else {
704             // lock screen wallpaper
705             if (!(wallpaperInfo instanceof LiveWallpaperInfo)
706                     || !WallpaperConnection.isPreviewAvailable()) {
707                 // Load wallpaper color from thumbnail for static wallpaper.
708                 WallpaperColorsLoader.getWallpaperColors(
709                         activity,
710                         wallpaperInfo.getThumbAsset(activity),
711                         mLockScreenPreviewer::setColor);
712             }
713         }
714 
715         ((View) thumbnailView.getParent()).setOnClickListener(view -> {
716             getFragmentHost().showViewOnlyPreview(wallpaperInfo, isHomeWallpaper);
717             eventLogger.logCurrentWallpaperPreviewed();
718         });
719     }
720 
updateWallpaperSurface()721     private void updateWallpaperSurface() {
722         mWallpaperSurface.getHolder().addCallback(mWallpaperSurfaceCallback);
723         mWallpaperSurface.setZOrderMediaOverlay(true);
724     }
725 
updateWorkspaceSurface()726     private void updateWorkspaceSurface() {
727         mWorkspaceSurface.setZOrderMediaOverlay(true);
728         mWorkspaceSurface.getHolder().addCallback(mWorkspaceSurfaceCallback);
729     }
730 
731     private static class PreviewPagerAdapter extends PagerAdapter {
732 
733         private List<View> mPages;
734 
PreviewPagerAdapter(List<View> pages)735         PreviewPagerAdapter(List<View> pages) {
736             mPages = pages;
737         }
738 
739         @Override
destroyItem(@onNull ViewGroup container, int position, @NonNull Object object)740         public void destroyItem(@NonNull ViewGroup container, int position,
741                                 @NonNull Object object) {
742             container.removeView((View) object);
743         }
744 
745         @NonNull
746         @Override
instantiateItem(@onNull ViewGroup container, int position)747         public Object instantiateItem(@NonNull ViewGroup container, int position) {
748             View view = mPages.get(position);
749             container.addView(view);
750             return view;
751         }
752 
753         @Override
getCount()754         public int getCount() {
755             return mPages.size();
756         }
757 
758         @Override
isViewFromObject(@onNull View view, @NonNull Object o)759         public boolean isViewFromObject(@NonNull View view, @NonNull Object o) {
760             return view == o;
761         }
762     }
763 }
764