blob: a05d0264b60046ea8a70ed911275ab6199e01259 [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.individual;
17
18import android.app.Activity;
19import android.app.ProgressDialog;
20import android.content.Context;
Santiago Etchebehereb1854472019-06-06 17:44:54 -070021import android.content.res.Configuration;
Jon Miranda16ea1b12017-12-12 14:52:48 -080022import android.content.res.Resources.NotFoundException;
23import android.graphics.Point;
24import android.graphics.PorterDuff.Mode;
25import android.os.Build.VERSION;
26import android.os.Build.VERSION_CODES;
27import android.os.Bundle;
28import android.os.Handler;
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -070029import android.service.wallpaper.WallpaperService;
Jon Miranda16ea1b12017-12-12 14:52:48 -080030import android.util.Log;
31import android.view.LayoutInflater;
32import android.view.View;
33import android.view.ViewGroup;
34import android.widget.FrameLayout;
35import android.widget.ImageView;
36import android.widget.TextView;
37import android.widget.Toast;
38
Santiago Etchebehereb1854472019-06-06 17:44:54 -070039import androidx.annotation.NonNull;
Santiago Etchebeherefab49612019-01-15 12:22:42 -080040import androidx.fragment.app.DialogFragment;
41import androidx.fragment.app.Fragment;
42import androidx.recyclerview.widget.GridLayoutManager;
43import androidx.recyclerview.widget.RecyclerView;
44import androidx.recyclerview.widget.RecyclerView.OnScrollListener;
45import androidx.recyclerview.widget.RecyclerView.ViewHolder;
46
Jon Miranda16ea1b12017-12-12 14:52:48 -080047import com.android.wallpaper.R;
48import com.android.wallpaper.asset.Asset;
49import com.android.wallpaper.asset.Asset.DrawableLoadedListener;
50import com.android.wallpaper.config.Flags;
Santiago Etchebehere8648bb82019-08-06 17:09:02 -070051import com.android.wallpaper.model.Category;
52import com.android.wallpaper.model.CategoryProvider;
53import com.android.wallpaper.model.CategoryReceiver;
Jon Miranda16ea1b12017-12-12 14:52:48 -080054import com.android.wallpaper.model.WallpaperCategory;
55import com.android.wallpaper.model.WallpaperInfo;
56import com.android.wallpaper.model.WallpaperReceiver;
57import com.android.wallpaper.model.WallpaperRotationInitializer;
58import com.android.wallpaper.model.WallpaperRotationInitializer.Listener;
59import com.android.wallpaper.model.WallpaperRotationInitializer.NetworkPreference;
60import com.android.wallpaper.model.WallpaperRotationInitializer.RotationInitializationState;
Jon Miranda16ea1b12017-12-12 14:52:48 -080061import com.android.wallpaper.module.FormFactorChecker;
62import com.android.wallpaper.module.FormFactorChecker.FormFactor;
63import com.android.wallpaper.module.Injector;
64import com.android.wallpaper.module.InjectorProvider;
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -070065import com.android.wallpaper.module.PackageStatusNotifier;
Jon Miranda16ea1b12017-12-12 14:52:48 -080066import com.android.wallpaper.module.WallpaperChangedNotifier;
67import com.android.wallpaper.module.WallpaperPersister;
68import com.android.wallpaper.module.WallpaperPersister.Destination;
69import com.android.wallpaper.module.WallpaperPreferences;
70import com.android.wallpaper.picker.BaseActivity;
71import com.android.wallpaper.picker.CurrentWallpaperBottomSheetPresenter;
Santiago Etchebeherefab49612019-01-15 12:22:42 -080072import com.android.wallpaper.picker.MyPhotosStarter.MyPhotosStarterProvider;
Jon Miranda16ea1b12017-12-12 14:52:48 -080073import com.android.wallpaper.picker.RotationStarter;
74import com.android.wallpaper.picker.SetWallpaperErrorDialogFragment;
75import com.android.wallpaper.picker.StartRotationDialogFragment;
76import com.android.wallpaper.picker.StartRotationErrorDialogFragment;
77import com.android.wallpaper.picker.WallpapersUiContainer;
78import com.android.wallpaper.picker.individual.SetIndividualHolder.OnSetListener;
79import com.android.wallpaper.util.DiskBasedLogger;
80import com.android.wallpaper.util.TileSizeCalculator;
81import com.android.wallpaper.widget.GridMarginDecoration;
Sunny Goyal8600a3f2018-08-15 12:48:01 -070082
Jon Miranda16ea1b12017-12-12 14:52:48 -080083import com.bumptech.glide.Glide;
84import com.bumptech.glide.MemoryCategory;
85
86import java.util.ArrayList;
87import java.util.Date;
88import java.util.List;
89import java.util.Random;
90
91/**
92 * Displays the Main UI for picking an individual wallpaper image.
93 */
94public class IndividualPickerFragment extends Fragment
95 implements RotationStarter, StartRotationErrorDialogFragment.Listener,
96 CurrentWallpaperBottomSheetPresenter.RefreshListener,
97 SetWallpaperErrorDialogFragment.Listener {
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +080098 /**
99 * Position of a special tile that doesn't belong to an individual wallpaper of the category,
100 * such as "my photos" or "daily rotation".
101 */
102 static final int SPECIAL_FIXED_TILE_ADAPTER_POSITION = 0;
103 static final String ARG_CATEGORY_COLLECTION_ID = "category_collection_id";
104
Jon Miranda16ea1b12017-12-12 14:52:48 -0800105 private static final String TAG = "IndividualPickerFrgmnt";
Jon Miranda16ea1b12017-12-12 14:52:48 -0800106 private static final int UNUSED_REQUEST_CODE = 1;
107 private static final String TAG_START_ROTATION_DIALOG = "start_rotation_dialog";
108 private static final String TAG_START_ROTATION_ERROR_DIALOG = "start_rotation_error_dialog";
109 private static final String PROGRESS_DIALOG_NO_TITLE = null;
110 private static final boolean PROGRESS_DIALOG_INDETERMINATE = true;
111 private static final String TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT =
112 "individual_set_wallpaper_error_dialog";
Santiago Etchebehereb1854472019-06-06 17:44:54 -0700113 private static final String KEY_NIGHT_MODE = "IndividualPickerFragment.NIGHT_MODE";
Jon Miranda16ea1b12017-12-12 14:52:48 -0800114
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800115 WallpaperPreferences mWallpaperPreferences;
116 WallpaperChangedNotifier mWallpaperChangedNotifier;
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800117 RecyclerView mImageGrid;
118 IndividualAdapter mAdapter;
119 WallpaperCategory mCategory;
120 WallpaperRotationInitializer mWallpaperRotationInitializer;
121 List<WallpaperInfo> mWallpapers;
122 Point mTileSizePx;
123 WallpapersUiContainer mWallpapersUiContainer;
124 @FormFactor
125 int mFormFactor;
126 PackageStatusNotifier mPackageStatusNotifier;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800127
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800128 Handler mHandler;
129 Random mRandom;
130
131 WallpaperChangedNotifier.Listener mWallpaperChangedListener =
132 new WallpaperChangedNotifier.Listener() {
133 @Override
134 public void onWallpaperChanged() {
135 if (mFormFactor != FormFactorChecker.FORM_FACTOR_DESKTOP) {
136 return;
137 }
138
139 ViewHolder selectedViewHolder = mImageGrid.findViewHolderForAdapterPosition(
140 mAdapter.mSelectedAdapterPosition);
141
142 // Null remote ID => My Photos wallpaper, so deselect whatever was previously selected.
143 if (mWallpaperPreferences.getHomeWallpaperRemoteId() == null) {
144 if (selectedViewHolder instanceof SelectableHolder) {
145 ((SelectableHolder) selectedViewHolder).setSelectionState(
146 SelectableHolder.SELECTION_STATE_DESELECTED);
147 }
148 } else {
149 mAdapter.updateSelectedTile(mAdapter.mPendingSelectedAdapterPosition);
150 }
151 }
152 };
153 PackageStatusNotifier.Listener mAppStatusListener;
154
Jon Miranda16ea1b12017-12-12 14:52:48 -0800155 private ProgressDialog mProgressDialog;
156 private boolean mTestingMode;
157 private CurrentWallpaperBottomSheetPresenter mCurrentWallpaperBottomSheetPresenter;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800158 private SetIndividualHolder mPendingSetIndividualHolder;
159
160 /**
161 * Staged error dialog fragments that were unable to be shown when the activity didn't allow
162 * committing fragment transactions.
163 */
164 private SetWallpaperErrorDialogFragment mStagedSetWallpaperErrorDialogFragment;
165 private StartRotationErrorDialogFragment mStagedStartRotationErrorDialogFragment;
166
Jon Miranda16ea1b12017-12-12 14:52:48 -0800167 private Runnable mCurrentWallpaperBottomSheetExpandedRunnable;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800168
169 /**
170 * Whether {@code mUpdateDailyWallpaperThumbRunnable} has been run at least once in this
171 * invocation of the fragment.
172 */
173 private boolean mWasUpdateRunnableRun;
174
175 /**
176 * A Runnable which regularly updates the thumbnail for the "Daily wallpapers" tile in desktop
177 * mode.
178 */
179 private Runnable mUpdateDailyWallpaperThumbRunnable = new Runnable() {
180 @Override
181 public void run() {
182 ViewHolder viewHolder = mImageGrid.findViewHolderForAdapterPosition(
183 SPECIAL_FIXED_TILE_ADAPTER_POSITION);
184 if (viewHolder instanceof DesktopRotationHolder) {
185 updateDesktopDailyRotationThumbnail((DesktopRotationHolder) viewHolder);
186 } else { // viewHolder is null
187 // If the rotation tile is unavailable (because user has scrolled down, causing the
188 // ViewHolder to be recycled), schedule the update for some time later. Once user scrolls up
189 // again, the ViewHolder will be re-bound and its thumbnail will be updated.
190 mHandler.postDelayed(mUpdateDailyWallpaperThumbRunnable,
191 DesktopRotationHolder.CROSSFADE_DURATION_MILLIS
192 + DesktopRotationHolder.CROSSFADE_DURATION_PAUSE_MILLIS);
193 }
194 }
195 };
196
Jon Miranda16ea1b12017-12-12 14:52:48 -0800197 public static IndividualPickerFragment newInstance(String collectionId) {
198 Bundle args = new Bundle();
199 args.putString(ARG_CATEGORY_COLLECTION_ID, collectionId);
200
201 IndividualPickerFragment fragment = new IndividualPickerFragment();
202 fragment.setArguments(args);
203 return fragment;
204 }
205
206 private static int getResIdForRotationState(@RotationInitializationState int rotationState) {
207 switch (rotationState) {
208 case WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED:
209 return R.string.daily_refresh_tile_subtitle;
210 case WallpaperRotationInitializer.ROTATION_HOME_ONLY:
211 return R.string.home_screen_message;
212 case WallpaperRotationInitializer.ROTATION_HOME_AND_LOCK:
213 return R.string.home_and_lock_short_label;
214 default:
215 Log.e(TAG, "Unknown rotation intialization state: " + rotationState);
216 return R.string.home_screen_message;
217 }
218 }
219
220 private void updateDesktopDailyRotationThumbnail(DesktopRotationHolder holder) {
221 int wallpapersIndex = mRandom.nextInt(mWallpapers.size());
222 Asset newThumbnailAsset = mWallpapers.get(wallpapersIndex).getThumbAsset(
223 getActivity());
224 holder.updateThumbnail(newThumbnailAsset, new DrawableLoadedListener() {
225 @Override
226 public void onDrawableLoaded() {
227 if (getActivity() == null) {
228 return;
229 }
230
231 // Schedule the next update of the thumbnail.
232 int delayMillis = DesktopRotationHolder.CROSSFADE_DURATION_MILLIS
233 + DesktopRotationHolder.CROSSFADE_DURATION_PAUSE_MILLIS;
234 mHandler.postDelayed(mUpdateDailyWallpaperThumbRunnable, delayMillis);
235 }
236 });
237 }
238
239 @Override
240 public void onCreate(Bundle savedInstanceState) {
241 super.onCreate(savedInstanceState);
242
243 Injector injector = InjectorProvider.getInjector();
244 Context appContext = getContext().getApplicationContext();
245 mWallpaperPreferences = injector.getPreferences(appContext);
246
247 mWallpaperChangedNotifier = WallpaperChangedNotifier.getInstance();
248 mWallpaperChangedNotifier.registerListener(mWallpaperChangedListener);
249
Jon Miranda16ea1b12017-12-12 14:52:48 -0800250 mFormFactor = injector.getFormFactorChecker(appContext).getFormFactor();
251
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700252 mPackageStatusNotifier = injector.getPackageStatusNotifier(appContext);
253
Jon Miranda16ea1b12017-12-12 14:52:48 -0800254 mWallpapers = new ArrayList<>();
255 mRandom = new Random();
256 mHandler = new Handler();
257
Santiago Etchebehereb1854472019-06-06 17:44:54 -0700258 // Clear Glide's cache if night-mode changed to ensure thumbnails are reloaded
259 if (savedInstanceState != null && (savedInstanceState.getInt(KEY_NIGHT_MODE)
260 != (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK))) {
261 Glide.get(getContext()).clearMemory();
262 }
263
Santiago Etchebehere8648bb82019-08-06 17:09:02 -0700264 CategoryProvider categoryProvider = injector.getCategoryProvider(appContext);
265 categoryProvider.fetchCategories(new CategoryReceiver() {
266 @Override
267 public void onCategoryReceived(Category category) {
268 // Do nothing.
269 }
270
271 @Override
272 public void doneFetchingCategories() {
273 mCategory = (WallpaperCategory) categoryProvider.getCategory(
274 getArguments().getString(ARG_CATEGORY_COLLECTION_ID));
275 if (mCategory == null) {
276 DiskBasedLogger.e(TAG, "Failed to find the category.", getContext());
277
278 // The absence of this category in the CategoryProvider indicates a broken
279 // state, see b/38030129. Hence, finish the activity and return.
280 getActivity().finish();
281 return;
282 }
283 onCategoryLoaded();
284 }
285 }, false);
286 }
287
288 protected void onCategoryLoaded() {
289 mWallpaperRotationInitializer = mCategory.getWallpaperRotationInitializer();
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700290 fetchWallpapers(false);
291
292 if (mCategory.supportsThirdParty()) {
293 mAppStatusListener = (packageName, status) -> {
294 if (status != PackageStatusNotifier.PackageStatus.REMOVED ||
295 mCategory.containsThirdParty(packageName)) {
296 fetchWallpapers(true);
297 }
298 };
299 mPackageStatusNotifier.addListener(mAppStatusListener,
300 WallpaperService.SERVICE_INTERFACE);
301 }
Santiago Etchebehere8648bb82019-08-06 17:09:02 -0700302
Santiago Etchebehere460fbcb2019-08-20 11:47:30 -0700303 maybeSetUpImageGrid();
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700304 }
305
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800306 void fetchWallpapers(boolean forceReload) {
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700307 mWallpapers.clear();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800308 mCategory.fetchWallpapers(getActivity().getApplicationContext(), new WallpaperReceiver() {
309 @Override
310 public void onWallpapersReceived(List<WallpaperInfo> wallpapers) {
311 for (WallpaperInfo wallpaper : wallpapers) {
312 mWallpapers.add(wallpaper);
313 }
314
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700315 // Wallpapers may load after the adapter is initialized, in which case we have
316 // to explicitly notify that the data set has changed.
Jon Miranda16ea1b12017-12-12 14:52:48 -0800317 if (mAdapter != null) {
318 mAdapter.notifyDataSetChanged();
319 }
320
321 if (mWallpapersUiContainer != null) {
322 mWallpapersUiContainer.onWallpapersReady();
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700323 } else {
324 if (wallpapers.isEmpty()) {
325 // If there are no more wallpapers and we're on phone, just finish the
326 // Activity.
327 Activity activity = getActivity();
328 if (activity != null
329 && mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
330 activity.finish();
331 }
332 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800333 }
334 }
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700335 }, forceReload);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800336 }
337
338 @Override
Santiago Etchebehereb1854472019-06-06 17:44:54 -0700339 public void onSaveInstanceState(@NonNull Bundle outState) {
340 super.onSaveInstanceState(outState);
Santiago Etchebehere0ec065c2019-06-13 11:30:21 -0700341 outState.putInt(KEY_NIGHT_MODE,
Santiago Etchebehereb1854472019-06-06 17:44:54 -0700342 getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK);
343 }
344
345 @Override
Jon Miranda16ea1b12017-12-12 14:52:48 -0800346 public View onCreateView(LayoutInflater inflater, ViewGroup container,
347 Bundle savedInstanceState) {
348 View view = inflater.inflate(R.layout.fragment_individual_picker, container, false);
349
350 mTileSizePx = TileSizeCalculator.getIndividualTileSize(getActivity());
351
352 mImageGrid = (RecyclerView) view.findViewById(R.id.wallpaper_grid);
353 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
354 int gridPaddingPx = getResources().getDimensionPixelSize(R.dimen.grid_padding_desktop);
355 updateImageGridPadding(false /* addExtraBottomSpace */);
356 mImageGrid.setScrollBarSize(gridPaddingPx);
357 }
358 GridMarginDecoration.applyTo(mImageGrid);
359
Santiago Etchebehere460fbcb2019-08-20 11:47:30 -0700360 maybeSetUpImageGrid();
Santiago Etchebehere8648bb82019-08-06 17:09:02 -0700361
Jon Miranda16ea1b12017-12-12 14:52:48 -0800362 setUpBottomSheet();
363
364 return view;
365 }
366
367 @Override
368 public void onClickTryAgain(@Destination int unused) {
369 if (mPendingSetIndividualHolder != null) {
370 mPendingSetIndividualHolder.setWallpaper();
371 }
372 }
373
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800374 void updateImageGridPadding(boolean addExtraBottomSpace) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800375 int gridPaddingPx = getResources().getDimensionPixelSize(R.dimen.grid_padding_desktop);
376 int bottomSheetHeightPx = getResources().getDimensionPixelSize(
377 R.dimen.current_wallpaper_bottom_sheet_layout_height);
378 int paddingBottomPx = addExtraBottomSpace ? bottomSheetHeightPx : 0;
379 // Only left and top may be set in order for the GridMarginDecoration to work properly.
380 mImageGrid.setPadding(
381 gridPaddingPx, gridPaddingPx, 0, paddingBottomPx);
382 }
383
Santiago Etchebehere460fbcb2019-08-20 11:47:30 -0700384 private void maybeSetUpImageGrid() {
Santiago Etchebehere8648bb82019-08-06 17:09:02 -0700385 // Skip if mImageGrid been initialized yet
386 if (mImageGrid == null) {
387 return;
388 }
389 // Skip if category hasn't loaded yet
390 if (mCategory == null) {
391 return;
392 }
393 // Skip if the adapter was already created
394 if (mAdapter != null) {
395 return;
396 }
Santiago Etchebehere460fbcb2019-08-20 11:47:30 -0700397 setUpImageGrid();
398 }
399
400 /**
401 * Create the adapter and assign it to mImageGrid.
402 * Both mImageGrid and mCategory are guaranteed to not be null when this method is called.
403 */
404 void setUpImageGrid() {
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800405 mAdapter = new IndividualAdapter(mWallpapers);
406 mImageGrid.setAdapter(mAdapter);
407 mImageGrid.setLayoutManager(new GridLayoutManager(getActivity(), getNumColumns()));
408 }
409
Jon Miranda16ea1b12017-12-12 14:52:48 -0800410 /**
411 * Enables and populates the "Currently set" wallpaper BottomSheet.
412 */
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800413 void setUpBottomSheet() {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800414 mImageGrid.addOnScrollListener(new OnScrollListener() {
415 @Override
416 public void onScrolled(RecyclerView recyclerView, int dx, final int dy) {
417 if (mCurrentWallpaperBottomSheetPresenter == null) {
418 return;
419 }
420
421 if (mCurrentWallpaperBottomSheetExpandedRunnable != null) {
422 mHandler.removeCallbacks(mCurrentWallpaperBottomSheetExpandedRunnable);
423 }
424 mCurrentWallpaperBottomSheetExpandedRunnable = new Runnable() {
425 @Override
426 public void run() {
427 if (dy > 0) {
428 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(false);
429 } else {
430 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(true);
431 }
432 }
433 };
434 mHandler.postDelayed(mCurrentWallpaperBottomSheetExpandedRunnable, 100);
435 }
436 });
437 }
438
439 @Override
440 public void onResume() {
441 super.onResume();
442
Santiago Etchebehere8cad0dd2019-10-17 10:52:39 -0700443 WallpaperPreferences preferences = InjectorProvider.getInjector()
444 .getPreferences(getActivity());
Jon Miranda16ea1b12017-12-12 14:52:48 -0800445 preferences.setLastAppActiveTimestamp(new Date().getTime());
446
447 // Reset Glide memory settings to a "normal" level of usage since it may have been lowered in
448 // PreviewFragment.
449 Glide.get(getActivity()).setMemoryCategory(MemoryCategory.NORMAL);
450
451 // Show the staged 'start rotation' error dialog fragment if there is one that was unable to be
452 // shown earlier when this fragment's hosting activity didn't allow committing fragment
453 // transactions.
454 if (mStagedStartRotationErrorDialogFragment != null) {
455 mStagedStartRotationErrorDialogFragment.show(
456 getFragmentManager(), TAG_START_ROTATION_ERROR_DIALOG);
457 mStagedStartRotationErrorDialogFragment = null;
458 }
459
460 // Show the staged 'load wallpaper' or 'set wallpaper' error dialog fragments if there is one
461 // that was unable to be shown earlier when this fragment's hosting activity didn't allow
462 // committing fragment transactions.
463 if (mStagedSetWallpaperErrorDialogFragment != null) {
464 mStagedSetWallpaperErrorDialogFragment.show(
465 getFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
466 mStagedSetWallpaperErrorDialogFragment = null;
467 }
468
469 if (isRotationEnabled()) {
470 if (mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
471 // Refresh the state of the "start rotation" in case something changed the current daily
472 // rotation while this fragment was paused.
473 RotationHolder rotationHolder = (RotationHolder) mImageGrid
474 .findViewHolderForAdapterPosition(
475 SPECIAL_FIXED_TILE_ADAPTER_POSITION);
476 // The RotationHolder may be null if the RecyclerView has not created the view
477 // holder yet.
478 if (rotationHolder != null && Flags.dynamicStartRotationTileEnabled) {
479 refreshRotationHolder(rotationHolder);
480 }
481 } else if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
482 if (mWasUpdateRunnableRun && !mWallpapers.isEmpty()) {
483 // Must be resuming from a previously stopped state, so re-schedule the update of the
484 // daily wallpapers tile thumbnail.
485 mUpdateDailyWallpaperThumbRunnable.run();
486 }
487 }
488 }
489
490 }
491
492 @Override
493 public void onStop() {
494 super.onStop();
495 mHandler.removeCallbacks(mUpdateDailyWallpaperThumbRunnable);
496 }
497
498 @Override
499 public void onDestroy() {
500 super.onDestroy();
501 if (mProgressDialog != null) {
502 mProgressDialog.dismiss();
503 }
504 mWallpaperChangedNotifier.unregisterListener(mWallpaperChangedListener);
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700505 if (mAppStatusListener != null) {
506 mPackageStatusNotifier.removeListener(mAppStatusListener);
507 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800508 }
509
510 @Override
511 public void retryStartRotation(@NetworkPreference int networkPreference) {
512 startRotation(networkPreference);
513 }
514
515 public void setCurrentWallpaperBottomSheetPresenter(
516 CurrentWallpaperBottomSheetPresenter presenter) {
517 mCurrentWallpaperBottomSheetPresenter = presenter;
518 }
519
520 public void setWallpapersUiContainer(WallpapersUiContainer uiContainer) {
521 mWallpapersUiContainer = uiContainer;
522 }
523
524 /**
525 * Enable a test mode of operation -- in which certain UI features are disabled to allow for
526 * UI tests to run correctly. Works around issue in ProgressDialog currently where the dialog
527 * constantly keeps the UI thread alive and blocks a test forever.
528 *
529 * @param testingMode
530 */
531 void setTestingMode(boolean testingMode) {
532 mTestingMode = testingMode;
533 }
534
535 /**
536 * Asynchronously fetches the refreshed rotation initialization state that is up to date with the
537 * state of the user's device and binds the state of the current category's rotation to the "start
538 * rotation" tile.
539 */
Santiago Etchebehere8cad0dd2019-10-17 10:52:39 -0700540 private void refreshRotationHolder(RotationHolder rotationHolder) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800541 mWallpaperRotationInitializer.fetchRotationInitializationState(getContext(),
Santiago Etchebehere8cad0dd2019-10-17 10:52:39 -0700542 rotationState -> {
543 // Update the UI state of the "start rotation" tile displayed on screen.
544 // Do this in a Handler so it is scheduled at the end of the message queue.
545 // This is necessary to ensure we do not remove or add data from the adapter
546 // while the layout is still being computed. RecyclerView documentation
547 // therefore recommends performing such changes in a Handler.
548 new Handler().post(() -> {
549 // A config change may have destroyed the activity since the refresh
550 // started, so check for that to avoid an NPE.
551 if (getActivity() == null) {
552 return;
553 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800554
Santiago Etchebehere8cad0dd2019-10-17 10:52:39 -0700555 rotationHolder.bindRotationInitializationState(rotationState);
556 });
Jon Miranda16ea1b12017-12-12 14:52:48 -0800557 });
558 }
559
560 @Override
561 public void startRotation(@NetworkPreference final int networkPreference) {
562 if (!isRotationEnabled()) {
563 Log.e(TAG, "Rotation is not enabled for this category " + mCategory.getTitle());
564 return;
565 }
566
567 // ProgressDialog endlessly updates the UI thread, keeping it from going idle which therefore
568 // causes Espresso to hang once the dialog is shown.
569 if (mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE && !mTestingMode) {
570 int themeResId;
571 if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
572 themeResId = R.style.ProgressDialogThemePreL;
573 } else {
574 themeResId = R.style.LightDialogTheme;
575 }
576 mProgressDialog = new ProgressDialog(getActivity(), themeResId);
577
578 mProgressDialog.setTitle(PROGRESS_DIALOG_NO_TITLE);
579 mProgressDialog.setMessage(
580 getResources().getString(R.string.start_rotation_progress_message));
581 mProgressDialog.setIndeterminate(PROGRESS_DIALOG_INDETERMINATE);
582 mProgressDialog.show();
583 }
584
585 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
586 mAdapter.mPendingSelectedAdapterPosition = SPECIAL_FIXED_TILE_ADAPTER_POSITION;
587 }
588
589 final Context appContext = getActivity().getApplicationContext();
590
591 mWallpaperRotationInitializer.setFirstWallpaperInRotation(
592 appContext,
593 networkPreference,
594 new Listener() {
595 @Override
596 public void onFirstWallpaperInRotationSet() {
597 if (mProgressDialog != null) {
598 mProgressDialog.dismiss();
599 }
600
601 // The fragment may be detached from its containing activity if the user exits the
602 // app before the first wallpaper image in rotation finishes downloading.
603 Activity activity = getActivity();
604
Jon Miranda16ea1b12017-12-12 14:52:48 -0800605
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700606 if (mWallpaperRotationInitializer.startRotation(appContext)) {
607 if (activity != null
608 && mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
609 try {
610 Toast.makeText(getActivity(),
611 R.string.wallpaper_set_successfully_message,
612 Toast.LENGTH_SHORT).show();
613 } catch (NotFoundException e) {
614 Log.e(TAG, "Could not show toast " + e);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800615 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800616
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700617 activity.setResult(Activity.RESULT_OK);
618 activity.finish();
619 } else if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
620 mAdapter.updateSelectedTile(SPECIAL_FIXED_TILE_ADAPTER_POSITION);
621 }
622 } else { // Failed to start rotation.
623 showStartRotationErrorDialog(networkPreference);
624
625 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
626 DesktopRotationHolder rotationViewHolder =
627 (DesktopRotationHolder)
628 mImageGrid.findViewHolderForAdapterPosition(
629 SPECIAL_FIXED_TILE_ADAPTER_POSITION);
630 rotationViewHolder.setSelectionState(
631 SelectableHolder.SELECTION_STATE_DESELECTED);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800632 }
633 }
634 }
635
636 @Override
637 public void onError() {
638 if (mProgressDialog != null) {
639 mProgressDialog.dismiss();
640 }
641
642 showStartRotationErrorDialog(networkPreference);
643
644 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
645 DesktopRotationHolder rotationViewHolder =
646 (DesktopRotationHolder) mImageGrid.findViewHolderForAdapterPosition(
647 SPECIAL_FIXED_TILE_ADAPTER_POSITION);
648 rotationViewHolder.setSelectionState(SelectableHolder.SELECTION_STATE_DESELECTED);
649 }
650 }
651 });
652 }
653
654 private void showStartRotationErrorDialog(@NetworkPreference int networkPreference) {
655 BaseActivity activity = (BaseActivity) getActivity();
656 if (activity != null) {
657 StartRotationErrorDialogFragment startRotationErrorDialogFragment =
658 StartRotationErrorDialogFragment.newInstance(networkPreference);
659 startRotationErrorDialogFragment.setTargetFragment(
660 IndividualPickerFragment.this, UNUSED_REQUEST_CODE);
661
662 if (activity.isSafeToCommitFragmentTransaction()) {
663 startRotationErrorDialogFragment.show(
664 getFragmentManager(), TAG_START_ROTATION_ERROR_DIALOG);
665 } else {
666 mStagedStartRotationErrorDialogFragment = startRotationErrorDialogFragment;
667 }
668 }
669 }
670
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800671 int getNumColumns() {
Ching-Sung Li9df77a32019-07-10 11:45:30 +0800672 Activity activity = getActivity();
673 return activity == null ? 0 : TileSizeCalculator.getNumIndividualColumns(activity);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800674 }
675
676 /**
677 * Returns whether rotation is enabled for this category.
678 */
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800679 boolean isRotationEnabled() {
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700680 return mWallpaperRotationInitializer != null;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800681 }
682
683 @Override
684 public void onCurrentWallpaperRefreshed() {
685 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(true);
686 }
687
688 /**
689 * Shows a "set wallpaper" error dialog with a failure message and button to try again.
690 */
691 private void showSetWallpaperErrorDialog() {
692 SetWallpaperErrorDialogFragment dialogFragment = SetWallpaperErrorDialogFragment.newInstance(
693 R.string.set_wallpaper_error_message, WallpaperPersister.DEST_BOTH);
694 dialogFragment.setTargetFragment(this, UNUSED_REQUEST_CODE);
695
696 if (((BaseActivity) getActivity()).isSafeToCommitFragmentTransaction()) {
697 dialogFragment.show(getFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
698 } else {
699 mStagedSetWallpaperErrorDialogFragment = dialogFragment;
700 }
701 }
702
703 /**
704 * ViewHolder subclass for "daily refresh" tile in the RecyclerView, only shown if rotation is
705 * enabled for this category.
706 */
707 private class RotationHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
708
709 private FrameLayout mTileLayout;
710 private TextView mRotationMessage;
711 private TextView mRotationTitle;
712 private ImageView mRefreshIcon;
713
714 RotationHolder(View itemView) {
715 super(itemView);
716 itemView.setOnClickListener(this);
717
Santiago Etchebehere8cad0dd2019-10-17 10:52:39 -0700718 mTileLayout = itemView.findViewById(R.id.daily_refresh);
719 mRotationMessage = itemView.findViewById(R.id.rotation_tile_message);
720 mRotationTitle = itemView.findViewById(R.id.rotation_tile_title);
721 mRefreshIcon = itemView.findViewById(R.id.rotation_tile_refresh_icon);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800722 mTileLayout.getLayoutParams().height = mTileSizePx.y;
723
724 // If the feature flag for "dynamic start rotation tile" is not enabled, fall back to the
725 // static UI with a blue accent color background and "Tap to turn on" text.
726 if (!Flags.dynamicStartRotationTileEnabled) {
727 mTileLayout.setBackgroundColor(
728 getResources().getColor(R.color.rotation_tile_enabled_background_color));
729 mRotationMessage.setText(R.string.daily_refresh_tile_subtitle);
730 mRotationTitle.setTextColor(
731 getResources().getColor(R.color.rotation_tile_enabled_title_text_color));
732 mRotationMessage.setTextColor(
733 getResources().getColor(R.color.rotation_tile_enabled_subtitle_text_color));
734 mRefreshIcon.setColorFilter(
Santiago Etchebehere8cad0dd2019-10-17 10:52:39 -0700735 getResources().getColor(R.color.rotation_tile_enabled_refresh_icon_color),
736 Mode.SRC_IN);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800737 return;
738 }
739
740 // Initialize the state of the "start rotation" tile (i.e., whether it is gray or blue to
741 // indicate if rotation is turned on for the current category) with last-known rotation state
742 // that could be stale. The last-known rotation state is correct in most cases and is a good
743 // starting point but may not be accurate if the user set a wallpaper through a 3rd party app
744 // while this app was paused.
745 int rotationState = mWallpaperRotationInitializer.getRotationInitializationStateDirty(
746 getContext());
747 bindRotationInitializationState(rotationState);
748 }
749
750 @Override
751 public void onClick(View v) {
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700752 DialogFragment startRotationDialogFragment = new StartRotationDialogFragment();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800753 startRotationDialogFragment.setTargetFragment(
754 IndividualPickerFragment.this, UNUSED_REQUEST_CODE);
755 startRotationDialogFragment.show(getFragmentManager(), TAG_START_ROTATION_DIALOG);
756 }
757
758 /**
759 * Binds the provided rotation initialization state to the RotationHolder and updates the tile's
760 * UI to be in sync with the state (i.e., message and color appropriately reflect the state to
761 * the user).
762 */
763 void bindRotationInitializationState(@RotationInitializationState int rotationState) {
764 int newBackgroundColor =
765 (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
766 ? getResources().getColor(R.color.rotation_tile_not_enabled_background_color)
767 : getResources().getColor(R.color.rotation_tile_enabled_background_color);
768 int newTitleTextColor =
769 (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
770 ? getResources().getColor(R.color.rotation_tile_not_enabled_title_text_color)
771 : getResources().getColor(R.color.rotation_tile_enabled_title_text_color);
772 int newSubtitleTextColor =
773 (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
774 ? getResources().getColor(R.color.rotation_tile_not_enabled_subtitle_text_color)
775 : getResources().getColor(R.color.rotation_tile_enabled_subtitle_text_color);
776 int newRefreshIconColor =
777 (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
778 ? getResources().getColor(R.color.rotation_tile_not_enabled_refresh_icon_color)
779 : getResources().getColor(R.color.rotation_tile_enabled_refresh_icon_color);
780
781 mTileLayout.setBackgroundColor(newBackgroundColor);
782 mRotationTitle.setTextColor(newTitleTextColor);
783 mRotationMessage.setText(getResIdForRotationState(rotationState));
784 mRotationMessage.setTextColor(newSubtitleTextColor);
785 mRefreshIcon.setColorFilter(newRefreshIconColor, Mode.SRC_IN);
786 }
787 }
788
789 /**
790 * RecyclerView Adapter subclass for the wallpaper tiles in the RecyclerView.
791 */
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800792 class IndividualAdapter extends RecyclerView.Adapter<ViewHolder> {
793 static final int ITEM_VIEW_TYPE_ROTATION = 1;
794 static final int ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER = 2;
795 static final int ITEM_VIEW_TYPE_MY_PHOTOS = 3;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800796
797 private final List<WallpaperInfo> mWallpapers;
798
799 private int mPendingSelectedAdapterPosition;
800 private int mSelectedAdapterPosition;
801
802 IndividualAdapter(List<WallpaperInfo> wallpapers) {
803 mWallpapers = wallpapers;
804 mPendingSelectedAdapterPosition = -1;
805 mSelectedAdapterPosition = -1;
806 }
807
808 @Override
809 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
810 switch (viewType) {
811 case ITEM_VIEW_TYPE_ROTATION:
812 return createRotationHolder(parent);
813 case ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER:
814 return createIndividualHolder(parent);
815 case ITEM_VIEW_TYPE_MY_PHOTOS:
816 return createMyPhotosHolder(parent);
817 default:
818 Log.e(TAG, "Unsupported viewType " + viewType + " in IndividualAdapter");
819 return null;
820 }
821 }
822
823 @Override
824 public int getItemViewType(int position) {
825 if (isRotationEnabled() && position == SPECIAL_FIXED_TILE_ADAPTER_POSITION) {
826 return ITEM_VIEW_TYPE_ROTATION;
827 }
828
829 // A category cannot have both a "start rotation" tile and a "my photos" tile.
830 if (mCategory.supportsCustomPhotos()
831 && !isRotationEnabled()
832 && position == SPECIAL_FIXED_TILE_ADAPTER_POSITION) {
833 return ITEM_VIEW_TYPE_MY_PHOTOS;
834 }
835
836 return ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER;
837 }
838
839 @Override
840 public void onBindViewHolder(ViewHolder holder, int position) {
841 int viewType = getItemViewType(position);
842
843 switch (viewType) {
844 case ITEM_VIEW_TYPE_ROTATION:
845 onBindRotationHolder(holder, position);
846 break;
847 case ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER:
848 onBindIndividualHolder(holder, position);
849 break;
850 case ITEM_VIEW_TYPE_MY_PHOTOS:
851 ((MyPhotosViewHolder) holder).bind();
852 break;
853 default:
854 Log.e(TAG, "Unsupported viewType " + viewType + " in IndividualAdapter");
855 }
856 }
857
858 @Override
859 public int getItemCount() {
860 return (isRotationEnabled() || mCategory.supportsCustomPhotos())
861 ? mWallpapers.size() + 1
862 : mWallpapers.size();
863 }
864
865 private ViewHolder createRotationHolder(ViewGroup parent) {
866 LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
867 View view;
868
869 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
870 view = layoutInflater.inflate(R.layout.grid_item_rotation_desktop, parent, false);
871 SelectionAnimator selectionAnimator =
872 new CheckmarkSelectionAnimator(getActivity(), view);
873 return new DesktopRotationHolder(
874 getActivity(), mTileSizePx.y, view, selectionAnimator,
875 IndividualPickerFragment.this);
876 } else { // MOBILE
877 view = layoutInflater.inflate(R.layout.grid_item_rotation, parent, false);
878 return new RotationHolder(view);
879 }
880 }
881
882 private ViewHolder createIndividualHolder(ViewGroup parent) {
883 LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
884 View view = layoutInflater.inflate(R.layout.grid_item_image, parent, false);
885
886 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
887 SelectionAnimator selectionAnimator =
888 new CheckmarkSelectionAnimator(getActivity(), view);
889 return new SetIndividualHolder(
890 getActivity(), mTileSizePx.y, view,
891 selectionAnimator,
892 new OnSetListener() {
893 @Override
894 public void onPendingWallpaperSet(int adapterPosition) {
895 // Deselect and hide loading indicator for any previously pending tile.
896 if (mPendingSelectedAdapterPosition != -1) {
897 ViewHolder oldViewHolder = mImageGrid.findViewHolderForAdapterPosition(
898 mPendingSelectedAdapterPosition);
899 if (oldViewHolder instanceof SelectableHolder) {
900 ((SelectableHolder) oldViewHolder).setSelectionState(
901 SelectableHolder.SELECTION_STATE_DESELECTED);
902 }
903 }
904
905 if (mSelectedAdapterPosition != -1) {
906 ViewHolder oldViewHolder = mImageGrid.findViewHolderForAdapterPosition(
907 mSelectedAdapterPosition);
908 if (oldViewHolder instanceof SelectableHolder) {
909 ((SelectableHolder) oldViewHolder).setSelectionState(
910 SelectableHolder.SELECTION_STATE_DESELECTED);
911 }
912 }
913
914 mPendingSelectedAdapterPosition = adapterPosition;
915 }
916
917 @Override
918 public void onWallpaperSet(int adapterPosition) {
919 // No-op -- UI handles a new wallpaper being set by reacting to the
920 // WallpaperChangedNotifier.
921 }
922
923 @Override
924 public void onWallpaperSetFailed(SetIndividualHolder holder) {
925 showSetWallpaperErrorDialog();
926 mPendingSetIndividualHolder = holder;
927 }
928 });
929 } else { // MOBILE
Chuck Liao3d1a51c2020-02-17 18:29:34 +0800930 return new PreviewIndividualHolder(getActivity(), mTileSizePx.y, view);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800931 }
932 }
933
934 private ViewHolder createMyPhotosHolder(ViewGroup parent) {
935 LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
936 View view = layoutInflater.inflate(R.layout.grid_item_my_photos, parent, false);
937
Santiago Etchebeherefab49612019-01-15 12:22:42 -0800938 return new MyPhotosViewHolder(getActivity(),
939 ((MyPhotosStarterProvider) getActivity()).getMyPhotosStarter(),
940 mTileSizePx.y, view);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800941 }
942
943 /**
944 * Marks the tile at the given position as selected with a visual indication. Also updates the
945 * "currently selected" BottomSheet to reflect the newly selected tile.
946 */
947 private void updateSelectedTile(int newlySelectedPosition) {
948 // Prevent multiple spinners from appearing with a user tapping several tiles in rapid
949 // succession.
950 if (mPendingSelectedAdapterPosition == mSelectedAdapterPosition) {
951 return;
952 }
953
954 if (mCurrentWallpaperBottomSheetPresenter != null) {
955 mCurrentWallpaperBottomSheetPresenter.refreshCurrentWallpapers(
956 IndividualPickerFragment.this);
957
958 if (mCurrentWallpaperBottomSheetExpandedRunnable != null) {
959 mHandler.removeCallbacks(mCurrentWallpaperBottomSheetExpandedRunnable);
960 }
961 mCurrentWallpaperBottomSheetExpandedRunnable = new Runnable() {
962 @Override
963 public void run() {
964 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(true);
965 }
966 };
967 mHandler.postDelayed(mCurrentWallpaperBottomSheetExpandedRunnable, 100);
968 }
969
970 // User may have switched to another category, thus detaching this fragment, so check here.
971 // NOTE: We do this check after updating the current wallpaper BottomSheet so that the update
972 // still occurs in the UI after the user selects that other category.
973 if (getActivity() == null) {
974 return;
975 }
976
977 // Update the newly selected wallpaper ViewHolder and the old one so that if
978 // selection UI state applies (desktop UI), it is updated.
979 if (mSelectedAdapterPosition >= 0) {
980 ViewHolder oldViewHolder = mImageGrid.findViewHolderForAdapterPosition(
981 mSelectedAdapterPosition);
982 if (oldViewHolder instanceof SelectableHolder) {
983 ((SelectableHolder) oldViewHolder).setSelectionState(
984 SelectableHolder.SELECTION_STATE_DESELECTED);
985 }
986 }
987
988 // Animate selection of newly selected tile.
989 ViewHolder newViewHolder = mImageGrid
990 .findViewHolderForAdapterPosition(newlySelectedPosition);
991 if (newViewHolder instanceof SelectableHolder) {
992 ((SelectableHolder) newViewHolder).setSelectionState(
993 SelectableHolder.SELECTION_STATE_SELECTED);
994 }
995
996 mSelectedAdapterPosition = newlySelectedPosition;
997
998 // If the tile was in the last row of the grid, add space below it so the user can scroll down
999 // and up to see the BottomSheet without it fully overlapping the newly selected tile.
1000 int spanCount = ((GridLayoutManager) mImageGrid.getLayoutManager()).getSpanCount();
1001 int numRows = (int) Math.ceil((float) getItemCount() / spanCount);
1002 int rowOfNewlySelectedTile = newlySelectedPosition / spanCount;
1003 boolean isInLastRow = rowOfNewlySelectedTile == numRows - 1;
1004
1005 updateImageGridPadding(isInLastRow /* addExtraBottomSpace */);
1006 }
1007
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +08001008 void onBindRotationHolder(ViewHolder holder, int position) {
Jon Miranda16ea1b12017-12-12 14:52:48 -08001009 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
1010 String collectionId = mCategory.getCollectionId();
1011 ((DesktopRotationHolder) holder).bind(collectionId);
1012
1013 if (mWallpaperPreferences.getWallpaperPresentationMode()
1014 == WallpaperPreferences.PRESENTATION_MODE_ROTATING
1015 && collectionId.equals(mWallpaperPreferences.getHomeWallpaperCollectionId())) {
1016 mSelectedAdapterPosition = position;
1017 }
1018
1019 if (!mWasUpdateRunnableRun && !mWallpapers.isEmpty()) {
1020 updateDesktopDailyRotationThumbnail((DesktopRotationHolder) holder);
1021 mWasUpdateRunnableRun = true;
1022 }
1023 }
1024 }
1025
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +08001026 void onBindIndividualHolder(ViewHolder holder, int position) {
Jon Miranda16ea1b12017-12-12 14:52:48 -08001027 int wallpaperIndex = (isRotationEnabled() || mCategory.supportsCustomPhotos())
1028 ? position - 1 : position;
1029 WallpaperInfo wallpaper = mWallpapers.get(wallpaperIndex);
1030 ((IndividualHolder) holder).bindWallpaper(wallpaper);
1031 WallpaperPreferences prefs = InjectorProvider.getInjector().getPreferences(getContext());
1032
1033 String wallpaperId = wallpaper.getWallpaperId();
1034 if (wallpaperId != null && wallpaperId.equals(prefs.getHomeWallpaperRemoteId())) {
1035 mSelectedAdapterPosition = position;
1036 }
1037 }
1038 }
1039}