blob: 401634ddc7f75311d01ebde33f73e0f55b6c3d47 [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;
61import com.android.wallpaper.model.WallpaperRotationInitializer.RotationStateListener;
62import com.android.wallpaper.module.FormFactorChecker;
63import com.android.wallpaper.module.FormFactorChecker.FormFactor;
64import com.android.wallpaper.module.Injector;
65import com.android.wallpaper.module.InjectorProvider;
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -070066import com.android.wallpaper.module.PackageStatusNotifier;
Jon Miranda16ea1b12017-12-12 14:52:48 -080067import com.android.wallpaper.module.WallpaperChangedNotifier;
68import com.android.wallpaper.module.WallpaperPersister;
69import com.android.wallpaper.module.WallpaperPersister.Destination;
70import com.android.wallpaper.module.WallpaperPreferences;
71import com.android.wallpaper.picker.BaseActivity;
72import com.android.wallpaper.picker.CurrentWallpaperBottomSheetPresenter;
Santiago Etchebeherefab49612019-01-15 12:22:42 -080073import com.android.wallpaper.picker.MyPhotosStarter.MyPhotosStarterProvider;
Jon Miranda16ea1b12017-12-12 14:52:48 -080074import com.android.wallpaper.picker.RotationStarter;
75import com.android.wallpaper.picker.SetWallpaperErrorDialogFragment;
76import com.android.wallpaper.picker.StartRotationDialogFragment;
77import com.android.wallpaper.picker.StartRotationErrorDialogFragment;
78import com.android.wallpaper.picker.WallpapersUiContainer;
79import com.android.wallpaper.picker.individual.SetIndividualHolder.OnSetListener;
80import com.android.wallpaper.util.DiskBasedLogger;
81import com.android.wallpaper.util.TileSizeCalculator;
82import com.android.wallpaper.widget.GridMarginDecoration;
Sunny Goyal8600a3f2018-08-15 12:48:01 -070083
Jon Miranda16ea1b12017-12-12 14:52:48 -080084import com.bumptech.glide.Glide;
85import com.bumptech.glide.MemoryCategory;
86
87import java.util.ArrayList;
88import java.util.Date;
89import java.util.List;
90import java.util.Random;
91
92/**
93 * Displays the Main UI for picking an individual wallpaper image.
94 */
95public class IndividualPickerFragment extends Fragment
96 implements RotationStarter, StartRotationErrorDialogFragment.Listener,
97 CurrentWallpaperBottomSheetPresenter.RefreshListener,
98 SetWallpaperErrorDialogFragment.Listener {
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +080099 /**
100 * Position of a special tile that doesn't belong to an individual wallpaper of the category,
101 * such as "my photos" or "daily rotation".
102 */
103 static final int SPECIAL_FIXED_TILE_ADAPTER_POSITION = 0;
104 static final String ARG_CATEGORY_COLLECTION_ID = "category_collection_id";
105
Jon Miranda16ea1b12017-12-12 14:52:48 -0800106 private static final String TAG = "IndividualPickerFrgmnt";
Jon Miranda16ea1b12017-12-12 14:52:48 -0800107 private static final int UNUSED_REQUEST_CODE = 1;
108 private static final String TAG_START_ROTATION_DIALOG = "start_rotation_dialog";
109 private static final String TAG_START_ROTATION_ERROR_DIALOG = "start_rotation_error_dialog";
110 private static final String PROGRESS_DIALOG_NO_TITLE = null;
111 private static final boolean PROGRESS_DIALOG_INDETERMINATE = true;
112 private static final String TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT =
113 "individual_set_wallpaper_error_dialog";
Santiago Etchebehereb1854472019-06-06 17:44:54 -0700114 private static final String KEY_NIGHT_MODE = "IndividualPickerFragment.NIGHT_MODE";
Jon Miranda16ea1b12017-12-12 14:52:48 -0800115
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800116 WallpaperPreferences mWallpaperPreferences;
117 WallpaperChangedNotifier mWallpaperChangedNotifier;
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800118 RecyclerView mImageGrid;
119 IndividualAdapter mAdapter;
120 WallpaperCategory mCategory;
121 WallpaperRotationInitializer mWallpaperRotationInitializer;
122 List<WallpaperInfo> mWallpapers;
123 Point mTileSizePx;
124 WallpapersUiContainer mWallpapersUiContainer;
125 @FormFactor
126 int mFormFactor;
127 PackageStatusNotifier mPackageStatusNotifier;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800128
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800129 Handler mHandler;
130 Random mRandom;
131
132 WallpaperChangedNotifier.Listener mWallpaperChangedListener =
133 new WallpaperChangedNotifier.Listener() {
134 @Override
135 public void onWallpaperChanged() {
136 if (mFormFactor != FormFactorChecker.FORM_FACTOR_DESKTOP) {
137 return;
138 }
139
140 ViewHolder selectedViewHolder = mImageGrid.findViewHolderForAdapterPosition(
141 mAdapter.mSelectedAdapterPosition);
142
143 // Null remote ID => My Photos wallpaper, so deselect whatever was previously selected.
144 if (mWallpaperPreferences.getHomeWallpaperRemoteId() == null) {
145 if (selectedViewHolder instanceof SelectableHolder) {
146 ((SelectableHolder) selectedViewHolder).setSelectionState(
147 SelectableHolder.SELECTION_STATE_DESELECTED);
148 }
149 } else {
150 mAdapter.updateSelectedTile(mAdapter.mPendingSelectedAdapterPosition);
151 }
152 }
153 };
154 PackageStatusNotifier.Listener mAppStatusListener;
155
Jon Miranda16ea1b12017-12-12 14:52:48 -0800156 private ProgressDialog mProgressDialog;
157 private boolean mTestingMode;
158 private CurrentWallpaperBottomSheetPresenter mCurrentWallpaperBottomSheetPresenter;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800159 private SetIndividualHolder mPendingSetIndividualHolder;
160
161 /**
162 * Staged error dialog fragments that were unable to be shown when the activity didn't allow
163 * committing fragment transactions.
164 */
165 private SetWallpaperErrorDialogFragment mStagedSetWallpaperErrorDialogFragment;
166 private StartRotationErrorDialogFragment mStagedStartRotationErrorDialogFragment;
167
Jon Miranda16ea1b12017-12-12 14:52:48 -0800168 private Runnable mCurrentWallpaperBottomSheetExpandedRunnable;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800169
170 /**
171 * Whether {@code mUpdateDailyWallpaperThumbRunnable} has been run at least once in this
172 * invocation of the fragment.
173 */
174 private boolean mWasUpdateRunnableRun;
175
176 /**
177 * A Runnable which regularly updates the thumbnail for the "Daily wallpapers" tile in desktop
178 * mode.
179 */
180 private Runnable mUpdateDailyWallpaperThumbRunnable = new Runnable() {
181 @Override
182 public void run() {
183 ViewHolder viewHolder = mImageGrid.findViewHolderForAdapterPosition(
184 SPECIAL_FIXED_TILE_ADAPTER_POSITION);
185 if (viewHolder instanceof DesktopRotationHolder) {
186 updateDesktopDailyRotationThumbnail((DesktopRotationHolder) viewHolder);
187 } else { // viewHolder is null
188 // If the rotation tile is unavailable (because user has scrolled down, causing the
189 // ViewHolder to be recycled), schedule the update for some time later. Once user scrolls up
190 // again, the ViewHolder will be re-bound and its thumbnail will be updated.
191 mHandler.postDelayed(mUpdateDailyWallpaperThumbRunnable,
192 DesktopRotationHolder.CROSSFADE_DURATION_MILLIS
193 + DesktopRotationHolder.CROSSFADE_DURATION_PAUSE_MILLIS);
194 }
195 }
196 };
197
Jon Miranda16ea1b12017-12-12 14:52:48 -0800198 public static IndividualPickerFragment newInstance(String collectionId) {
199 Bundle args = new Bundle();
200 args.putString(ARG_CATEGORY_COLLECTION_ID, collectionId);
201
202 IndividualPickerFragment fragment = new IndividualPickerFragment();
203 fragment.setArguments(args);
204 return fragment;
205 }
206
207 private static int getResIdForRotationState(@RotationInitializationState int rotationState) {
208 switch (rotationState) {
209 case WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED:
210 return R.string.daily_refresh_tile_subtitle;
211 case WallpaperRotationInitializer.ROTATION_HOME_ONLY:
212 return R.string.home_screen_message;
213 case WallpaperRotationInitializer.ROTATION_HOME_AND_LOCK:
214 return R.string.home_and_lock_short_label;
215 default:
216 Log.e(TAG, "Unknown rotation intialization state: " + rotationState);
217 return R.string.home_screen_message;
218 }
219 }
220
221 private void updateDesktopDailyRotationThumbnail(DesktopRotationHolder holder) {
222 int wallpapersIndex = mRandom.nextInt(mWallpapers.size());
223 Asset newThumbnailAsset = mWallpapers.get(wallpapersIndex).getThumbAsset(
224 getActivity());
225 holder.updateThumbnail(newThumbnailAsset, new DrawableLoadedListener() {
226 @Override
227 public void onDrawableLoaded() {
228 if (getActivity() == null) {
229 return;
230 }
231
232 // Schedule the next update of the thumbnail.
233 int delayMillis = DesktopRotationHolder.CROSSFADE_DURATION_MILLIS
234 + DesktopRotationHolder.CROSSFADE_DURATION_PAUSE_MILLIS;
235 mHandler.postDelayed(mUpdateDailyWallpaperThumbRunnable, delayMillis);
236 }
237 });
238 }
239
240 @Override
241 public void onCreate(Bundle savedInstanceState) {
242 super.onCreate(savedInstanceState);
243
244 Injector injector = InjectorProvider.getInjector();
245 Context appContext = getContext().getApplicationContext();
246 mWallpaperPreferences = injector.getPreferences(appContext);
247
248 mWallpaperChangedNotifier = WallpaperChangedNotifier.getInstance();
249 mWallpaperChangedNotifier.registerListener(mWallpaperChangedListener);
250
Jon Miranda16ea1b12017-12-12 14:52:48 -0800251 mFormFactor = injector.getFormFactorChecker(appContext).getFormFactor();
252
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700253 mPackageStatusNotifier = injector.getPackageStatusNotifier(appContext);
254
Jon Miranda16ea1b12017-12-12 14:52:48 -0800255 mWallpapers = new ArrayList<>();
256 mRandom = new Random();
257 mHandler = new Handler();
258
Santiago Etchebehereb1854472019-06-06 17:44:54 -0700259 // Clear Glide's cache if night-mode changed to ensure thumbnails are reloaded
260 if (savedInstanceState != null && (savedInstanceState.getInt(KEY_NIGHT_MODE)
261 != (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK))) {
262 Glide.get(getContext()).clearMemory();
263 }
264
Santiago Etchebehere8648bb82019-08-06 17:09:02 -0700265 CategoryProvider categoryProvider = injector.getCategoryProvider(appContext);
266 categoryProvider.fetchCategories(new CategoryReceiver() {
267 @Override
268 public void onCategoryReceived(Category category) {
269 // Do nothing.
270 }
271
272 @Override
273 public void doneFetchingCategories() {
274 mCategory = (WallpaperCategory) categoryProvider.getCategory(
275 getArguments().getString(ARG_CATEGORY_COLLECTION_ID));
276 if (mCategory == null) {
277 DiskBasedLogger.e(TAG, "Failed to find the category.", getContext());
278
279 // The absence of this category in the CategoryProvider indicates a broken
280 // state, see b/38030129. Hence, finish the activity and return.
281 getActivity().finish();
282 return;
283 }
284 onCategoryLoaded();
285 }
286 }, false);
287 }
288
289 protected void onCategoryLoaded() {
290 mWallpaperRotationInitializer = mCategory.getWallpaperRotationInitializer();
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700291 fetchWallpapers(false);
292
293 if (mCategory.supportsThirdParty()) {
294 mAppStatusListener = (packageName, status) -> {
295 if (status != PackageStatusNotifier.PackageStatus.REMOVED ||
296 mCategory.containsThirdParty(packageName)) {
297 fetchWallpapers(true);
298 }
299 };
300 mPackageStatusNotifier.addListener(mAppStatusListener,
301 WallpaperService.SERVICE_INTERFACE);
302 }
Santiago Etchebehere8648bb82019-08-06 17:09:02 -0700303
Santiago Etchebehere460fbcb2019-08-20 11:47:30 -0700304 maybeSetUpImageGrid();
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700305 }
306
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800307 void fetchWallpapers(boolean forceReload) {
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700308 mWallpapers.clear();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800309 mCategory.fetchWallpapers(getActivity().getApplicationContext(), new WallpaperReceiver() {
310 @Override
311 public void onWallpapersReceived(List<WallpaperInfo> wallpapers) {
312 for (WallpaperInfo wallpaper : wallpapers) {
313 mWallpapers.add(wallpaper);
314 }
315
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700316 // Wallpapers may load after the adapter is initialized, in which case we have
317 // to explicitly notify that the data set has changed.
Jon Miranda16ea1b12017-12-12 14:52:48 -0800318 if (mAdapter != null) {
319 mAdapter.notifyDataSetChanged();
320 }
321
322 if (mWallpapersUiContainer != null) {
323 mWallpapersUiContainer.onWallpapersReady();
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700324 } else {
325 if (wallpapers.isEmpty()) {
326 // If there are no more wallpapers and we're on phone, just finish the
327 // Activity.
328 Activity activity = getActivity();
329 if (activity != null
330 && mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
331 activity.finish();
332 }
333 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800334 }
335 }
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700336 }, forceReload);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800337 }
338
339 @Override
Santiago Etchebehereb1854472019-06-06 17:44:54 -0700340 public void onSaveInstanceState(@NonNull Bundle outState) {
341 super.onSaveInstanceState(outState);
Santiago Etchebehere0ec065c2019-06-13 11:30:21 -0700342 outState.putInt(KEY_NIGHT_MODE,
Santiago Etchebehereb1854472019-06-06 17:44:54 -0700343 getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK);
344 }
345
346 @Override
Jon Miranda16ea1b12017-12-12 14:52:48 -0800347 public View onCreateView(LayoutInflater inflater, ViewGroup container,
348 Bundle savedInstanceState) {
349 View view = inflater.inflate(R.layout.fragment_individual_picker, container, false);
350
351 mTileSizePx = TileSizeCalculator.getIndividualTileSize(getActivity());
352
353 mImageGrid = (RecyclerView) view.findViewById(R.id.wallpaper_grid);
354 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
355 int gridPaddingPx = getResources().getDimensionPixelSize(R.dimen.grid_padding_desktop);
356 updateImageGridPadding(false /* addExtraBottomSpace */);
357 mImageGrid.setScrollBarSize(gridPaddingPx);
358 }
359 GridMarginDecoration.applyTo(mImageGrid);
360
Santiago Etchebehere460fbcb2019-08-20 11:47:30 -0700361 maybeSetUpImageGrid();
Santiago Etchebehere8648bb82019-08-06 17:09:02 -0700362
Jon Miranda16ea1b12017-12-12 14:52:48 -0800363 setUpBottomSheet();
364
365 return view;
366 }
367
368 @Override
369 public void onClickTryAgain(@Destination int unused) {
370 if (mPendingSetIndividualHolder != null) {
371 mPendingSetIndividualHolder.setWallpaper();
372 }
373 }
374
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800375 void updateImageGridPadding(boolean addExtraBottomSpace) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800376 int gridPaddingPx = getResources().getDimensionPixelSize(R.dimen.grid_padding_desktop);
377 int bottomSheetHeightPx = getResources().getDimensionPixelSize(
378 R.dimen.current_wallpaper_bottom_sheet_layout_height);
379 int paddingBottomPx = addExtraBottomSpace ? bottomSheetHeightPx : 0;
380 // Only left and top may be set in order for the GridMarginDecoration to work properly.
381 mImageGrid.setPadding(
382 gridPaddingPx, gridPaddingPx, 0, paddingBottomPx);
383 }
384
Santiago Etchebehere460fbcb2019-08-20 11:47:30 -0700385 private void maybeSetUpImageGrid() {
Santiago Etchebehere8648bb82019-08-06 17:09:02 -0700386 // Skip if mImageGrid been initialized yet
387 if (mImageGrid == null) {
388 return;
389 }
390 // Skip if category hasn't loaded yet
391 if (mCategory == null) {
392 return;
393 }
394 // Skip if the adapter was already created
395 if (mAdapter != null) {
396 return;
397 }
Santiago Etchebehere460fbcb2019-08-20 11:47:30 -0700398 setUpImageGrid();
399 }
400
401 /**
402 * Create the adapter and assign it to mImageGrid.
403 * Both mImageGrid and mCategory are guaranteed to not be null when this method is called.
404 */
405 void setUpImageGrid() {
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800406 mAdapter = new IndividualAdapter(mWallpapers);
407 mImageGrid.setAdapter(mAdapter);
408 mImageGrid.setLayoutManager(new GridLayoutManager(getActivity(), getNumColumns()));
409 }
410
Jon Miranda16ea1b12017-12-12 14:52:48 -0800411 /**
412 * Enables and populates the "Currently set" wallpaper BottomSheet.
413 */
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800414 void setUpBottomSheet() {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800415 mImageGrid.addOnScrollListener(new OnScrollListener() {
416 @Override
417 public void onScrolled(RecyclerView recyclerView, int dx, final int dy) {
418 if (mCurrentWallpaperBottomSheetPresenter == null) {
419 return;
420 }
421
422 if (mCurrentWallpaperBottomSheetExpandedRunnable != null) {
423 mHandler.removeCallbacks(mCurrentWallpaperBottomSheetExpandedRunnable);
424 }
425 mCurrentWallpaperBottomSheetExpandedRunnable = new Runnable() {
426 @Override
427 public void run() {
428 if (dy > 0) {
429 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(false);
430 } else {
431 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(true);
432 }
433 }
434 };
435 mHandler.postDelayed(mCurrentWallpaperBottomSheetExpandedRunnable, 100);
436 }
437 });
438 }
439
440 @Override
441 public void onResume() {
442 super.onResume();
443
444 WallpaperPreferences preferences = InjectorProvider.getInjector().getPreferences(getActivity());
445 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 */
540 private void refreshRotationHolder(final RotationHolder rotationHolder) {
541 mWallpaperRotationInitializer.fetchRotationInitializationState(getContext(),
542 new RotationStateListener() {
543 @Override
544 public void onRotationStateReceived(
545 @RotationInitializationState final int rotationInitializationState) {
546
547 // Update the UI state of the "start rotation" tile displayed on screen. Do this in a
548 // Handler so it is scheduled at the end of the message queue. This is necessary to
549 // ensure we do not remove or add data from the adapter while the layout is still being
550 // computed. RecyclerView documentation therefore recommends performing such changes in
551 // a Handler.
552 new android.os.Handler().post(new Runnable() {
553 @Override
554 public void run() {
555 // A config change may have destroyed the activity since the refresh started, so
556 // check for that to avoid an NPE.
557 if (getActivity() == null) {
558 return;
559 }
560
561 rotationHolder.bindRotationInitializationState(rotationInitializationState);
562 }
563 });
564 }
565 });
566 }
567
568 @Override
569 public void startRotation(@NetworkPreference final int networkPreference) {
570 if (!isRotationEnabled()) {
571 Log.e(TAG, "Rotation is not enabled for this category " + mCategory.getTitle());
572 return;
573 }
574
575 // ProgressDialog endlessly updates the UI thread, keeping it from going idle which therefore
576 // causes Espresso to hang once the dialog is shown.
577 if (mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE && !mTestingMode) {
578 int themeResId;
579 if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
580 themeResId = R.style.ProgressDialogThemePreL;
581 } else {
582 themeResId = R.style.LightDialogTheme;
583 }
584 mProgressDialog = new ProgressDialog(getActivity(), themeResId);
585
586 mProgressDialog.setTitle(PROGRESS_DIALOG_NO_TITLE);
587 mProgressDialog.setMessage(
588 getResources().getString(R.string.start_rotation_progress_message));
589 mProgressDialog.setIndeterminate(PROGRESS_DIALOG_INDETERMINATE);
590 mProgressDialog.show();
591 }
592
593 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
594 mAdapter.mPendingSelectedAdapterPosition = SPECIAL_FIXED_TILE_ADAPTER_POSITION;
595 }
596
597 final Context appContext = getActivity().getApplicationContext();
598
599 mWallpaperRotationInitializer.setFirstWallpaperInRotation(
600 appContext,
601 networkPreference,
602 new Listener() {
603 @Override
604 public void onFirstWallpaperInRotationSet() {
605 if (mProgressDialog != null) {
606 mProgressDialog.dismiss();
607 }
608
609 // The fragment may be detached from its containing activity if the user exits the
610 // app before the first wallpaper image in rotation finishes downloading.
611 Activity activity = getActivity();
612
Jon Miranda16ea1b12017-12-12 14:52:48 -0800613
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700614 if (mWallpaperRotationInitializer.startRotation(appContext)) {
615 if (activity != null
616 && mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
617 try {
618 Toast.makeText(getActivity(),
619 R.string.wallpaper_set_successfully_message,
620 Toast.LENGTH_SHORT).show();
621 } catch (NotFoundException e) {
622 Log.e(TAG, "Could not show toast " + e);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800623 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800624
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700625 activity.setResult(Activity.RESULT_OK);
626 activity.finish();
627 } else if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
628 mAdapter.updateSelectedTile(SPECIAL_FIXED_TILE_ADAPTER_POSITION);
629 }
630 } else { // Failed to start rotation.
631 showStartRotationErrorDialog(networkPreference);
632
633 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
634 DesktopRotationHolder rotationViewHolder =
635 (DesktopRotationHolder)
636 mImageGrid.findViewHolderForAdapterPosition(
637 SPECIAL_FIXED_TILE_ADAPTER_POSITION);
638 rotationViewHolder.setSelectionState(
639 SelectableHolder.SELECTION_STATE_DESELECTED);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800640 }
641 }
642 }
643
644 @Override
645 public void onError() {
646 if (mProgressDialog != null) {
647 mProgressDialog.dismiss();
648 }
649
650 showStartRotationErrorDialog(networkPreference);
651
652 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
653 DesktopRotationHolder rotationViewHolder =
654 (DesktopRotationHolder) mImageGrid.findViewHolderForAdapterPosition(
655 SPECIAL_FIXED_TILE_ADAPTER_POSITION);
656 rotationViewHolder.setSelectionState(SelectableHolder.SELECTION_STATE_DESELECTED);
657 }
658 }
659 });
660 }
661
662 private void showStartRotationErrorDialog(@NetworkPreference int networkPreference) {
663 BaseActivity activity = (BaseActivity) getActivity();
664 if (activity != null) {
665 StartRotationErrorDialogFragment startRotationErrorDialogFragment =
666 StartRotationErrorDialogFragment.newInstance(networkPreference);
667 startRotationErrorDialogFragment.setTargetFragment(
668 IndividualPickerFragment.this, UNUSED_REQUEST_CODE);
669
670 if (activity.isSafeToCommitFragmentTransaction()) {
671 startRotationErrorDialogFragment.show(
672 getFragmentManager(), TAG_START_ROTATION_ERROR_DIALOG);
673 } else {
674 mStagedStartRotationErrorDialogFragment = startRotationErrorDialogFragment;
675 }
676 }
677 }
678
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800679 int getNumColumns() {
Ching-Sung Li9df77a32019-07-10 11:45:30 +0800680 Activity activity = getActivity();
681 return activity == null ? 0 : TileSizeCalculator.getNumIndividualColumns(activity);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800682 }
683
684 /**
685 * Returns whether rotation is enabled for this category.
686 */
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800687 boolean isRotationEnabled() {
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700688 return mWallpaperRotationInitializer != null;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800689 }
690
691 @Override
692 public void onCurrentWallpaperRefreshed() {
693 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(true);
694 }
695
696 /**
697 * Shows a "set wallpaper" error dialog with a failure message and button to try again.
698 */
699 private void showSetWallpaperErrorDialog() {
700 SetWallpaperErrorDialogFragment dialogFragment = SetWallpaperErrorDialogFragment.newInstance(
701 R.string.set_wallpaper_error_message, WallpaperPersister.DEST_BOTH);
702 dialogFragment.setTargetFragment(this, UNUSED_REQUEST_CODE);
703
704 if (((BaseActivity) getActivity()).isSafeToCommitFragmentTransaction()) {
705 dialogFragment.show(getFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
706 } else {
707 mStagedSetWallpaperErrorDialogFragment = dialogFragment;
708 }
709 }
710
711 /**
712 * ViewHolder subclass for "daily refresh" tile in the RecyclerView, only shown if rotation is
713 * enabled for this category.
714 */
715 private class RotationHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
716
717 private FrameLayout mTileLayout;
718 private TextView mRotationMessage;
719 private TextView mRotationTitle;
720 private ImageView mRefreshIcon;
721
722 RotationHolder(View itemView) {
723 super(itemView);
724 itemView.setOnClickListener(this);
725
726 mTileLayout = (FrameLayout) itemView.findViewById(R.id.daily_refresh);
727 mRotationMessage = (TextView) itemView.findViewById(R.id.rotation_tile_message);
728 mRotationTitle = (TextView) itemView.findViewById(R.id.rotation_tile_title);
729 mRefreshIcon = (ImageView) itemView.findViewById(R.id.rotation_tile_refresh_icon);
730 mTileLayout.getLayoutParams().height = mTileSizePx.y;
731
732 // If the feature flag for "dynamic start rotation tile" is not enabled, fall back to the
733 // static UI with a blue accent color background and "Tap to turn on" text.
734 if (!Flags.dynamicStartRotationTileEnabled) {
735 mTileLayout.setBackgroundColor(
736 getResources().getColor(R.color.rotation_tile_enabled_background_color));
737 mRotationMessage.setText(R.string.daily_refresh_tile_subtitle);
738 mRotationTitle.setTextColor(
739 getResources().getColor(R.color.rotation_tile_enabled_title_text_color));
740 mRotationMessage.setTextColor(
741 getResources().getColor(R.color.rotation_tile_enabled_subtitle_text_color));
742 mRefreshIcon.setColorFilter(
743 getResources().getColor(R.color.rotation_tile_enabled_refresh_icon_color), Mode.SRC_IN);
744 return;
745 }
746
747 // Initialize the state of the "start rotation" tile (i.e., whether it is gray or blue to
748 // indicate if rotation is turned on for the current category) with last-known rotation state
749 // that could be stale. The last-known rotation state is correct in most cases and is a good
750 // starting point but may not be accurate if the user set a wallpaper through a 3rd party app
751 // while this app was paused.
752 int rotationState = mWallpaperRotationInitializer.getRotationInitializationStateDirty(
753 getContext());
754 bindRotationInitializationState(rotationState);
755 }
756
757 @Override
758 public void onClick(View v) {
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700759 DialogFragment startRotationDialogFragment = new StartRotationDialogFragment();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800760 startRotationDialogFragment.setTargetFragment(
761 IndividualPickerFragment.this, UNUSED_REQUEST_CODE);
762 startRotationDialogFragment.show(getFragmentManager(), TAG_START_ROTATION_DIALOG);
763 }
764
765 /**
766 * Binds the provided rotation initialization state to the RotationHolder and updates the tile's
767 * UI to be in sync with the state (i.e., message and color appropriately reflect the state to
768 * the user).
769 */
770 void bindRotationInitializationState(@RotationInitializationState int rotationState) {
771 int newBackgroundColor =
772 (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
773 ? getResources().getColor(R.color.rotation_tile_not_enabled_background_color)
774 : getResources().getColor(R.color.rotation_tile_enabled_background_color);
775 int newTitleTextColor =
776 (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
777 ? getResources().getColor(R.color.rotation_tile_not_enabled_title_text_color)
778 : getResources().getColor(R.color.rotation_tile_enabled_title_text_color);
779 int newSubtitleTextColor =
780 (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
781 ? getResources().getColor(R.color.rotation_tile_not_enabled_subtitle_text_color)
782 : getResources().getColor(R.color.rotation_tile_enabled_subtitle_text_color);
783 int newRefreshIconColor =
784 (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
785 ? getResources().getColor(R.color.rotation_tile_not_enabled_refresh_icon_color)
786 : getResources().getColor(R.color.rotation_tile_enabled_refresh_icon_color);
787
788 mTileLayout.setBackgroundColor(newBackgroundColor);
789 mRotationTitle.setTextColor(newTitleTextColor);
790 mRotationMessage.setText(getResIdForRotationState(rotationState));
791 mRotationMessage.setTextColor(newSubtitleTextColor);
792 mRefreshIcon.setColorFilter(newRefreshIconColor, Mode.SRC_IN);
793 }
794 }
795
796 /**
797 * RecyclerView Adapter subclass for the wallpaper tiles in the RecyclerView.
798 */
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800799 class IndividualAdapter extends RecyclerView.Adapter<ViewHolder> {
800 static final int ITEM_VIEW_TYPE_ROTATION = 1;
801 static final int ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER = 2;
802 static final int ITEM_VIEW_TYPE_MY_PHOTOS = 3;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800803
804 private final List<WallpaperInfo> mWallpapers;
805
806 private int mPendingSelectedAdapterPosition;
807 private int mSelectedAdapterPosition;
808
809 IndividualAdapter(List<WallpaperInfo> wallpapers) {
810 mWallpapers = wallpapers;
811 mPendingSelectedAdapterPosition = -1;
812 mSelectedAdapterPosition = -1;
813 }
814
815 @Override
816 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
817 switch (viewType) {
818 case ITEM_VIEW_TYPE_ROTATION:
819 return createRotationHolder(parent);
820 case ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER:
821 return createIndividualHolder(parent);
822 case ITEM_VIEW_TYPE_MY_PHOTOS:
823 return createMyPhotosHolder(parent);
824 default:
825 Log.e(TAG, "Unsupported viewType " + viewType + " in IndividualAdapter");
826 return null;
827 }
828 }
829
830 @Override
831 public int getItemViewType(int position) {
832 if (isRotationEnabled() && position == SPECIAL_FIXED_TILE_ADAPTER_POSITION) {
833 return ITEM_VIEW_TYPE_ROTATION;
834 }
835
836 // A category cannot have both a "start rotation" tile and a "my photos" tile.
837 if (mCategory.supportsCustomPhotos()
838 && !isRotationEnabled()
839 && position == SPECIAL_FIXED_TILE_ADAPTER_POSITION) {
840 return ITEM_VIEW_TYPE_MY_PHOTOS;
841 }
842
843 return ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER;
844 }
845
846 @Override
847 public void onBindViewHolder(ViewHolder holder, int position) {
848 int viewType = getItemViewType(position);
849
850 switch (viewType) {
851 case ITEM_VIEW_TYPE_ROTATION:
852 onBindRotationHolder(holder, position);
853 break;
854 case ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER:
855 onBindIndividualHolder(holder, position);
856 break;
857 case ITEM_VIEW_TYPE_MY_PHOTOS:
858 ((MyPhotosViewHolder) holder).bind();
859 break;
860 default:
861 Log.e(TAG, "Unsupported viewType " + viewType + " in IndividualAdapter");
862 }
863 }
864
865 @Override
866 public int getItemCount() {
867 return (isRotationEnabled() || mCategory.supportsCustomPhotos())
868 ? mWallpapers.size() + 1
869 : mWallpapers.size();
870 }
871
872 private ViewHolder createRotationHolder(ViewGroup parent) {
873 LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
874 View view;
875
876 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
877 view = layoutInflater.inflate(R.layout.grid_item_rotation_desktop, parent, false);
878 SelectionAnimator selectionAnimator =
879 new CheckmarkSelectionAnimator(getActivity(), view);
880 return new DesktopRotationHolder(
881 getActivity(), mTileSizePx.y, view, selectionAnimator,
882 IndividualPickerFragment.this);
883 } else { // MOBILE
884 view = layoutInflater.inflate(R.layout.grid_item_rotation, parent, false);
885 return new RotationHolder(view);
886 }
887 }
888
889 private ViewHolder createIndividualHolder(ViewGroup parent) {
890 LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
891 View view = layoutInflater.inflate(R.layout.grid_item_image, parent, false);
892
893 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
894 SelectionAnimator selectionAnimator =
895 new CheckmarkSelectionAnimator(getActivity(), view);
896 return new SetIndividualHolder(
897 getActivity(), mTileSizePx.y, view,
898 selectionAnimator,
899 new OnSetListener() {
900 @Override
901 public void onPendingWallpaperSet(int adapterPosition) {
902 // Deselect and hide loading indicator for any previously pending tile.
903 if (mPendingSelectedAdapterPosition != -1) {
904 ViewHolder oldViewHolder = mImageGrid.findViewHolderForAdapterPosition(
905 mPendingSelectedAdapterPosition);
906 if (oldViewHolder instanceof SelectableHolder) {
907 ((SelectableHolder) oldViewHolder).setSelectionState(
908 SelectableHolder.SELECTION_STATE_DESELECTED);
909 }
910 }
911
912 if (mSelectedAdapterPosition != -1) {
913 ViewHolder oldViewHolder = mImageGrid.findViewHolderForAdapterPosition(
914 mSelectedAdapterPosition);
915 if (oldViewHolder instanceof SelectableHolder) {
916 ((SelectableHolder) oldViewHolder).setSelectionState(
917 SelectableHolder.SELECTION_STATE_DESELECTED);
918 }
919 }
920
921 mPendingSelectedAdapterPosition = adapterPosition;
922 }
923
924 @Override
925 public void onWallpaperSet(int adapterPosition) {
926 // No-op -- UI handles a new wallpaper being set by reacting to the
927 // WallpaperChangedNotifier.
928 }
929
930 @Override
931 public void onWallpaperSetFailed(SetIndividualHolder holder) {
932 showSetWallpaperErrorDialog();
933 mPendingSetIndividualHolder = holder;
934 }
935 });
936 } else { // MOBILE
937 return new PreviewIndividualHolder(
938 (IndividualPickerActivity) getActivity(), mTileSizePx.y, view);
939 }
940 }
941
942 private ViewHolder createMyPhotosHolder(ViewGroup parent) {
943 LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
944 View view = layoutInflater.inflate(R.layout.grid_item_my_photos, parent, false);
945
Santiago Etchebeherefab49612019-01-15 12:22:42 -0800946 return new MyPhotosViewHolder(getActivity(),
947 ((MyPhotosStarterProvider) getActivity()).getMyPhotosStarter(),
948 mTileSizePx.y, view);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800949 }
950
951 /**
952 * Marks the tile at the given position as selected with a visual indication. Also updates the
953 * "currently selected" BottomSheet to reflect the newly selected tile.
954 */
955 private void updateSelectedTile(int newlySelectedPosition) {
956 // Prevent multiple spinners from appearing with a user tapping several tiles in rapid
957 // succession.
958 if (mPendingSelectedAdapterPosition == mSelectedAdapterPosition) {
959 return;
960 }
961
962 if (mCurrentWallpaperBottomSheetPresenter != null) {
963 mCurrentWallpaperBottomSheetPresenter.refreshCurrentWallpapers(
964 IndividualPickerFragment.this);
965
966 if (mCurrentWallpaperBottomSheetExpandedRunnable != null) {
967 mHandler.removeCallbacks(mCurrentWallpaperBottomSheetExpandedRunnable);
968 }
969 mCurrentWallpaperBottomSheetExpandedRunnable = new Runnable() {
970 @Override
971 public void run() {
972 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(true);
973 }
974 };
975 mHandler.postDelayed(mCurrentWallpaperBottomSheetExpandedRunnable, 100);
976 }
977
978 // User may have switched to another category, thus detaching this fragment, so check here.
979 // NOTE: We do this check after updating the current wallpaper BottomSheet so that the update
980 // still occurs in the UI after the user selects that other category.
981 if (getActivity() == null) {
982 return;
983 }
984
985 // Update the newly selected wallpaper ViewHolder and the old one so that if
986 // selection UI state applies (desktop UI), it is updated.
987 if (mSelectedAdapterPosition >= 0) {
988 ViewHolder oldViewHolder = mImageGrid.findViewHolderForAdapterPosition(
989 mSelectedAdapterPosition);
990 if (oldViewHolder instanceof SelectableHolder) {
991 ((SelectableHolder) oldViewHolder).setSelectionState(
992 SelectableHolder.SELECTION_STATE_DESELECTED);
993 }
994 }
995
996 // Animate selection of newly selected tile.
997 ViewHolder newViewHolder = mImageGrid
998 .findViewHolderForAdapterPosition(newlySelectedPosition);
999 if (newViewHolder instanceof SelectableHolder) {
1000 ((SelectableHolder) newViewHolder).setSelectionState(
1001 SelectableHolder.SELECTION_STATE_SELECTED);
1002 }
1003
1004 mSelectedAdapterPosition = newlySelectedPosition;
1005
1006 // If the tile was in the last row of the grid, add space below it so the user can scroll down
1007 // and up to see the BottomSheet without it fully overlapping the newly selected tile.
1008 int spanCount = ((GridLayoutManager) mImageGrid.getLayoutManager()).getSpanCount();
1009 int numRows = (int) Math.ceil((float) getItemCount() / spanCount);
1010 int rowOfNewlySelectedTile = newlySelectedPosition / spanCount;
1011 boolean isInLastRow = rowOfNewlySelectedTile == numRows - 1;
1012
1013 updateImageGridPadding(isInLastRow /* addExtraBottomSpace */);
1014 }
1015
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +08001016 void onBindRotationHolder(ViewHolder holder, int position) {
Jon Miranda16ea1b12017-12-12 14:52:48 -08001017 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
1018 String collectionId = mCategory.getCollectionId();
1019 ((DesktopRotationHolder) holder).bind(collectionId);
1020
1021 if (mWallpaperPreferences.getWallpaperPresentationMode()
1022 == WallpaperPreferences.PRESENTATION_MODE_ROTATING
1023 && collectionId.equals(mWallpaperPreferences.getHomeWallpaperCollectionId())) {
1024 mSelectedAdapterPosition = position;
1025 }
1026
1027 if (!mWasUpdateRunnableRun && !mWallpapers.isEmpty()) {
1028 updateDesktopDailyRotationThumbnail((DesktopRotationHolder) holder);
1029 mWasUpdateRunnableRun = true;
1030 }
1031 }
1032 }
1033
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +08001034 void onBindIndividualHolder(ViewHolder holder, int position) {
Jon Miranda16ea1b12017-12-12 14:52:48 -08001035 int wallpaperIndex = (isRotationEnabled() || mCategory.supportsCustomPhotos())
1036 ? position - 1 : position;
1037 WallpaperInfo wallpaper = mWallpapers.get(wallpaperIndex);
1038 ((IndividualHolder) holder).bindWallpaper(wallpaper);
1039 WallpaperPreferences prefs = InjectorProvider.getInjector().getPreferences(getContext());
1040
1041 String wallpaperId = wallpaper.getWallpaperId();
1042 if (wallpaperId != null && wallpaperId.equals(prefs.getHomeWallpaperRemoteId())) {
1043 mSelectedAdapterPosition = position;
1044 }
1045 }
1046 }
1047}