• 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;
17 
18 import android.app.Activity;
19 import android.app.ProgressDialog;
20 import android.content.Context;
21 import android.content.DialogInterface;
22 import android.content.Intent;
23 import android.content.pm.PackageManager;
24 import android.graphics.Point;
25 import android.graphics.PorterDuff.Mode;
26 import android.net.Uri;
27 import android.os.Build.VERSION;
28 import android.os.Build.VERSION_CODES;
29 import android.os.Bundle;
30 import android.provider.Settings;
31 import android.util.DisplayMetrics;
32 import android.util.Log;
33 import android.view.Display;
34 import android.view.LayoutInflater;
35 import android.view.View;
36 import android.view.View.OnClickListener;
37 import android.view.ViewGroup;
38 import android.widget.Button;
39 import android.widget.FrameLayout;
40 import android.widget.ImageButton;
41 import android.widget.ImageView;
42 import android.widget.ProgressBar;
43 import android.widget.RelativeLayout;
44 import android.widget.TextView;
45 
46 import androidx.annotation.Nullable;
47 import androidx.appcompat.app.AlertDialog;
48 import androidx.recyclerview.widget.GridLayoutManager;
49 import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup;
50 import androidx.recyclerview.widget.RecyclerView;
51 import androidx.recyclerview.widget.RecyclerView.ViewHolder;
52 
53 import com.android.wallpaper.R;
54 import com.android.wallpaper.asset.Asset;
55 import com.android.wallpaper.config.Flags;
56 import com.android.wallpaper.model.Category;
57 import com.android.wallpaper.model.WallpaperInfo;
58 import com.android.wallpaper.module.CurrentWallpaperInfoFactory;
59 import com.android.wallpaper.module.CurrentWallpaperInfoFactory.WallpaperInfoCallback;
60 import com.android.wallpaper.module.ExploreIntentChecker;
61 import com.android.wallpaper.module.InjectorProvider;
62 import com.android.wallpaper.module.LockWallpaperStatusChecker;
63 import com.android.wallpaper.module.UserEventLogger;
64 import com.android.wallpaper.module.WallpaperPreferences;
65 import com.android.wallpaper.module.WallpaperPreferences.PresentationMode;
66 import com.android.wallpaper.module.WallpaperRotationRefresher;
67 import com.android.wallpaper.module.WallpaperRotationRefresher.Listener;
68 import com.android.wallpaper.picker.MyPhotosStarter.MyPhotosStarterProvider;
69 import com.android.wallpaper.picker.MyPhotosStarter.PermissionChangedListener;
70 import com.android.wallpaper.util.DisplayMetricsRetriever;
71 import com.android.wallpaper.util.ScreenSizeCalculator;
72 import com.android.wallpaper.util.TileSizeCalculator;
73 import com.android.wallpaper.widget.GridMarginDecoration;
74 
75 import com.bumptech.glide.Glide;
76 import com.bumptech.glide.MemoryCategory;
77 
78 import java.util.ArrayList;
79 import java.util.Date;
80 import java.util.List;
81 
82 /**
83  * Displays the Main UI for picking a category of wallpapers to choose from.
84  */
85 public class CategoryFragment extends ToolbarFragment {
86 
87     /**
88      * Interface to be implemented by an Activity hosting a {@link CategoryFragment}
89      */
90     public interface CategoryFragmentHost extends MyPhotosStarterProvider {
91 
requestExternalStoragePermission(PermissionChangedListener listener)92         void requestExternalStoragePermission(PermissionChangedListener listener);
93 
isReadExternalStoragePermissionGranted()94         boolean isReadExternalStoragePermissionGranted();
95 
showViewOnlyPreview(WallpaperInfo wallpaperInfo)96         void showViewOnlyPreview(WallpaperInfo wallpaperInfo);
97 
show(String collectionId)98         void show(String collectionId);
99     }
100 
newInstance(CharSequence title)101     public static CategoryFragment newInstance(CharSequence title) {
102         CategoryFragment fragment = new CategoryFragment();
103         fragment.setArguments(ToolbarFragment.createArguments(title));
104         return fragment;
105     }
106 
107     private static final String TAG = "CategoryFragment";
108 
109     // The number of ViewHolders that don't pertain to category tiles.
110     // Currently 2: one for the metadata section and one for the "Select wallpaper" header.
111     private static final int NUM_NON_CATEGORY_VIEW_HOLDERS = 2;
112 
113     /**
114      * The fixed RecyclerView.Adapter position of the ViewHolder for the initial item in the grid --
115      * usually the wallpaper metadata, or a "permission needed" warning UI.
116      */
117     private static final int INITIAL_HOLDER_ADAPTER_POSITION = 0;
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 RecyclerView mImageGrid;
125     private CategoryAdapter mAdapter;
126     private ArrayList<Category> mCategories = new ArrayList<>();
127     private Point mTileSizePx;
128     private boolean mAwaitingCategories;
129     private ProgressDialog mRefreshWallpaperProgressDialog;
130     private boolean mTestingMode;
131 
CategoryFragment()132     public CategoryFragment() {
133     }
134 
135     @Override
onCreate(Bundle savedInstanceState)136     public void onCreate(Bundle savedInstanceState) {
137         super.onCreate(savedInstanceState);
138         mAdapter = new CategoryAdapter(mCategories);
139     }
140 
141     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)142     public View onCreateView(LayoutInflater inflater, ViewGroup container,
143                              Bundle savedInstanceState) {
144         View view = inflater.inflate(
145                 R.layout.fragment_category_picker, container, /* attachToRoot */ false);
146 
147         mImageGrid = view.findViewById(R.id.category_grid);
148         GridMarginDecoration.applyTo(mImageGrid);
149 
150         mTileSizePx = TileSizeCalculator.getCategoryTileSize(getActivity());
151 
152         if (LockWallpaperStatusChecker.isLockWallpaperSet(getContext())) {
153             mAdapter.setNumMetadataCards(CategoryAdapter.METADATA_VIEW_TWO_CARDS);
154         } else {
155             mAdapter.setNumMetadataCards(CategoryAdapter.METADATA_VIEW_SINGLE_CARD);
156         }
157         mImageGrid.setAdapter(mAdapter);
158 
159         GridLayoutManager gridLayoutManager = new GridLayoutManager(getActivity(), getNumColumns());
160         gridLayoutManager.setSpanSizeLookup(new CategorySpanSizeLookup(mAdapter));
161         mImageGrid.setLayoutManager(gridLayoutManager);
162         setUpToolbar(view);
163         return view;
164     }
165 
166     @Override
getDefaultTitle()167     public CharSequence getDefaultTitle() {
168         return getContext().getString(R.string.app_name);
169     }
170 
171     @Override
onResume()172     public void onResume() {
173         super.onResume();
174 
175         WallpaperPreferences preferences = InjectorProvider.getInjector().getPreferences(getActivity());
176         preferences.setLastAppActiveTimestamp(new Date().getTime());
177 
178         // Reset Glide memory settings to a "normal" level of usage since it may have been lowered in
179         // PreviewFragment.
180         Glide.get(getActivity()).setMemoryCategory(MemoryCategory.NORMAL);
181 
182         // Refresh metadata since it may have changed since the activity was paused.
183         ViewHolder initialViewHolder =
184                 mImageGrid.findViewHolderForAdapterPosition(INITIAL_HOLDER_ADAPTER_POSITION);
185         MetadataHolder metadataHolder = null;
186         if (initialViewHolder instanceof MetadataHolder) {
187             metadataHolder = (MetadataHolder) initialViewHolder;
188         }
189 
190         // The wallpaper may have been set while this fragment was paused, so force refresh the current
191         // wallpapers and presentation mode.
192         refreshCurrentWallpapers(metadataHolder, true /* forceRefresh */);
193     }
194 
195     @Override
onDestroy()196     public void onDestroy() {
197         super.onDestroy();
198         if (mRefreshWallpaperProgressDialog != null) {
199             mRefreshWallpaperProgressDialog.dismiss();
200         }
201     }
202 
203     @Override
onActivityResult(int requestCode, int resultCode, Intent data)204     public void onActivityResult(int requestCode, int resultCode, Intent data) {
205         if (requestCode == SETTINGS_APP_INFO_REQUEST_CODE) {
206             mAdapter.notifyDataSetChanged();
207         }
208     }
209 
210     /**
211      * Inserts the given category into the categories list in priority order.
212      */
addCategory(Category category, boolean loading)213     public void addCategory(Category category, boolean loading) {
214         // If not previously waiting for categories, enter the waiting state by showing the loading
215         // indicator.
216         if (loading && !mAwaitingCategories) {
217             mAdapter.notifyItemChanged(getNumColumns());
218             mAdapter.notifyItemInserted(getNumColumns());
219             mAwaitingCategories = true;
220         }
221         // Not add existing category to category list
222         if (mCategories.indexOf(category) >= 0) {
223             return;
224         }
225 
226         int priority = category.getPriority();
227 
228         int index = 0;
229         while (index < mCategories.size() && priority >= mCategories.get(index).getPriority()) {
230             index++;
231         }
232 
233         mCategories.add(index, category);
234         if (mAdapter != null) {
235             // Offset the index because of the static metadata element at beginning of RecyclerView.
236             mAdapter.notifyItemInserted(index + NUM_NON_CATEGORY_VIEW_HOLDERS);
237         }
238     }
239 
removeCategory(Category category)240     public void removeCategory(Category category) {
241         int index = mCategories.indexOf(category);
242         if (index >= 0) {
243             mCategories.remove(index);
244             mAdapter.notifyItemRemoved(index + NUM_NON_CATEGORY_VIEW_HOLDERS);
245         }
246     }
247 
updateCategory(Category category)248     public void updateCategory(Category category) {
249         int index = mCategories.indexOf(category);
250         if (index >= 0) {
251             mCategories.remove(index);
252             mCategories.add(index, category);
253             mAdapter.notifyItemChanged(index + NUM_NON_CATEGORY_VIEW_HOLDERS);
254         }
255     }
256 
clearCategories()257     public void clearCategories() {
258         mCategories.clear();
259         mAdapter.notifyDataSetChanged();
260     }
261 
262     /**
263      * Notifies the CategoryFragment that no further categories are expected so it may hide
264      * the loading indicator.
265      */
doneFetchingCategories()266     public void doneFetchingCategories() {
267         if (mAwaitingCategories) {
268             mAdapter.notifyItemRemoved(mAdapter.getItemCount() - 1);
269             mAwaitingCategories = false;
270         }
271     }
272 
273     /**
274      * Enable a test mode of operation -- in which certain UI features are disabled to allow for
275      * UI tests to run correctly. Works around issue in ProgressDialog currently where the dialog
276      * constantly keeps the UI thread alive and blocks a test forever.
277      */
setTestingMode(boolean testingMode)278     void setTestingMode(boolean testingMode) {
279         mTestingMode = testingMode;
280     }
281 
canShowCurrentWallpaper()282     private boolean canShowCurrentWallpaper() {
283         Activity activity = getActivity();
284         CategoryFragmentHost host = getFragmentHost();
285         PackageManager packageManager = activity.getPackageManager();
286         String packageName = activity.getPackageName();
287 
288         boolean hasReadWallpaperInternal = packageManager.checkPermission(
289                 PERMISSION_READ_WALLPAPER_INTERNAL, packageName) == PackageManager.PERMISSION_GRANTED;
290         return hasReadWallpaperInternal || host.isReadExternalStoragePermissionGranted();
291     }
292 
getFragmentHost()293     private CategoryFragmentHost getFragmentHost() {
294         return (CategoryFragmentHost) getActivity();
295     }
296 
297     /**
298      * Obtains the {@link WallpaperInfo} object(s) representing the wallpaper(s) currently set to the
299      * device from the {@link CurrentWallpaperInfoFactory} and binds them to the provided
300      * {@link MetadataHolder}.
301      */
refreshCurrentWallpapers(@ullable final MetadataHolder holder, boolean forceRefresh)302     private void refreshCurrentWallpapers(@Nullable final MetadataHolder holder,
303                                           boolean forceRefresh) {
304         CurrentWallpaperInfoFactory factory = InjectorProvider.getInjector()
305                 .getCurrentWallpaperFactory(getActivity().getApplicationContext());
306 
307         factory.createCurrentWallpaperInfos(new WallpaperInfoCallback() {
308             @Override
309             public void onWallpaperInfoCreated(
310                     final WallpaperInfo homeWallpaper,
311                     @Nullable final WallpaperInfo lockWallpaper,
312                     @PresentationMode final int presentationMode) {
313 
314                 // Update the metadata displayed on screen. Do this in a Handler so it is scheduled at the
315                 // end of the message queue. This is necessary to ensure we do not remove or add data from
316                 // the adapter while the layout is being computed. RecyclerView documentation therefore
317                 // recommends performing such changes in a Handler.
318                 new android.os.Handler().post(new Runnable() {
319                     @Override
320                     public void run() {
321                         // A config change may have destroyed the activity since the refresh started, so check
322                         // for that.
323                         if (getActivity() == null) {
324                             return;
325                         }
326 
327                         int numMetadataCards = (lockWallpaper == null)
328                                 ? CategoryAdapter.METADATA_VIEW_SINGLE_CARD
329                                 : CategoryAdapter.METADATA_VIEW_TWO_CARDS;
330                         mAdapter.setNumMetadataCards(numMetadataCards);
331 
332                         // The MetadataHolder may be null if the RecyclerView has not yet created the view
333                         // holder.
334                         if (holder != null) {
335                             holder.bindWallpapers(homeWallpaper, lockWallpaper, presentationMode);
336                         }
337                     }
338                 });
339             }
340         }, forceRefresh);
341     }
342 
getNumColumns()343     private int getNumColumns() {
344         return TileSizeCalculator.getNumCategoryColumns(getActivity());
345     }
346 
347     /**
348      * Returns the width to use for the home screen wallpaper in the "single metadata" configuration.
349      */
getSingleWallpaperImageWidth()350     private int getSingleWallpaperImageWidth() {
351         Point screenSize = ScreenSizeCalculator.getInstance()
352                 .getScreenSize(getActivity().getWindowManager().getDefaultDisplay());
353 
354         int height = getResources().getDimensionPixelSize(R.dimen.single_metadata_card_layout_height);
355         return height * screenSize.x / screenSize.y;
356     }
357 
358     /**
359      * Refreshes the current wallpaper in a daily wallpaper rotation.
360      */
refreshDailyWallpaper()361     private void refreshDailyWallpaper() {
362         // ProgressDialog endlessly updates the UI thread, keeping it from going idle which therefore
363         // causes Espresso to hang once the dialog is shown.
364         if (!mTestingMode) {
365             int themeResId;
366             if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
367                 themeResId = R.style.ProgressDialogThemePreL;
368             } else {
369                 themeResId = R.style.LightDialogTheme;
370             }
371             mRefreshWallpaperProgressDialog = new ProgressDialog(getActivity(), themeResId);
372             mRefreshWallpaperProgressDialog.setTitle(null);
373             mRefreshWallpaperProgressDialog.setMessage(
374                     getResources().getString(R.string.refreshing_daily_wallpaper_dialog_message));
375             mRefreshWallpaperProgressDialog.setIndeterminate(true);
376             mRefreshWallpaperProgressDialog.setCancelable(false);
377             mRefreshWallpaperProgressDialog.show();
378         }
379 
380         WallpaperRotationRefresher wallpaperRotationRefresher =
381                 InjectorProvider.getInjector().getWallpaperRotationRefresher();
382         wallpaperRotationRefresher.refreshWallpaper(getContext(), new Listener() {
383             @Override
384             public void onRefreshed() {
385                 // If the fragment is detached from the activity there's nothing to do here and the UI will
386                 // update when the fragment is resumed.
387                 if (getActivity() == null) {
388                     return;
389                 }
390 
391                 if (mRefreshWallpaperProgressDialog != null) {
392                     mRefreshWallpaperProgressDialog.dismiss();
393                 }
394 
395                 ViewHolder initialViewHolder =
396                         mImageGrid.findViewHolderForAdapterPosition(INITIAL_HOLDER_ADAPTER_POSITION);
397                 if (initialViewHolder instanceof MetadataHolder) {
398                     MetadataHolder metadataHolder = (MetadataHolder) initialViewHolder;
399                     // Update the metadata pane since we know now the UI there is stale.
400                     refreshCurrentWallpapers(metadataHolder, true /* forceRefresh */);
401                 }
402             }
403 
404             @Override
405             public void onError() {
406                 if (getActivity() == null) {
407                     return;
408                 }
409 
410                 if (mRefreshWallpaperProgressDialog != null) {
411                     mRefreshWallpaperProgressDialog.dismiss();
412                 }
413 
414                 AlertDialog errorDialog = new AlertDialog.Builder(getActivity(), R.style.LightDialogTheme)
415                         .setMessage(R.string.refresh_daily_wallpaper_failed_message)
416                         .setPositiveButton(android.R.string.ok, null /* onClickListener */)
417                         .create();
418                 errorDialog.show();
419             }
420         });
421     }
422 
423     /**
424      * Returns the width to use for the home and lock screen wallpapers in the "both metadata"
425      * configuration.
426      */
getBothWallpaperImageWidth()427     private int getBothWallpaperImageWidth() {
428         DisplayMetrics metrics = DisplayMetricsRetriever.getInstance().getDisplayMetrics(getResources(),
429                 getActivity().getWindowManager().getDefaultDisplay());
430 
431         // In the "both metadata" configuration, wallpaper images minus the gutters account for the full
432         // width of the device's screen.
433         return metrics.widthPixels - (3 * getResources().getDimensionPixelSize(R.dimen.grid_padding));
434     }
435 
436     private interface MetadataHolder {
437         /**
438          * Binds {@link WallpaperInfo} objects representing the currently-set wallpapers to the
439          * ViewHolder layout.
440          */
bindWallpapers(WallpaperInfo homeWallpaper, WallpaperInfo lockWallpaper, @PresentationMode int presentationMode)441         void bindWallpapers(WallpaperInfo homeWallpaper, WallpaperInfo lockWallpaper,
442                             @PresentationMode int presentationMode);
443     }
444 
445     private static class SelectWallpaperHeaderHolder extends RecyclerView.ViewHolder {
SelectWallpaperHeaderHolder(View headerView)446         public SelectWallpaperHeaderHolder(View headerView) {
447             super(headerView);
448         }
449     }
450 
451     /**
452      * SpanSizeLookup subclass which provides that the item in the first position spans the number of
453      * columns in the RecyclerView and all other items only take up a single span.
454      */
455     private class CategorySpanSizeLookup extends SpanSizeLookup {
456         CategoryAdapter mAdapter;
457 
CategorySpanSizeLookup(CategoryAdapter adapter)458         public CategorySpanSizeLookup(CategoryAdapter adapter) {
459             mAdapter = adapter;
460         }
461 
462         @Override
getSpanSize(int position)463         public int getSpanSize(int position) {
464             if (position < NUM_NON_CATEGORY_VIEW_HOLDERS
465                     || mAdapter.getItemViewType(position)
466                     == CategoryAdapter.ITEM_VIEW_TYPE_LOADING_INDICATOR) {
467                 return getNumColumns();
468             }
469 
470             return 1;
471         }
472     }
473 
474     /**
475      * ViewHolder subclass for a metadata "card" at the beginning of the RecyclerView.
476      */
477     private class SingleWallpaperMetadataHolder extends RecyclerView.ViewHolder
478             implements MetadataHolder {
479         private WallpaperInfo mWallpaperInfo;
480         private ImageView mWallpaperImage;
481         private TextView mWallpaperPresentationModeSubtitle;
482         private TextView mWallpaperTitle;
483         private TextView mWallpaperSubtitle;
484         private TextView mWallpaperSubtitle2;
485         private ImageButton mWallpaperExploreButtonNoText;
486         private ImageButton mSkipWallpaperButton;
487 
SingleWallpaperMetadataHolder(View metadataView)488         public SingleWallpaperMetadataHolder(View metadataView) {
489             super(metadataView);
490 
491             mWallpaperImage = metadataView.findViewById(R.id.wallpaper_image);
492             mWallpaperImage.getLayoutParams().width = getSingleWallpaperImageWidth();
493 
494             mWallpaperPresentationModeSubtitle =
495                     metadataView.findViewById(R.id.wallpaper_presentation_mode_subtitle);
496             mWallpaperTitle = metadataView.findViewById(R.id.wallpaper_title);
497             mWallpaperSubtitle = metadataView.findViewById(R.id.wallpaper_subtitle);
498             mWallpaperSubtitle2 = metadataView.findViewById(R.id.wallpaper_subtitle2);
499 
500             mWallpaperExploreButtonNoText =
501                     metadataView.findViewById(R.id.wallpaper_explore_button_notext);
502 
503             mSkipWallpaperButton = metadataView.findViewById(R.id.skip_wallpaper_button);
504         }
505 
506         /**
507          * Binds home screen wallpaper to the ViewHolder layout.
508          */
509         @Override
bindWallpapers(WallpaperInfo homeWallpaper, WallpaperInfo lockWallpaper, @PresentationMode int presentationMode)510         public void bindWallpapers(WallpaperInfo homeWallpaper, WallpaperInfo lockWallpaper,
511                 @PresentationMode int presentationMode) {
512             mWallpaperInfo = homeWallpaper;
513 
514             bindWallpaperAsset();
515             bindWallpaperText(presentationMode);
516             bindWallpaperActionButtons(presentationMode);
517         }
518 
bindWallpaperAsset()519         private void bindWallpaperAsset() {
520             final UserEventLogger eventLogger =
521                     InjectorProvider.getInjector().getUserEventLogger(getActivity());
522 
523             mWallpaperInfo.getThumbAsset(getActivity().getApplicationContext()).loadDrawable(
524                     getActivity(), mWallpaperImage, getResources().getColor(R.color.secondary_color));
525 
526             mWallpaperImage.setOnClickListener(new OnClickListener() {
527                 @Override
528                 public void onClick(View v) {
529                     getFragmentHost().showViewOnlyPreview(mWallpaperInfo);
530                     eventLogger.logCurrentWallpaperPreviewed();
531                 }
532             });
533         }
534 
bindWallpaperText(@resentationMode int presentationMode)535         private void bindWallpaperText(@PresentationMode int presentationMode) {
536             Context appContext = getActivity().getApplicationContext();
537 
538             mWallpaperPresentationModeSubtitle.setText(
539                     AttributionFormatter.getHumanReadableWallpaperPresentationMode(
540                             appContext, presentationMode));
541 
542             List<String> attributions = mWallpaperInfo.getAttributions(appContext);
543             if (!attributions.isEmpty()) {
544                 mWallpaperTitle.setText(attributions.get(0));
545             }
546             if (attributions.size() > 1) {
547                 mWallpaperSubtitle.setText(attributions.get(1));
548             } else {
549                 mWallpaperSubtitle.setVisibility(View.INVISIBLE);
550             }
551             if (attributions.size() > 2) {
552                 mWallpaperSubtitle2.setText(attributions.get(2));
553             } else {
554                 mWallpaperSubtitle2.setVisibility(View.INVISIBLE);
555             }
556         }
557 
bindWallpaperActionButtons(@resentationMode int presentationMode)558         private void bindWallpaperActionButtons(@PresentationMode int presentationMode) {
559             final Context appContext = getActivity().getApplicationContext();
560 
561             final String actionUrl = mWallpaperInfo.getActionUrl(appContext);
562             if (actionUrl != null && !actionUrl.isEmpty()) {
563 
564                 Uri exploreUri = Uri.parse(actionUrl);
565 
566                 ExploreIntentChecker intentChecker =
567                         InjectorProvider.getInjector().getExploreIntentChecker(appContext);
568                 intentChecker.fetchValidActionViewIntent(exploreUri, (@Nullable Intent exploreIntent) -> {
569                     if (getActivity() == null) {
570                         return;
571                     }
572 
573                     updateExploreSectionVisibility(presentationMode, exploreIntent);
574                 });
575             } else {
576                 updateExploreSectionVisibility(presentationMode, null /* exploreIntent */);
577             }
578         }
579 
580         /**
581          * Shows or hides appropriate elements in the "Explore section" (containing the Explore button
582          * and the Next Wallpaper button) depending on the current wallpaper.
583          *
584          * @param presentationMode The presentation mode of the current wallpaper.
585          * @param exploreIntent    An optional explore intent for the current wallpaper.
586          */
updateExploreSectionVisibility( @resentationMode int presentationMode, @Nullable Intent exploreIntent)587         private void updateExploreSectionVisibility(
588                 @PresentationMode int presentationMode, @Nullable Intent exploreIntent) {
589 
590             final Context appContext = getActivity().getApplicationContext();
591             final UserEventLogger eventLogger =
592                     InjectorProvider.getInjector().getUserEventLogger(appContext);
593 
594             boolean showSkipWallpaperButton = Flags.skipDailyWallpaperButtonEnabled
595                     && presentationMode == WallpaperPreferences.PRESENTATION_MODE_ROTATING;
596 
597             if (exploreIntent != null) {
598                 mWallpaperExploreButtonNoText.setImageDrawable(getContext().getDrawable(
599                                 mWallpaperInfo.getActionIconRes(appContext)));
600                 mWallpaperExploreButtonNoText.setContentDescription(
601                                 getString(mWallpaperInfo.getActionLabelRes(appContext)));
602                 mWallpaperExploreButtonNoText.setColorFilter(
603                                 getResources().getColor(R.color.currently_set_explore_button_color,
604                                         getContext().getTheme()),
605                                 Mode.SRC_IN);
606                 mWallpaperExploreButtonNoText.setVisibility(View.VISIBLE);
607                 mWallpaperExploreButtonNoText.setOnClickListener((View view) -> {
608                     eventLogger.logActionClicked(mWallpaperInfo.getCollectionId(appContext),
609                            mWallpaperInfo.getActionLabelRes(appContext));
610                     startActivity(exploreIntent);
611                 });
612             }
613 
614             if (showSkipWallpaperButton) {
615                 mSkipWallpaperButton.setVisibility(View.VISIBLE);
616                 mSkipWallpaperButton.setOnClickListener((View view) -> refreshDailyWallpaper());
617             }
618         }
619     }
620 
621     /**
622      * ViewHolder subclass for a metadata "card" at the beginning of the RecyclerView that shows
623      * both home screen and lock screen wallpapers.
624      */
625     private class TwoWallpapersMetadataHolder extends RecyclerView.ViewHolder
626             implements MetadataHolder {
627         private WallpaperInfo mHomeWallpaperInfo;
628         private ImageView mHomeWallpaperImage;
629         private TextView mHomeWallpaperPresentationMode;
630         private TextView mHomeWallpaperTitle;
631         private TextView mHomeWallpaperSubtitle1;
632         private TextView mHomeWallpaperSubtitle2;
633 
634         private ImageButton mHomeWallpaperExploreButton;
635         private ImageButton mSkipWallpaperButton;
636         private ViewGroup mHomeWallpaperPresentationSection;
637 
638         private WallpaperInfo mLockWallpaperInfo;
639         private ImageView mLockWallpaperImage;
640         private TextView mLockWallpaperTitle;
641         private TextView mLockWallpaperSubtitle1;
642         private TextView mLockWallpaperSubtitle2;
643 
644         private ImageButton mLockWallpaperExploreButton;
645 
TwoWallpapersMetadataHolder(View metadataView)646         public TwoWallpapersMetadataHolder(View metadataView) {
647             super(metadataView);
648 
649             // Set the min width of the metadata panel to be the screen width minus space for the
650             // 2 gutters on the sides. This ensures the RecyclerView's GridLayoutManager gives it
651             // a wide-enough initial width to fill up the width of the grid prior to the view being
652             // fully populated.
653             final Display display = getActivity().getWindowManager().getDefaultDisplay();
654             Point screenSize = ScreenSizeCalculator.getInstance().getScreenSize(display);
655             metadataView.setMinimumWidth(
656                     screenSize.x - 2 * getResources().getDimensionPixelSize(R.dimen.grid_padding));
657 
658             int bothWallpaperImageWidth = getBothWallpaperImageWidth();
659 
660             FrameLayout homeWallpaperSection = metadataView.findViewById(
661                     R.id.home_wallpaper_section);
662             homeWallpaperSection.setMinimumWidth(bothWallpaperImageWidth);
663             mHomeWallpaperImage = metadataView.findViewById(R.id.home_wallpaper_image);
664 
665             mHomeWallpaperPresentationMode =
666                     metadataView.findViewById(R.id.home_wallpaper_presentation_mode);
667             mHomeWallpaperTitle = metadataView.findViewById(R.id.home_wallpaper_title);
668             mHomeWallpaperSubtitle1 = metadataView.findViewById(R.id.home_wallpaper_subtitle1);
669             mHomeWallpaperSubtitle2 = metadataView.findViewById(R.id.home_wallpaper_subtitle2);
670             mHomeWallpaperPresentationSection = metadataView.findViewById(
671                     R.id.home_wallpaper_presentation_section);
672             mHomeWallpaperExploreButton =
673                     metadataView.findViewById(R.id.home_wallpaper_explore_button);
674             mSkipWallpaperButton = metadataView.findViewById(R.id.skip_home_wallpaper);
675 
676             FrameLayout lockWallpaperSection = metadataView.findViewById(
677                     R.id.lock_wallpaper_section);
678             lockWallpaperSection.setMinimumWidth(bothWallpaperImageWidth);
679             mLockWallpaperImage = metadataView.findViewById(R.id.lock_wallpaper_image);
680 
681             mLockWallpaperTitle = metadataView.findViewById(R.id.lock_wallpaper_title);
682             mLockWallpaperSubtitle1 = metadataView.findViewById(R.id.lock_wallpaper_subtitle1);
683             mLockWallpaperSubtitle2 = metadataView.findViewById(R.id.lock_wallpaper_subtitle2);
684             mLockWallpaperExploreButton =
685                     metadataView.findViewById(R.id.lock_wallpaper_explore_button);
686         }
687 
688         @Override
bindWallpapers(WallpaperInfo homeWallpaper, WallpaperInfo lockWallpaper, @PresentationMode int presentationMode)689         public void bindWallpapers(WallpaperInfo homeWallpaper, WallpaperInfo lockWallpaper,
690                 @PresentationMode int presentationMode) {
691             bindHomeWallpaper(homeWallpaper, presentationMode);
692             bindLockWallpaper(lockWallpaper);
693         }
694 
bindHomeWallpaper(WallpaperInfo homeWallpaper, @PresentationMode int presentationMode)695         private void bindHomeWallpaper(WallpaperInfo homeWallpaper,
696                                        @PresentationMode int presentationMode) {
697             final Context appContext = getActivity().getApplicationContext();
698             final UserEventLogger eventLogger =
699                     InjectorProvider.getInjector().getUserEventLogger(appContext);
700 
701             mHomeWallpaperInfo = homeWallpaper;
702 
703             homeWallpaper.getThumbAsset(appContext).loadDrawable(
704                     getActivity(), mHomeWallpaperImage,
705                     getResources().getColor(R.color.secondary_color, getContext().getTheme()));
706 
707             mHomeWallpaperPresentationMode.setText(
708                     AttributionFormatter.getHumanReadableWallpaperPresentationMode(
709                             appContext, presentationMode));
710 
711             List<String> attributions = homeWallpaper.getAttributions(appContext);
712             if (!attributions.isEmpty()) {
713                 mHomeWallpaperTitle.setText(attributions.get(0));
714             }
715             if (attributions.size() > 1) {
716                 mHomeWallpaperSubtitle1.setText(attributions.get(1));
717             }
718             if (attributions.size() > 2) {
719                 mHomeWallpaperSubtitle2.setText(attributions.get(2));
720             }
721 
722             final String homeActionUrl = homeWallpaper.getActionUrl(appContext);
723 
724             if (homeActionUrl != null && !homeActionUrl.isEmpty()) {
725                 Uri homeExploreUri = Uri.parse(homeActionUrl);
726 
727                 ExploreIntentChecker intentChecker =
728                         InjectorProvider.getInjector().getExploreIntentChecker(appContext);
729 
730                 intentChecker.fetchValidActionViewIntent(
731                     homeExploreUri, (@Nullable Intent exploreIntent) -> {
732                         if (exploreIntent == null || getActivity() == null) {
733                             return;
734                         }
735 
736                         mHomeWallpaperExploreButton.setVisibility(View.VISIBLE);
737                         mHomeWallpaperExploreButton.setImageDrawable(getContext().getDrawable(
738                                 homeWallpaper.getActionIconRes(appContext)));
739                         mHomeWallpaperExploreButton.setContentDescription(getString(homeWallpaper
740                                 .getActionLabelRes(appContext)));
741                         mHomeWallpaperExploreButton.setColorFilter(
742                                 getResources().getColor(R.color.currently_set_explore_button_color,
743                                         getContext().getTheme()),
744                                 Mode.SRC_IN);
745                         mHomeWallpaperExploreButton.setOnClickListener(v -> {
746                             eventLogger.logActionClicked(
747                                     mHomeWallpaperInfo.getCollectionId(appContext),
748                                     mHomeWallpaperInfo.getActionLabelRes(appContext));
749                             startActivity(exploreIntent);
750                         });
751                     });
752             } else {
753                 mHomeWallpaperExploreButton.setVisibility(View.GONE);
754             }
755 
756             if (presentationMode == WallpaperPreferences.PRESENTATION_MODE_ROTATING) {
757                 mHomeWallpaperPresentationSection.setVisibility(View.VISIBLE);
758                 if (Flags.skipDailyWallpaperButtonEnabled) {
759                     mSkipWallpaperButton.setVisibility(View.VISIBLE);
760                     mSkipWallpaperButton.setColorFilter(
761                             getResources().getColor(R.color.currently_set_explore_button_color,
762                                     getContext().getTheme()), Mode.SRC_IN);
763                     mSkipWallpaperButton.setOnClickListener(view -> refreshDailyWallpaper());
764                 } else {
765                     mSkipWallpaperButton.setVisibility(View.GONE);
766                 }
767             } else {
768                 mHomeWallpaperPresentationSection.setVisibility(View.GONE);
769             }
770 
771             mHomeWallpaperImage.setOnClickListener(v -> {
772                 eventLogger.logCurrentWallpaperPreviewed();
773                 getFragmentHost().showViewOnlyPreview(mHomeWallpaperInfo);
774             });
775         }
776 
bindLockWallpaper(WallpaperInfo lockWallpaper)777         private void bindLockWallpaper(WallpaperInfo lockWallpaper) {
778             if (lockWallpaper == null) {
779                 Log.e(TAG, "TwoWallpapersMetadataHolder bound without a lock screen wallpaper.");
780                 return;
781             }
782 
783             final Context appContext = getActivity().getApplicationContext();
784             final UserEventLogger eventLogger =
785                     InjectorProvider.getInjector().getUserEventLogger(getActivity());
786 
787             mLockWallpaperInfo = lockWallpaper;
788 
789             lockWallpaper.getThumbAsset(appContext).loadDrawable(
790                     getActivity(), mLockWallpaperImage, getResources().getColor(R.color.secondary_color));
791 
792             List<String> lockAttributions = lockWallpaper.getAttributions(appContext);
793             if (!lockAttributions.isEmpty()) {
794                 mLockWallpaperTitle.setText(lockAttributions.get(0));
795             }
796             if (lockAttributions.size() > 1) {
797                 mLockWallpaperSubtitle1.setText(lockAttributions.get(1));
798             }
799             if (lockAttributions.size() > 2) {
800                 mLockWallpaperSubtitle2.setText(lockAttributions.get(2));
801             }
802 
803             final String lockActionUrl = lockWallpaper.getActionUrl(appContext);
804 
805             if (lockActionUrl != null && !lockActionUrl.isEmpty()) {
806                 Uri lockExploreUri = Uri.parse(lockActionUrl);
807 
808                 ExploreIntentChecker intentChecker =
809                         InjectorProvider.getInjector().getExploreIntentChecker(appContext);
810                 intentChecker.fetchValidActionViewIntent(
811                         lockExploreUri, (@Nullable Intent exploreIntent) -> {
812                             if (exploreIntent == null || getActivity() == null) {
813                                 return;
814                             }
815                             mLockWallpaperExploreButton.setImageDrawable(getContext().getDrawable(
816                                     lockWallpaper.getActionIconRes(appContext)));
817                             mLockWallpaperExploreButton.setContentDescription(getString(
818                                     lockWallpaper.getActionLabelRes(appContext)));
819                             mLockWallpaperExploreButton.setVisibility(View.VISIBLE);
820                             mLockWallpaperExploreButton.setColorFilter(
821                                     getResources().getColor(
822                                             R.color.currently_set_explore_button_color),
823                                     Mode.SRC_IN);
824                             mLockWallpaperExploreButton.setOnClickListener(new OnClickListener() {
825                                 @Override
826                                 public void onClick(View v) {
827                                     eventLogger.logActionClicked(
828                                             mLockWallpaperInfo.getCollectionId(appContext),
829                                             mLockWallpaperInfo.getActionLabelRes(appContext));
830                                     startActivity(exploreIntent);
831                                 }
832                             });
833                         });
834             } else {
835                 mLockWallpaperExploreButton.setVisibility(View.GONE);
836             }
837 
838             mLockWallpaperImage.setOnClickListener(new OnClickListener() {
839                 @Override
840                 public void onClick(View v) {
841                     eventLogger.logCurrentWallpaperPreviewed();
842                     getFragmentHost().showViewOnlyPreview(mLockWallpaperInfo);
843                 }
844             });
845         }
846     }
847 
848     /**
849      * ViewHolder subclass for a category tile in the RecyclerView.
850      */
851     private class CategoryHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
852         private Category mCategory;
853         private RelativeLayout mTileLayout;
854         private ImageView mImageView;
855         private ImageView mOverlayIconView;
856         private TextView mTitleView;
857 
CategoryHolder(View itemView)858         public CategoryHolder(View itemView) {
859             super(itemView);
860             itemView.setOnClickListener(this);
861 
862             mTileLayout = itemView.findViewById(R.id.tile);
863             mImageView = itemView.findViewById(R.id.image);
864             mOverlayIconView = itemView.findViewById(R.id.overlay_icon);
865             mTitleView = itemView.findViewById(R.id.category_title);
866 
867             mTileLayout.getLayoutParams().height = mTileSizePx.y;
868         }
869 
870         @Override
onClick(View view)871         public void onClick(View view) {
872             final UserEventLogger eventLogger =
873                     InjectorProvider.getInjector().getUserEventLogger(getActivity());
874             eventLogger.logCategorySelected(mCategory.getCollectionId());
875 
876             if (mCategory.supportsCustomPhotos()) {
877                 getFragmentHost().getMyPhotosStarter().requestCustomPhotoPicker(
878                         new PermissionChangedListener() {
879                             @Override
880                             public void onPermissionsGranted() {
881                                 drawThumbnailAndOverlayIcon();
882                             }
883 
884                             @Override
885                             public void onPermissionsDenied(boolean dontAskAgain) {
886                                 // No-op
887                             }
888                         });
889                 return;
890             }
891 
892             getFragmentHost().show(mCategory.getCollectionId());
893         }
894 
895         /**
896          * Binds the given category to this CategoryHolder.
897          */
bindCategory(Category category)898         public void bindCategory(Category category) {
899             mCategory = category;
900             mTitleView.setText(category.getTitle());
901             drawThumbnailAndOverlayIcon();
902         }
903 
904         /**
905          * Draws the CategoryHolder's thumbnail and overlay icon.
906          */
drawThumbnailAndOverlayIcon()907         public void drawThumbnailAndOverlayIcon() {
908             mOverlayIconView.setImageDrawable(mCategory.getOverlayIcon(
909                     getActivity().getApplicationContext()));
910 
911             // Size the overlay icon according to the category.
912             int overlayIconDimenDp = mCategory.getOverlayIconSizeDp();
913             DisplayMetrics metrics = DisplayMetricsRetriever.getInstance().getDisplayMetrics(
914                     getResources(), getActivity().getWindowManager().getDefaultDisplay());
915             int overlayIconDimenPx = (int) (overlayIconDimenDp * metrics.density);
916             mOverlayIconView.getLayoutParams().width = overlayIconDimenPx;
917             mOverlayIconView.getLayoutParams().height = overlayIconDimenPx;
918 
919             Asset thumbnail = mCategory.getThumbnail(getActivity().getApplicationContext());
920             if (thumbnail != null) {
921                 thumbnail.loadDrawable(getActivity(), mImageView,
922                         getResources().getColor(R.color.secondary_color));
923             } else {
924                 // TODO(orenb): Replace this workaround for b/62584914 with a proper way of unloading the
925                 // ImageView such that no incorrect image is improperly loaded upon rapid scroll.
926                 Object nullObj = null;
927                 Glide.with(getActivity())
928                         .asDrawable()
929                         .load(nullObj)
930                         .into(mImageView);
931 
932             }
933         }
934     }
935 
936     /**
937      * ViewHolder subclass for the loading indicator ("spinner") shown when categories are being
938      * fetched.
939      */
940     private class LoadingIndicatorHolder extends RecyclerView.ViewHolder {
LoadingIndicatorHolder(View view)941         public LoadingIndicatorHolder(View view) {
942             super(view);
943             ProgressBar progressBar = view.findViewById(R.id.loading_indicator);
944             progressBar.getIndeterminateDrawable().setColorFilter(
945                     getResources().getColor(R.color.accent_color), Mode.SRC_IN);
946         }
947     }
948 
949     /**
950      * ViewHolder subclass for a "card" at the beginning of the RecyclerView showing the app needs the
951      * user to grant the storage permission to show the currently set wallpaper.
952      */
953     private class PermissionNeededHolder extends RecyclerView.ViewHolder {
954         private Button mAllowAccessButton;
955 
PermissionNeededHolder(View view)956         public PermissionNeededHolder(View view) {
957             super(view);
958 
959             mAllowAccessButton = view.findViewById(R.id.permission_needed_allow_access_button);
960             mAllowAccessButton.setOnClickListener((View v) -> {
961                 getFragmentHost().requestExternalStoragePermission(mAdapter);
962             });
963 
964             // Replace explanation text with text containing the Wallpapers app name which replaces the
965             // placeholder.
966             String appName = getString(R.string.app_name);
967             String explanation = getString(R.string.permission_needed_explanation, appName);
968             TextView explanationTextView = view.findViewById(R.id.permission_needed_explanation);
969             explanationTextView.setText(explanation);
970         }
971     }
972 
973     /**
974      * RecyclerView Adapter subclass for the category tiles in the RecyclerView.
975      */
976     private class CategoryAdapter extends RecyclerView.Adapter<ViewHolder>
977             implements PermissionChangedListener {
978         public static final int METADATA_VIEW_SINGLE_CARD = 1;
979         public static final int METADATA_VIEW_TWO_CARDS = 2;
980         private static final int ITEM_VIEW_TYPE_METADATA = 1;
981         private static final int ITEM_VIEW_TYPE_SELECT_WALLPAPER_HEADER = 2;
982         private static final int ITEM_VIEW_TYPE_CATEGORY = 3;
983         private static final int ITEM_VIEW_TYPE_LOADING_INDICATOR = 4;
984         private static final int ITEM_VIEW_TYPE_PERMISSION_NEEDED = 5;
985         private List<Category> mCategories;
986         private int mNumMetadataCards;
987 
CategoryAdapter(List<Category> categories)988         public CategoryAdapter(List<Category> categories) {
989             mCategories = categories;
990             mNumMetadataCards = METADATA_VIEW_SINGLE_CARD;
991         }
992 
993         /**
994          * Sets the number of metadata cards to be shown in the metadata view holder. Updates the UI
995          * to reflect any changes in that number (e.g., a lock screen wallpaper has been set so we now
996          * need to show two cards).
997          */
setNumMetadataCards(int numMetadataCards)998         public void setNumMetadataCards(int numMetadataCards) {
999             if (numMetadataCards != mNumMetadataCards && getItemCount() > 0) {
1000                 notifyItemChanged(0);
1001             }
1002 
1003             mNumMetadataCards = numMetadataCards;
1004         }
1005 
1006         @Override
getItemViewType(int position)1007         public int getItemViewType(int position) {
1008             if (position == 0) {
1009                 if (canShowCurrentWallpaper()) {
1010                     return ITEM_VIEW_TYPE_METADATA;
1011                 } else {
1012                     return ITEM_VIEW_TYPE_PERMISSION_NEEDED;
1013                 }
1014             } else if (position == 1) {
1015                 return ITEM_VIEW_TYPE_SELECT_WALLPAPER_HEADER;
1016             } else if (mAwaitingCategories && position == getItemCount() - 1) {
1017                 return ITEM_VIEW_TYPE_LOADING_INDICATOR;
1018             }
1019 
1020             return ITEM_VIEW_TYPE_CATEGORY;
1021         }
1022 
1023         @Override
onCreateViewHolder(ViewGroup parent, int viewType)1024         public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
1025             LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
1026             View view;
1027 
1028             switch (viewType) {
1029                 case ITEM_VIEW_TYPE_METADATA:
1030                     if (mNumMetadataCards == METADATA_VIEW_SINGLE_CARD) {
1031                         view = layoutInflater.inflate(
1032                                 R.layout.grid_item_single_metadata, parent, /* attachToRoot */ false);
1033                         return new SingleWallpaperMetadataHolder(view);
1034                     } else { // TWO_CARDS
1035                         view = layoutInflater.inflate(
1036                                 R.layout.grid_item_both_metadata, parent, /* attachToRoot */ false);
1037                         return new TwoWallpapersMetadataHolder(view);
1038                     }
1039                 case ITEM_VIEW_TYPE_SELECT_WALLPAPER_HEADER:
1040                     view = layoutInflater.inflate(
1041                             R.layout.grid_item_select_wallpaper_header, parent, /* attachToRoot */ false);
1042                     return new SelectWallpaperHeaderHolder(view);
1043                 case ITEM_VIEW_TYPE_LOADING_INDICATOR:
1044                     view = layoutInflater.inflate(
1045                             R.layout.grid_item_loading_indicator, parent, /* attachToRoot */ false);
1046                     return new LoadingIndicatorHolder(view);
1047                 case ITEM_VIEW_TYPE_CATEGORY:
1048                     view = layoutInflater.inflate(
1049                             R.layout.grid_item_category, parent, /* attachToRoot */ false);
1050                     return new CategoryHolder(view);
1051                 case ITEM_VIEW_TYPE_PERMISSION_NEEDED:
1052                     view = layoutInflater.inflate(
1053                             R.layout.grid_item_permission_needed, parent, /* attachToRoot */ false);
1054                     return new PermissionNeededHolder(view);
1055                 default:
1056                     Log.e(TAG, "Unsupported viewType " + viewType + " in CategoryAdapter");
1057                     return null;
1058             }
1059         }
1060 
1061         @Override
onBindViewHolder(ViewHolder holder, int position)1062         public void onBindViewHolder(ViewHolder holder, int position) {
1063             int viewType = getItemViewType(position);
1064 
1065             switch (viewType) {
1066                 case ITEM_VIEW_TYPE_METADATA:
1067                     refreshCurrentWallpapers((MetadataHolder) holder, false /* forceRefresh */);
1068                     break;
1069                 case ITEM_VIEW_TYPE_SELECT_WALLPAPER_HEADER:
1070                     // No op.
1071                     break;
1072                 case ITEM_VIEW_TYPE_CATEGORY:
1073                     // Offset position to get category index to account for the non-category view holders.
1074                     Category category = mCategories.get(position - NUM_NON_CATEGORY_VIEW_HOLDERS);
1075                     ((CategoryHolder) holder).bindCategory(category);
1076                     break;
1077                 case ITEM_VIEW_TYPE_LOADING_INDICATOR:
1078                 case ITEM_VIEW_TYPE_PERMISSION_NEEDED:
1079                     // No op.
1080                     break;
1081                 default:
1082                     Log.e(TAG, "Unsupported viewType " + viewType + " in CategoryAdapter");
1083             }
1084         }
1085 
1086         @Override
getItemCount()1087         public int getItemCount() {
1088             // Add to size of categories to account for the metadata related views.
1089             // Add 1 more for the loading indicator if not yet done loading.
1090             int size = mCategories.size() + NUM_NON_CATEGORY_VIEW_HOLDERS;
1091             if (mAwaitingCategories) {
1092                 size += 1;
1093             }
1094 
1095             return size;
1096         }
1097 
1098         @Override
onPermissionsGranted()1099         public void onPermissionsGranted() {
1100             notifyDataSetChanged();
1101         }
1102 
1103         @Override
onPermissionsDenied(boolean dontAskAgain)1104         public void onPermissionsDenied(boolean dontAskAgain) {
1105             if (!dontAskAgain) {
1106                 return;
1107             }
1108 
1109             String permissionNeededMessage =
1110                     getString(R.string.permission_needed_explanation_go_to_settings);
1111             AlertDialog dialog = new AlertDialog.Builder(getActivity(), R.style.LightDialogTheme)
1112                     .setMessage(permissionNeededMessage)
1113                     .setPositiveButton(android.R.string.ok, null /* onClickListener */)
1114                     .setNegativeButton(
1115                             R.string.settings_button_label,
1116                             new DialogInterface.OnClickListener() {
1117                                 @Override
1118                                 public void onClick(DialogInterface dialogInterface, int i) {
1119                                     Intent appInfoIntent = new Intent();
1120                                     appInfoIntent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
1121                                     Uri uri = Uri.fromParts(
1122                                             "package", getActivity().getPackageName(), null /* fragment */);
1123                                     appInfoIntent.setData(uri);
1124                                     startActivityForResult(appInfoIntent, SETTINGS_APP_INFO_REQUEST_CODE);
1125                                 }
1126                             })
1127                     .create();
1128             dialog.show();
1129         }
1130     }
1131 }
1132