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