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.individual;
17 
18 import android.annotation.MenuRes;
19 import android.app.Activity;
20 import android.app.ProgressDialog;
21 import android.app.WallpaperManager;
22 import android.content.Context;
23 import android.content.DialogInterface;
24 import android.content.res.Configuration;
25 import android.content.res.Resources.NotFoundException;
26 import android.graphics.Point;
27 import android.os.Build.VERSION;
28 import android.os.Build.VERSION_CODES;
29 import android.os.Bundle;
30 import android.service.wallpaper.WallpaperService;
31 import android.text.TextUtils;
32 import android.util.ArraySet;
33 import android.util.Log;
34 import android.view.LayoutInflater;
35 import android.view.MenuItem;
36 import android.view.View;
37 import android.view.ViewGroup;
38 import android.widget.ImageView;
39 import android.widget.RelativeLayout;
40 import android.widget.Toast;
41 
42 import androidx.annotation.DrawableRes;
43 import androidx.annotation.NonNull;
44 import androidx.cardview.widget.CardView;
45 import androidx.core.widget.ContentLoadingProgressBar;
46 import androidx.fragment.app.DialogFragment;
47 import androidx.fragment.app.Fragment;
48 import androidx.recyclerview.widget.GridLayoutManager;
49 import androidx.recyclerview.widget.RecyclerView;
50 import androidx.recyclerview.widget.RecyclerView.ViewHolder;
51 
52 import com.android.wallpaper.R;
53 import com.android.wallpaper.model.Category;
54 import com.android.wallpaper.model.CategoryProvider;
55 import com.android.wallpaper.model.CategoryReceiver;
56 import com.android.wallpaper.model.WallpaperCategory;
57 import com.android.wallpaper.model.WallpaperInfo;
58 import com.android.wallpaper.model.WallpaperReceiver;
59 import com.android.wallpaper.model.WallpaperRotationInitializer;
60 import com.android.wallpaper.model.WallpaperRotationInitializer.Listener;
61 import com.android.wallpaper.model.WallpaperRotationInitializer.NetworkPreference;
62 import com.android.wallpaper.module.Injector;
63 import com.android.wallpaper.module.InjectorProvider;
64 import com.android.wallpaper.module.PackageStatusNotifier;
65 import com.android.wallpaper.module.WallpaperPreferences;
66 import com.android.wallpaper.picker.AppbarFragment;
67 import com.android.wallpaper.picker.FragmentTransactionChecker;
68 import com.android.wallpaper.picker.MyPhotosStarter.MyPhotosStarterProvider;
69 import com.android.wallpaper.picker.RotationStarter;
70 import com.android.wallpaper.picker.StartRotationDialogFragment;
71 import com.android.wallpaper.picker.StartRotationErrorDialogFragment;
72 import com.android.wallpaper.util.ActivityUtils;
73 import com.android.wallpaper.util.LaunchUtils;
74 import com.android.wallpaper.util.SizeCalculator;
75 import com.android.wallpaper.widget.GridPaddingDecoration;
76 import com.android.wallpaper.widget.WallpaperPickerRecyclerViewAccessibilityDelegate;
77 import com.android.wallpaper.widget.WallpaperPickerRecyclerViewAccessibilityDelegate.BottomSheetHost;
78 
79 import com.bumptech.glide.Glide;
80 import com.bumptech.glide.MemoryCategory;
81 
82 import java.util.ArrayList;
83 import java.util.Date;
84 import java.util.List;
85 import java.util.Set;
86 
87 /**
88  * Displays the Main UI for picking an individual wallpaper image.
89  */
90 public class IndividualPickerFragment extends AppbarFragment
91         implements RotationStarter, StartRotationErrorDialogFragment.Listener,
92         StartRotationDialogFragment.Listener {
93 
94     /**
95      * Position of a special tile that doesn't belong to an individual wallpaper of the category,
96      * such as "my photos" or "daily rotation".
97      */
98     static final int SPECIAL_FIXED_TILE_ADAPTER_POSITION = 0;
99     static final String ARG_CATEGORY_COLLECTION_ID = "category_collection_id";
100 
101     protected static final int MAX_CAPACITY_IN_FEWER_COLUMN_LAYOUT = 8;
102 
103     private static final String TAG = "IndividualPickerFrgmnt";
104     private static final int UNUSED_REQUEST_CODE = 1;
105     private static final String TAG_START_ROTATION_DIALOG = "start_rotation_dialog";
106     private static final String TAG_START_ROTATION_ERROR_DIALOG = "start_rotation_error_dialog";
107     private static final String PROGRESS_DIALOG_NO_TITLE = null;
108     private static final boolean PROGRESS_DIALOG_INDETERMINATE = true;
109     private static final String KEY_NIGHT_MODE = "IndividualPickerFragment.NIGHT_MODE";
110 
111     /**
112      * Interface to be implemented by a Fragment(or an Activity) hosting
113      * a {@link IndividualPickerFragment}.
114      */
115     public interface IndividualPickerFragmentHost {
116         /**
117          * Indicates if the host has toolbar to show the title. If it does, we should set the title
118          * there.
119          */
isHostToolbarShown()120         boolean isHostToolbarShown();
121 
122         /**
123          * Sets the title in the host's toolbar.
124          */
setToolbarTitle(CharSequence title)125         void setToolbarTitle(CharSequence title);
126 
127         /**
128          * Configures the menu in the toolbar.
129          *
130          * @param menuResId the resource id of the menu
131          */
setToolbarMenu(@enuRes int menuResId)132         void setToolbarMenu(@MenuRes int menuResId);
133 
134         /**
135          * Removes the menu in the toolbar.
136          */
removeToolbarMenu()137         void removeToolbarMenu();
138 
139         /**
140          * Moves to the previous fragment.
141          */
moveToPreviousFragment()142         void moveToPreviousFragment();
143     }
144 
145     RecyclerView mImageGrid;
146     IndividualAdapter mAdapter;
147     WallpaperCategory mCategory;
148     WallpaperRotationInitializer mWallpaperRotationInitializer;
149     List<WallpaperInfo> mWallpapers;
150     Point mTileSizePx;
151     PackageStatusNotifier mPackageStatusNotifier;
152 
153     boolean mIsWallpapersReceived;
154     PackageStatusNotifier.Listener mAppStatusListener;
155 
156     private ProgressDialog mProgressDialog;
157     private ContentLoadingProgressBar mLoading;
158     private CategoryProvider mCategoryProvider;
159 
160     /**
161      * Staged error dialog fragments that were unable to be shown when the activity didn't allow
162      * committing fragment transactions.
163      */
164     private StartRotationErrorDialogFragment mStagedStartRotationErrorDialogFragment;
165 
166     private WallpaperManager mWallpaperManager;
167     private Set<String> mAppliedWallpaperIds;
168 
newInstance(String collectionId)169     public static IndividualPickerFragment newInstance(String collectionId) {
170         Bundle args = new Bundle();
171         args.putString(ARG_CATEGORY_COLLECTION_ID, collectionId);
172 
173         IndividualPickerFragment fragment = new IndividualPickerFragment();
174         fragment.setArguments(args);
175         return fragment;
176     }
177 
178     @Override
onCreate(Bundle savedInstanceState)179     public void onCreate(Bundle savedInstanceState) {
180         super.onCreate(savedInstanceState);
181 
182         Injector injector = InjectorProvider.getInjector();
183         Context appContext = getContext().getApplicationContext();
184 
185         mWallpaperManager = WallpaperManager.getInstance(appContext);
186 
187         mPackageStatusNotifier = injector.getPackageStatusNotifier(appContext);
188 
189         mWallpapers = new ArrayList<>();
190 
191         // Clear Glide's cache if night-mode changed to ensure thumbnails are reloaded
192         if (savedInstanceState != null && (savedInstanceState.getInt(KEY_NIGHT_MODE)
193                 != (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK))) {
194             Glide.get(getContext()).clearMemory();
195         }
196 
197         mCategoryProvider = injector.getCategoryProvider(appContext);
198         mCategoryProvider.fetchCategories(new CategoryReceiver() {
199             @Override
200             public void onCategoryReceived(Category category) {
201                 // Do nothing.
202             }
203 
204             @Override
205             public void doneFetchingCategories() {
206                 Category category = mCategoryProvider.getCategory(
207                         getArguments().getString(ARG_CATEGORY_COLLECTION_ID));
208                 if (category != null && !(category instanceof WallpaperCategory)) {
209                     return;
210                 }
211                 mCategory = (WallpaperCategory) category;
212                 if (mCategory == null) {
213                     // The absence of this category in the CategoryProvider indicates a broken
214                     // state, see b/38030129. Hence, finish the activity and return.
215                     getIndividualPickerFragmentHost().moveToPreviousFragment();
216                     Toast.makeText(getContext(), R.string.collection_not_exist_msg,
217                             Toast.LENGTH_SHORT).show();
218                     return;
219                 }
220                 onCategoryLoaded();
221             }
222         }, false);
223     }
224 
225 
onCategoryLoaded()226     protected void onCategoryLoaded() {
227         if (getIndividualPickerFragmentHost() == null) {
228             return;
229         }
230         if (getIndividualPickerFragmentHost().isHostToolbarShown()) {
231             getIndividualPickerFragmentHost().setToolbarTitle(mCategory.getTitle());
232         } else {
233             setTitle(mCategory.getTitle());
234         }
235         mWallpaperRotationInitializer = mCategory.getWallpaperRotationInitializer();
236         if (mToolbar != null && isRotationEnabled()) {
237             setUpToolbarMenu(R.menu.individual_picker_menu);
238         }
239         fetchWallpapers(false);
240 
241         if (mCategory.supportsThirdParty()) {
242             mAppStatusListener = (packageName, status) -> {
243                 if (status != PackageStatusNotifier.PackageStatus.REMOVED ||
244                         mCategory.containsThirdParty(packageName)) {
245                     fetchWallpapers(true);
246                 }
247             };
248             mPackageStatusNotifier.addListener(mAppStatusListener,
249                     WallpaperService.SERVICE_INTERFACE);
250         }
251     }
252 
fetchWallpapers(boolean forceReload)253     void fetchWallpapers(boolean forceReload) {
254         mWallpapers.clear();
255         mIsWallpapersReceived = false;
256         updateLoading();
257         mCategory.fetchWallpapers(getActivity().getApplicationContext(), new WallpaperReceiver() {
258             @Override
259             public void onWallpapersReceived(List<WallpaperInfo> wallpapers) {
260                 mIsWallpapersReceived = true;
261                 updateLoading();
262                 for (WallpaperInfo wallpaper : wallpapers) {
263                     mWallpapers.add(wallpaper);
264                 }
265                 maybeSetUpImageGrid();
266 
267                 // Wallpapers may load after the adapter is initialized, in which case we have
268                 // to explicitly notify that the data set has changed.
269                 if (mAdapter != null) {
270                     mAdapter.notifyDataSetChanged();
271                 }
272 
273                 if (wallpapers.isEmpty()) {
274                     // If there are no more wallpapers and we're on phone, just finish the
275                     // Activity.
276                     Activity activity = getActivity();
277                     if (activity != null) {
278                         activity.finish();
279                     }
280                 }
281             }
282         }, forceReload);
283     }
284 
updateLoading()285     void updateLoading() {
286         if (mLoading == null) {
287             return;
288         }
289 
290         if (mIsWallpapersReceived) {
291             mLoading.hide();
292         } else {
293             mLoading.show();
294         }
295     }
296 
297     @Override
onSaveInstanceState(@onNull Bundle outState)298     public void onSaveInstanceState(@NonNull Bundle outState) {
299         super.onSaveInstanceState(outState);
300         outState.putInt(KEY_NIGHT_MODE,
301                 getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK);
302     }
303 
304     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)305     public View onCreateView(LayoutInflater inflater, ViewGroup container,
306                              Bundle savedInstanceState) {
307         View view = inflater.inflate(R.layout.fragment_individual_picker, container, false);
308         if (getIndividualPickerFragmentHost().isHostToolbarShown()) {
309             view.findViewById(R.id.header_bar).setVisibility(View.GONE);
310             setUpArrowEnabled(/* upArrow= */ true);
311             if (isRotationEnabled()) {
312                 getIndividualPickerFragmentHost().setToolbarMenu(R.menu.individual_picker_menu);
313             }
314         } else {
315             setUpToolbar(view);
316             if (isRotationEnabled()) {
317                 setUpToolbarMenu(R.menu.individual_picker_menu);
318             }
319             if (mCategory != null) {
320                 setTitle(mCategory.getTitle());
321             }
322         }
323 
324         mAppliedWallpaperIds = getAppliedWallpaperIds();
325 
326         mImageGrid = (RecyclerView) view.findViewById(R.id.wallpaper_grid);
327         mLoading = view.findViewById(R.id.loading_indicator);
328         updateLoading();
329         maybeSetUpImageGrid();
330         // For nav bar edge-to-edge effect.
331         mImageGrid.setOnApplyWindowInsetsListener((v, windowInsets) -> {
332             v.setPadding(
333                     v.getPaddingLeft(),
334                     v.getPaddingTop(),
335                     v.getPaddingRight(),
336                     windowInsets.getSystemWindowInsetBottom());
337             return windowInsets.consumeSystemWindowInsets();
338         });
339         return view;
340     }
341 
getIndividualPickerFragmentHost()342     private IndividualPickerFragmentHost getIndividualPickerFragmentHost() {
343         Fragment parentFragment = getParentFragment();
344         if (parentFragment != null) {
345             return (IndividualPickerFragmentHost) parentFragment;
346         } else {
347             return (IndividualPickerFragmentHost) getActivity();
348         }
349     }
350 
maybeSetUpImageGrid()351     protected void maybeSetUpImageGrid() {
352         // Skip if mImageGrid been initialized yet
353         if (mImageGrid == null) {
354             return;
355         }
356         // Skip if category hasn't loaded yet
357         if (mCategory == null) {
358             return;
359         }
360         if (getContext() == null) {
361             return;
362         }
363 
364         // Wallpaper count could change, so we may need to change the layout(2 or 3 columns layout)
365         GridLayoutManager gridLayoutManager = (GridLayoutManager) mImageGrid.getLayoutManager();
366         boolean needUpdateLayout =
367                 gridLayoutManager != null && gridLayoutManager.getSpanCount() != getNumColumns();
368 
369         // Skip if the adapter was already created and don't need to change the layout
370         if (mAdapter != null && !needUpdateLayout) {
371             return;
372         }
373 
374         // Clear the old decoration
375         int decorationCount = mImageGrid.getItemDecorationCount();
376         for (int i = 0; i < decorationCount; i++) {
377             mImageGrid.removeItemDecorationAt(i);
378         }
379 
380         mImageGrid.addItemDecoration(new GridPaddingDecoration(getGridItemPaddingHorizontal(),
381                 getGridItemPaddingBottom()));
382         int edgePadding = getEdgePadding();
383         mImageGrid.setPadding(edgePadding, mImageGrid.getPaddingTop(), edgePadding,
384                 mImageGrid.getPaddingBottom());
385         mTileSizePx = isFewerColumnLayout()
386                 ? SizeCalculator.getFeaturedIndividualTileSize(getActivity())
387                 : SizeCalculator.getIndividualTileSize(getActivity());
388         setUpImageGrid();
389         mImageGrid.setAccessibilityDelegateCompat(
390                 new WallpaperPickerRecyclerViewAccessibilityDelegate(
391                         mImageGrid, (BottomSheetHost) getParentFragment(), getNumColumns()));
392     }
393 
isFewerColumnLayout()394     boolean isFewerColumnLayout() {
395         return mWallpapers != null && mWallpapers.size() <= MAX_CAPACITY_IN_FEWER_COLUMN_LAYOUT;
396     }
397 
getGridItemPaddingHorizontal()398     private int getGridItemPaddingHorizontal() {
399         return isFewerColumnLayout()
400                 ? getResources().getDimensionPixelSize(
401                 R.dimen.grid_item_featured_individual_padding_horizontal)
402                 : getResources().getDimensionPixelSize(
403                         R.dimen.grid_item_individual_padding_horizontal);
404     }
405 
getGridItemPaddingBottom()406     private int getGridItemPaddingBottom() {
407         return isFewerColumnLayout()
408                 ? getResources().getDimensionPixelSize(
409                 R.dimen.grid_item_featured_individual_padding_bottom)
410                 : getResources().getDimensionPixelSize(R.dimen.grid_item_individual_padding_bottom);
411     }
412 
getEdgePadding()413     private int getEdgePadding() {
414         return isFewerColumnLayout()
415                 ? getResources().getDimensionPixelSize(R.dimen.featured_wallpaper_grid_edge_space)
416                 : getResources().getDimensionPixelSize(R.dimen.wallpaper_grid_edge_space);
417     }
418 
419     /**
420      * Create the adapter and assign it to mImageGrid.
421      * Both mImageGrid and mCategory are guaranteed to not be null when this method is called.
422      */
setUpImageGrid()423     void setUpImageGrid() {
424         mAdapter = new IndividualAdapter(mWallpapers);
425         mImageGrid.setAdapter(mAdapter);
426         mImageGrid.setLayoutManager(new GridLayoutManager(getActivity(), getNumColumns()));
427     }
428 
429     @Override
onResume()430     public void onResume() {
431         super.onResume();
432 
433         WallpaperPreferences preferences = InjectorProvider.getInjector()
434                 .getPreferences(getActivity());
435         preferences.setLastAppActiveTimestamp(new Date().getTime());
436 
437         // Reset Glide memory settings to a "normal" level of usage since it may have been lowered in
438         // PreviewFragment.
439         Glide.get(getActivity()).setMemoryCategory(MemoryCategory.NORMAL);
440 
441         // Show the staged 'start rotation' error dialog fragment if there is one that was unable to be
442         // shown earlier when this fragment's hosting activity didn't allow committing fragment
443         // transactions.
444         if (mStagedStartRotationErrorDialogFragment != null) {
445             mStagedStartRotationErrorDialogFragment.show(
446                     getFragmentManager(), TAG_START_ROTATION_ERROR_DIALOG);
447             mStagedStartRotationErrorDialogFragment = null;
448         }
449     }
450 
451     @Override
onDestroyView()452     public void onDestroyView() {
453         super.onDestroyView();
454         getIndividualPickerFragmentHost().removeToolbarMenu();
455     }
456 
457     @Override
onDestroy()458     public void onDestroy() {
459         super.onDestroy();
460         if (mProgressDialog != null) {
461             mProgressDialog.dismiss();
462         }
463         if (mAppStatusListener != null) {
464             mPackageStatusNotifier.removeListener(mAppStatusListener);
465         }
466     }
467 
468     @Override
onStartRotationDialogDismiss(@onNull DialogInterface dialog)469     public void onStartRotationDialogDismiss(@NonNull DialogInterface dialog) {
470         // TODO(b/159310028): Refactor fragment layer to make it able to restore from config change.
471         // This is to handle config change with StartRotationDialog popup,  the StartRotationDialog
472         // still holds a reference to the destroyed Fragment and is calling
473         // onStartRotationDialogDismissed on that destroyed Fragment.
474     }
475 
476     @Override
retryStartRotation(@etworkPreference int networkPreference)477     public void retryStartRotation(@NetworkPreference int networkPreference) {
478         startRotation(networkPreference);
479     }
480 
481     @Override
startRotation(@etworkPreference final int networkPreference)482     public void startRotation(@NetworkPreference final int networkPreference) {
483         if (!isRotationEnabled()) {
484             Log.e(TAG, "Rotation is not enabled for this category " + mCategory.getTitle());
485             return;
486         }
487 
488         // ProgressDialog endlessly updates the UI thread, keeping it from going idle which therefore
489         // causes Espresso to hang once the dialog is shown.
490         int themeResId;
491         if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
492             themeResId = R.style.ProgressDialogThemePreL;
493         } else {
494             themeResId = R.style.LightDialogTheme;
495         }
496         mProgressDialog = new ProgressDialog(getActivity(), themeResId);
497 
498         mProgressDialog.setTitle(PROGRESS_DIALOG_NO_TITLE);
499         mProgressDialog.setMessage(
500                 getResources().getString(R.string.start_rotation_progress_message));
501         mProgressDialog.setIndeterminate(PROGRESS_DIALOG_INDETERMINATE);
502         mProgressDialog.show();
503 
504         final Context appContext = getActivity().getApplicationContext();
505 
506         mWallpaperRotationInitializer.setFirstWallpaperInRotation(
507                 appContext,
508                 networkPreference,
509                 new Listener() {
510                     @Override
511                     public void onFirstWallpaperInRotationSet() {
512                         if (mProgressDialog != null) {
513                             mProgressDialog.dismiss();
514                         }
515 
516                         // The fragment may be detached from its containing activity if the user exits the
517                         // app before the first wallpaper image in rotation finishes downloading.
518                         Activity activity = getActivity();
519 
520                         if (mWallpaperRotationInitializer.startRotation(appContext)) {
521                             if (activity != null) {
522                                 try {
523                                     Toast.makeText(activity,
524                                             R.string.wallpaper_set_successfully_message,
525                                             Toast.LENGTH_SHORT).show();
526                                 } catch (NotFoundException e) {
527                                     Log.e(TAG, "Could not show toast " + e);
528                                 }
529 
530                                 activity.setResult(Activity.RESULT_OK);
531                                 activity.finish();
532                                 if (!ActivityUtils.isSUWMode(appContext)) {
533                                     // Go back to launcher home.
534                                     LaunchUtils.launchHome(appContext);
535                                 }
536                             }
537                         } else { // Failed to start rotation.
538                             showStartRotationErrorDialog(networkPreference);
539                         }
540                     }
541 
542                     @Override
543                     public void onError() {
544                         if (mProgressDialog != null) {
545                             mProgressDialog.dismiss();
546                         }
547 
548                         showStartRotationErrorDialog(networkPreference);
549                     }
550                 });
551     }
552 
showStartRotationErrorDialog(@etworkPreference int networkPreference)553     private void showStartRotationErrorDialog(@NetworkPreference int networkPreference) {
554         FragmentTransactionChecker activity = (FragmentTransactionChecker) getActivity();
555         if (activity != null) {
556             StartRotationErrorDialogFragment startRotationErrorDialogFragment =
557                     StartRotationErrorDialogFragment.newInstance(networkPreference);
558             startRotationErrorDialogFragment.setTargetFragment(
559                     IndividualPickerFragment.this, UNUSED_REQUEST_CODE);
560 
561             if (activity.isSafeToCommitFragmentTransaction()) {
562                 startRotationErrorDialogFragment.show(
563                         getFragmentManager(), TAG_START_ROTATION_ERROR_DIALOG);
564             } else {
565                 mStagedStartRotationErrorDialogFragment = startRotationErrorDialogFragment;
566             }
567         }
568     }
569 
getNumColumns()570     int getNumColumns() {
571         Activity activity = getActivity();
572         if (activity == null) {
573             return 1;
574         }
575         return isFewerColumnLayout()
576                 ? SizeCalculator.getNumFeaturedIndividualColumns(activity)
577                 : SizeCalculator.getNumIndividualColumns(activity);
578     }
579 
580     /**
581      * Returns whether rotation is enabled for this category.
582      */
isRotationEnabled()583     boolean isRotationEnabled() {
584         return mWallpaperRotationInitializer != null;
585     }
586 
587     @Override
onMenuItemClick(MenuItem item)588     public boolean onMenuItemClick(MenuItem item) {
589         if (item.getItemId() == R.id.daily_rotation) {
590             showRotationDialog();
591             return true;
592         }
593         return super.onMenuItemClick(item);
594     }
595 
596     /**
597      * Popups a daily rotation dialog for the uses to confirm.
598      */
showRotationDialog()599     public void showRotationDialog() {
600         DialogFragment startRotationDialogFragment = new StartRotationDialogFragment();
601         startRotationDialogFragment.setTargetFragment(
602                 IndividualPickerFragment.this, UNUSED_REQUEST_CODE);
603         startRotationDialogFragment.show(getFragmentManager(), TAG_START_ROTATION_DIALOG);
604     }
605 
getAppliedWallpaperIds()606     private Set<String> getAppliedWallpaperIds() {
607         WallpaperPreferences prefs =
608                 InjectorProvider.getInjector().getPreferences(getContext());
609         android.app.WallpaperInfo wallpaperInfo = mWallpaperManager.getWallpaperInfo();
610         Set<String> appliedWallpaperIds = new ArraySet<>();
611 
612         String homeWallpaperId = wallpaperInfo != null ? wallpaperInfo.getServiceName()
613                 : prefs.getHomeWallpaperRemoteId();
614         if (!TextUtils.isEmpty(homeWallpaperId)) {
615             appliedWallpaperIds.add(homeWallpaperId);
616         }
617 
618         boolean isLockWallpaperApplied =
619                 mWallpaperManager.getWallpaperId(WallpaperManager.FLAG_LOCK) >= 0;
620         String lockWallpaperId = prefs.getLockWallpaperRemoteId();
621         if (isLockWallpaperApplied && !TextUtils.isEmpty(lockWallpaperId)) {
622             appliedWallpaperIds.add(lockWallpaperId);
623         }
624 
625         return appliedWallpaperIds;
626     }
627 
628     /**
629      * RecyclerView Adapter subclass for the wallpaper tiles in the RecyclerView.
630      */
631     class IndividualAdapter extends RecyclerView.Adapter<ViewHolder> {
632         static final int ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER = 2;
633         static final int ITEM_VIEW_TYPE_MY_PHOTOS = 3;
634 
635         private final List<WallpaperInfo> mWallpapers;
636 
IndividualAdapter(List<WallpaperInfo> wallpapers)637         IndividualAdapter(List<WallpaperInfo> wallpapers) {
638             mWallpapers = wallpapers;
639         }
640 
641         @Override
onCreateViewHolder(ViewGroup parent, int viewType)642         public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
643             switch (viewType) {
644                 case ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER:
645                     return createIndividualHolder(parent);
646                 case ITEM_VIEW_TYPE_MY_PHOTOS:
647                     return createMyPhotosHolder(parent);
648                 default:
649                     Log.e(TAG, "Unsupported viewType " + viewType + " in IndividualAdapter");
650                     return null;
651             }
652         }
653 
654         @Override
getItemViewType(int position)655         public int getItemViewType(int position) {
656             // A category cannot have both a "start rotation" tile and a "my photos" tile.
657             if (mCategory.supportsCustomPhotos()
658                     && !isRotationEnabled()
659                     && position == SPECIAL_FIXED_TILE_ADAPTER_POSITION) {
660                 return ITEM_VIEW_TYPE_MY_PHOTOS;
661             }
662 
663             return ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER;
664         }
665 
666         @Override
onBindViewHolder(ViewHolder holder, int position)667         public void onBindViewHolder(ViewHolder holder, int position) {
668             int viewType = getItemViewType(position);
669 
670             switch (viewType) {
671                 case ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER:
672                     onBindIndividualHolder(holder, position);
673                     break;
674                 case ITEM_VIEW_TYPE_MY_PHOTOS:
675                     ((MyPhotosViewHolder) holder).bind();
676                     break;
677                 default:
678                     Log.e(TAG, "Unsupported viewType " + viewType + " in IndividualAdapter");
679             }
680         }
681 
682         @Override
getItemCount()683         public int getItemCount() {
684             return mCategory.supportsCustomPhotos() ? mWallpapers.size() + 1 : mWallpapers.size();
685         }
686 
createIndividualHolder(ViewGroup parent)687         private ViewHolder createIndividualHolder(ViewGroup parent) {
688             LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
689             View view = layoutInflater.inflate(R.layout.grid_item_image, parent, false);
690 
691             return new PreviewIndividualHolder(getActivity(), mTileSizePx.y, view);
692         }
693 
createMyPhotosHolder(ViewGroup parent)694         private ViewHolder createMyPhotosHolder(ViewGroup parent) {
695             LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
696             View view = layoutInflater.inflate(R.layout.grid_item_my_photos, parent, false);
697 
698             return new MyPhotosViewHolder(getActivity(),
699                     ((MyPhotosStarterProvider) getActivity()).getMyPhotosStarter(),
700                     mTileSizePx.y, view);
701         }
702 
onBindIndividualHolder(ViewHolder holder, int position)703         void onBindIndividualHolder(ViewHolder holder, int position) {
704             int wallpaperIndex = mCategory.supportsCustomPhotos() ? position - 1 : position;
705             WallpaperInfo wallpaper = mWallpapers.get(wallpaperIndex);
706             wallpaper.computeColorInfo(holder.itemView.getContext());
707             ((IndividualHolder) holder).bindWallpaper(wallpaper);
708             boolean isWallpaperApplied = isWallpaperApplied(wallpaper);
709 
710             CardView container = holder.itemView.findViewById(R.id.wallpaper_container);
711             int radiusId = isFewerColumnLayout() ? R.dimen.grid_item_all_radius
712                     : R.dimen.grid_item_all_radius_small;
713             container.setRadius(getResources().getDimension(radiusId));
714             showBadge(holder, R.drawable.wallpaper_check_circle_24dp, isWallpaperApplied);
715         }
716 
isWallpaperApplied(WallpaperInfo wallpaper)717         protected boolean isWallpaperApplied(WallpaperInfo wallpaper) {
718             return mAppliedWallpaperIds.contains(wallpaper.getWallpaperId());
719         }
720 
showBadge(ViewHolder holder, @DrawableRes int icon, boolean show)721         protected void showBadge(ViewHolder holder, @DrawableRes int icon, boolean show) {
722             ImageView badge = holder.itemView.findViewById(R.id.indicator_icon);
723             if (show) {
724                 final float margin = isFewerColumnLayout() ? getResources().getDimension(
725                         R.dimen.grid_item_badge_margin) : getResources().getDimension(
726                         R.dimen.grid_item_badge_margin_small);
727                 final RelativeLayout.LayoutParams layoutParams =
728                         (RelativeLayout.LayoutParams) badge.getLayoutParams();
729                 layoutParams.setMargins(/* left= */ (int) margin, /* top= */ (int) margin,
730                         /* right= */ (int) margin, /* bottom= */ (int) margin);
731                 badge.setLayoutParams(layoutParams);
732                 badge.setBackgroundResource(icon);
733                 badge.setVisibility(View.VISIBLE);
734             } else {
735                 badge.setVisibility(View.GONE);
736             }
737         }
738     }
739 }
740