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