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