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