blob: 941b198a56c3f64617a85688fa94beb144e5a074 [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 Liao69630f12020-03-05 19:01:25 +080018import static com.android.wallpaper.widget.BottomActionBar.BottomAction.APPLY;
Chuck Liao8ec38e02020-02-26 20:59:32 +080019import static com.android.wallpaper.widget.BottomActionBar.BottomAction.CANCEL;
20import static com.android.wallpaper.widget.BottomActionBar.BottomAction.ROTATION;
21
Jon Miranda16ea1b12017-12-12 14:52:48 -080022import android.app.Activity;
23import android.app.ProgressDialog;
24import android.content.Context;
Santiago Etchebehereb1854472019-06-06 17:44:54 -070025import android.content.res.Configuration;
Jon Miranda16ea1b12017-12-12 14:52:48 -080026import android.content.res.Resources.NotFoundException;
27import android.graphics.Point;
28import android.graphics.PorterDuff.Mode;
29import android.os.Build.VERSION;
30import android.os.Build.VERSION_CODES;
31import android.os.Bundle;
32import android.os.Handler;
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -070033import android.service.wallpaper.WallpaperService;
Jon Miranda16ea1b12017-12-12 14:52:48 -080034import android.util.Log;
35import android.view.LayoutInflater;
36import android.view.View;
37import android.view.ViewGroup;
38import android.widget.FrameLayout;
39import android.widget.ImageView;
40import android.widget.TextView;
41import android.widget.Toast;
42
Santiago Etchebehereb1854472019-06-06 17:44:54 -070043import androidx.annotation.NonNull;
Chuck Liao69630f12020-03-05 19:01:25 +080044import androidx.annotation.Nullable;
Santiago Etchebeherefab49612019-01-15 12:22:42 -080045import androidx.fragment.app.DialogFragment;
46import androidx.fragment.app.Fragment;
47import androidx.recyclerview.widget.GridLayoutManager;
48import androidx.recyclerview.widget.RecyclerView;
49import androidx.recyclerview.widget.RecyclerView.OnScrollListener;
50import androidx.recyclerview.widget.RecyclerView.ViewHolder;
51
Jon Miranda16ea1b12017-12-12 14:52:48 -080052import com.android.wallpaper.R;
53import com.android.wallpaper.asset.Asset;
54import com.android.wallpaper.asset.Asset.DrawableLoadedListener;
55import com.android.wallpaper.config.Flags;
Santiago Etchebehere8648bb82019-08-06 17:09:02 -070056import com.android.wallpaper.model.Category;
57import com.android.wallpaper.model.CategoryProvider;
58import com.android.wallpaper.model.CategoryReceiver;
Chuck Liao69630f12020-03-05 19:01:25 +080059import com.android.wallpaper.model.LiveWallpaperInfo;
Jon Miranda16ea1b12017-12-12 14:52:48 -080060import com.android.wallpaper.model.WallpaperCategory;
61import com.android.wallpaper.model.WallpaperInfo;
62import com.android.wallpaper.model.WallpaperReceiver;
63import com.android.wallpaper.model.WallpaperRotationInitializer;
64import com.android.wallpaper.model.WallpaperRotationInitializer.Listener;
65import com.android.wallpaper.model.WallpaperRotationInitializer.NetworkPreference;
66import com.android.wallpaper.model.WallpaperRotationInitializer.RotationInitializationState;
Jon Miranda16ea1b12017-12-12 14:52:48 -080067import com.android.wallpaper.module.FormFactorChecker;
68import com.android.wallpaper.module.FormFactorChecker.FormFactor;
69import com.android.wallpaper.module.Injector;
70import com.android.wallpaper.module.InjectorProvider;
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -070071import com.android.wallpaper.module.PackageStatusNotifier;
Jon Miranda16ea1b12017-12-12 14:52:48 -080072import com.android.wallpaper.module.WallpaperChangedNotifier;
73import com.android.wallpaper.module.WallpaperPersister;
74import com.android.wallpaper.module.WallpaperPersister.Destination;
75import com.android.wallpaper.module.WallpaperPreferences;
Chuck Liao69630f12020-03-05 19:01:25 +080076import com.android.wallpaper.module.WallpaperSetter;
Jon Miranda16ea1b12017-12-12 14:52:48 -080077import com.android.wallpaper.picker.BaseActivity;
78import com.android.wallpaper.picker.CurrentWallpaperBottomSheetPresenter;
Santiago Etchebeherefab49612019-01-15 12:22:42 -080079import com.android.wallpaper.picker.MyPhotosStarter.MyPhotosStarterProvider;
Jon Miranda16ea1b12017-12-12 14:52:48 -080080import com.android.wallpaper.picker.RotationStarter;
Chuck Liao69630f12020-03-05 19:01:25 +080081import com.android.wallpaper.picker.SetWallpaperDialogFragment;
Jon Miranda16ea1b12017-12-12 14:52:48 -080082import com.android.wallpaper.picker.SetWallpaperErrorDialogFragment;
83import com.android.wallpaper.picker.StartRotationDialogFragment;
84import com.android.wallpaper.picker.StartRotationErrorDialogFragment;
85import com.android.wallpaper.picker.WallpapersUiContainer;
86import com.android.wallpaper.picker.individual.SetIndividualHolder.OnSetListener;
87import com.android.wallpaper.util.DiskBasedLogger;
88import com.android.wallpaper.util.TileSizeCalculator;
Chuck Liao8ec38e02020-02-26 20:59:32 +080089import com.android.wallpaper.widget.BottomActionBar;
Jon Miranda16ea1b12017-12-12 14:52:48 -080090import com.android.wallpaper.widget.GridMarginDecoration;
Sunny Goyal8600a3f2018-08-15 12:48:01 -070091
Jon Miranda16ea1b12017-12-12 14:52:48 -080092import com.bumptech.glide.Glide;
93import com.bumptech.glide.MemoryCategory;
94
95import java.util.ArrayList;
96import java.util.Date;
Chuck Liao8ec38e02020-02-26 20:59:32 +080097import java.util.EnumSet;
Jon Miranda16ea1b12017-12-12 14:52:48 -080098import java.util.List;
99import java.util.Random;
100
101/**
102 * Displays the Main UI for picking an individual wallpaper image.
103 */
104public class IndividualPickerFragment extends Fragment
105 implements RotationStarter, StartRotationErrorDialogFragment.Listener,
106 CurrentWallpaperBottomSheetPresenter.RefreshListener,
Chuck Liao69630f12020-03-05 19:01:25 +0800107 SetWallpaperErrorDialogFragment.Listener, SetWallpaperDialogFragment.Listener {
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800108 /**
109 * Position of a special tile that doesn't belong to an individual wallpaper of the category,
110 * such as "my photos" or "daily rotation".
111 */
112 static final int SPECIAL_FIXED_TILE_ADAPTER_POSITION = 0;
113 static final String ARG_CATEGORY_COLLECTION_ID = "category_collection_id";
114
Jon Miranda16ea1b12017-12-12 14:52:48 -0800115 private static final String TAG = "IndividualPickerFrgmnt";
Jon Miranda16ea1b12017-12-12 14:52:48 -0800116 private static final int UNUSED_REQUEST_CODE = 1;
117 private static final String TAG_START_ROTATION_DIALOG = "start_rotation_dialog";
118 private static final String TAG_START_ROTATION_ERROR_DIALOG = "start_rotation_error_dialog";
119 private static final String PROGRESS_DIALOG_NO_TITLE = null;
120 private static final boolean PROGRESS_DIALOG_INDETERMINATE = true;
121 private static final String TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT =
122 "individual_set_wallpaper_error_dialog";
Santiago Etchebehereb1854472019-06-06 17:44:54 -0700123 private static final String KEY_NIGHT_MODE = "IndividualPickerFragment.NIGHT_MODE";
Jon Miranda16ea1b12017-12-12 14:52:48 -0800124
Chuck Liao8ec38e02020-02-26 20:59:32 +0800125 /**
126 * A temporary flag to hide the bottom action bar feature.
127 */
128 private static final boolean TEMP_BOTTOM_ACTION_BAR_FEATURE = false;
129
Chuck Liaof40063f2020-03-03 18:35:24 +0800130 /**
131 * An interface for updating the thumbnail with the specific wallpaper.
132 */
133 public interface ThumbnailUpdater {
134 /**
135 * Updates the thumbnail with the specific wallpaper.
136 */
137 void updateThumbnail(WallpaperInfo wallpaperInfo);
138
139 /**
140 * Restores to the thumbnails of the wallpapers which were applied.
141 */
142 void restoreThumbnails();
143 }
144
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800145 WallpaperPreferences mWallpaperPreferences;
146 WallpaperChangedNotifier mWallpaperChangedNotifier;
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800147 RecyclerView mImageGrid;
148 IndividualAdapter mAdapter;
149 WallpaperCategory mCategory;
150 WallpaperRotationInitializer mWallpaperRotationInitializer;
151 List<WallpaperInfo> mWallpapers;
152 Point mTileSizePx;
153 WallpapersUiContainer mWallpapersUiContainer;
154 @FormFactor
155 int mFormFactor;
156 PackageStatusNotifier mPackageStatusNotifier;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800157
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800158 Handler mHandler;
159 Random mRandom;
160
161 WallpaperChangedNotifier.Listener mWallpaperChangedListener =
162 new WallpaperChangedNotifier.Listener() {
163 @Override
164 public void onWallpaperChanged() {
165 if (mFormFactor != FormFactorChecker.FORM_FACTOR_DESKTOP) {
166 return;
167 }
168
169 ViewHolder selectedViewHolder = mImageGrid.findViewHolderForAdapterPosition(
170 mAdapter.mSelectedAdapterPosition);
171
172 // Null remote ID => My Photos wallpaper, so deselect whatever was previously selected.
173 if (mWallpaperPreferences.getHomeWallpaperRemoteId() == null) {
174 if (selectedViewHolder instanceof SelectableHolder) {
175 ((SelectableHolder) selectedViewHolder).setSelectionState(
176 SelectableHolder.SELECTION_STATE_DESELECTED);
177 }
178 } else {
179 mAdapter.updateSelectedTile(mAdapter.mPendingSelectedAdapterPosition);
180 }
181 }
182 };
183 PackageStatusNotifier.Listener mAppStatusListener;
184
Jon Miranda16ea1b12017-12-12 14:52:48 -0800185 private ProgressDialog mProgressDialog;
186 private boolean mTestingMode;
187 private CurrentWallpaperBottomSheetPresenter mCurrentWallpaperBottomSheetPresenter;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800188 private SetIndividualHolder mPendingSetIndividualHolder;
189
190 /**
191 * Staged error dialog fragments that were unable to be shown when the activity didn't allow
192 * committing fragment transactions.
193 */
194 private SetWallpaperErrorDialogFragment mStagedSetWallpaperErrorDialogFragment;
195 private StartRotationErrorDialogFragment mStagedStartRotationErrorDialogFragment;
196
Jon Miranda16ea1b12017-12-12 14:52:48 -0800197 private Runnable mCurrentWallpaperBottomSheetExpandedRunnable;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800198
199 /**
200 * Whether {@code mUpdateDailyWallpaperThumbRunnable} has been run at least once in this
201 * invocation of the fragment.
202 */
203 private boolean mWasUpdateRunnableRun;
204
205 /**
206 * A Runnable which regularly updates the thumbnail for the "Daily wallpapers" tile in desktop
207 * mode.
208 */
209 private Runnable mUpdateDailyWallpaperThumbRunnable = new Runnable() {
210 @Override
211 public void run() {
212 ViewHolder viewHolder = mImageGrid.findViewHolderForAdapterPosition(
213 SPECIAL_FIXED_TILE_ADAPTER_POSITION);
214 if (viewHolder instanceof DesktopRotationHolder) {
215 updateDesktopDailyRotationThumbnail((DesktopRotationHolder) viewHolder);
216 } else { // viewHolder is null
217 // If the rotation tile is unavailable (because user has scrolled down, causing the
218 // ViewHolder to be recycled), schedule the update for some time later. Once user scrolls up
219 // again, the ViewHolder will be re-bound and its thumbnail will be updated.
220 mHandler.postDelayed(mUpdateDailyWallpaperThumbRunnable,
221 DesktopRotationHolder.CROSSFADE_DURATION_MILLIS
222 + DesktopRotationHolder.CROSSFADE_DURATION_PAUSE_MILLIS);
223 }
224 }
225 };
226
Chuck Liao8ec38e02020-02-26 20:59:32 +0800227 private BottomActionBar mBottomActionBar;
Chuck Liao69630f12020-03-05 19:01:25 +0800228 private WallpaperSetter mWallpaperSetter;
229 private WallpaperInfo mSelectedWallpaperInfo;
Chuck Liao8ec38e02020-02-26 20:59:32 +0800230
Jon Miranda16ea1b12017-12-12 14:52:48 -0800231 public static IndividualPickerFragment newInstance(String collectionId) {
232 Bundle args = new Bundle();
233 args.putString(ARG_CATEGORY_COLLECTION_ID, collectionId);
234
235 IndividualPickerFragment fragment = new IndividualPickerFragment();
236 fragment.setArguments(args);
237 return fragment;
238 }
239
240 private static int getResIdForRotationState(@RotationInitializationState int rotationState) {
241 switch (rotationState) {
242 case WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED:
243 return R.string.daily_refresh_tile_subtitle;
244 case WallpaperRotationInitializer.ROTATION_HOME_ONLY:
245 return R.string.home_screen_message;
246 case WallpaperRotationInitializer.ROTATION_HOME_AND_LOCK:
247 return R.string.home_and_lock_short_label;
248 default:
249 Log.e(TAG, "Unknown rotation intialization state: " + rotationState);
250 return R.string.home_screen_message;
251 }
252 }
253
254 private void updateDesktopDailyRotationThumbnail(DesktopRotationHolder holder) {
255 int wallpapersIndex = mRandom.nextInt(mWallpapers.size());
256 Asset newThumbnailAsset = mWallpapers.get(wallpapersIndex).getThumbAsset(
257 getActivity());
258 holder.updateThumbnail(newThumbnailAsset, new DrawableLoadedListener() {
259 @Override
260 public void onDrawableLoaded() {
261 if (getActivity() == null) {
262 return;
263 }
264
265 // Schedule the next update of the thumbnail.
266 int delayMillis = DesktopRotationHolder.CROSSFADE_DURATION_MILLIS
267 + DesktopRotationHolder.CROSSFADE_DURATION_PAUSE_MILLIS;
268 mHandler.postDelayed(mUpdateDailyWallpaperThumbRunnable, delayMillis);
269 }
270 });
271 }
272
273 @Override
274 public void onCreate(Bundle savedInstanceState) {
275 super.onCreate(savedInstanceState);
276
277 Injector injector = InjectorProvider.getInjector();
278 Context appContext = getContext().getApplicationContext();
279 mWallpaperPreferences = injector.getPreferences(appContext);
280
281 mWallpaperChangedNotifier = WallpaperChangedNotifier.getInstance();
282 mWallpaperChangedNotifier.registerListener(mWallpaperChangedListener);
283
Jon Miranda16ea1b12017-12-12 14:52:48 -0800284 mFormFactor = injector.getFormFactorChecker(appContext).getFormFactor();
285
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700286 mPackageStatusNotifier = injector.getPackageStatusNotifier(appContext);
287
Chuck Liao69630f12020-03-05 19:01:25 +0800288 mWallpaperSetter = new WallpaperSetter(
289 injector.getWallpaperPersister(appContext),
290 injector.getPreferences(appContext),
291 injector.getUserEventLogger(appContext),
292 false);
293
Jon Miranda16ea1b12017-12-12 14:52:48 -0800294 mWallpapers = new ArrayList<>();
295 mRandom = new Random();
296 mHandler = new Handler();
297
Santiago Etchebehereb1854472019-06-06 17:44:54 -0700298 // Clear Glide's cache if night-mode changed to ensure thumbnails are reloaded
299 if (savedInstanceState != null && (savedInstanceState.getInt(KEY_NIGHT_MODE)
300 != (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK))) {
301 Glide.get(getContext()).clearMemory();
302 }
303
Santiago Etchebehere8648bb82019-08-06 17:09:02 -0700304 CategoryProvider categoryProvider = injector.getCategoryProvider(appContext);
305 categoryProvider.fetchCategories(new CategoryReceiver() {
306 @Override
307 public void onCategoryReceived(Category category) {
308 // Do nothing.
309 }
310
311 @Override
312 public void doneFetchingCategories() {
313 mCategory = (WallpaperCategory) categoryProvider.getCategory(
314 getArguments().getString(ARG_CATEGORY_COLLECTION_ID));
315 if (mCategory == null) {
316 DiskBasedLogger.e(TAG, "Failed to find the category.", getContext());
317
318 // The absence of this category in the CategoryProvider indicates a broken
319 // state, see b/38030129. Hence, finish the activity and return.
320 getActivity().finish();
321 return;
322 }
323 onCategoryLoaded();
324 }
325 }, false);
326 }
327
328 protected void onCategoryLoaded() {
329 mWallpaperRotationInitializer = mCategory.getWallpaperRotationInitializer();
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700330 fetchWallpapers(false);
331
332 if (mCategory.supportsThirdParty()) {
333 mAppStatusListener = (packageName, status) -> {
334 if (status != PackageStatusNotifier.PackageStatus.REMOVED ||
335 mCategory.containsThirdParty(packageName)) {
336 fetchWallpapers(true);
337 }
338 };
339 mPackageStatusNotifier.addListener(mAppStatusListener,
340 WallpaperService.SERVICE_INTERFACE);
341 }
Santiago Etchebehere8648bb82019-08-06 17:09:02 -0700342
Santiago Etchebehere460fbcb2019-08-20 11:47:30 -0700343 maybeSetUpImageGrid();
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700344 }
345
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800346 void fetchWallpapers(boolean forceReload) {
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700347 mWallpapers.clear();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800348 mCategory.fetchWallpapers(getActivity().getApplicationContext(), new WallpaperReceiver() {
349 @Override
350 public void onWallpapersReceived(List<WallpaperInfo> wallpapers) {
351 for (WallpaperInfo wallpaper : wallpapers) {
352 mWallpapers.add(wallpaper);
353 }
354
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700355 // Wallpapers may load after the adapter is initialized, in which case we have
356 // to explicitly notify that the data set has changed.
Jon Miranda16ea1b12017-12-12 14:52:48 -0800357 if (mAdapter != null) {
358 mAdapter.notifyDataSetChanged();
359 }
360
361 if (mWallpapersUiContainer != null) {
362 mWallpapersUiContainer.onWallpapersReady();
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700363 } else {
364 if (wallpapers.isEmpty()) {
365 // If there are no more wallpapers and we're on phone, just finish the
366 // Activity.
367 Activity activity = getActivity();
368 if (activity != null
369 && mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
370 activity.finish();
371 }
372 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800373 }
374 }
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700375 }, forceReload);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800376 }
377
378 @Override
Santiago Etchebehereb1854472019-06-06 17:44:54 -0700379 public void onSaveInstanceState(@NonNull Bundle outState) {
380 super.onSaveInstanceState(outState);
Santiago Etchebehere0ec065c2019-06-13 11:30:21 -0700381 outState.putInt(KEY_NIGHT_MODE,
Santiago Etchebehereb1854472019-06-06 17:44:54 -0700382 getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK);
383 }
384
385 @Override
Jon Miranda16ea1b12017-12-12 14:52:48 -0800386 public View onCreateView(LayoutInflater inflater, ViewGroup container,
387 Bundle savedInstanceState) {
388 View view = inflater.inflate(R.layout.fragment_individual_picker, container, false);
389
390 mTileSizePx = TileSizeCalculator.getIndividualTileSize(getActivity());
391
392 mImageGrid = (RecyclerView) view.findViewById(R.id.wallpaper_grid);
393 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
394 int gridPaddingPx = getResources().getDimensionPixelSize(R.dimen.grid_padding_desktop);
395 updateImageGridPadding(false /* addExtraBottomSpace */);
396 mImageGrid.setScrollBarSize(gridPaddingPx);
397 }
398 GridMarginDecoration.applyTo(mImageGrid);
399
Santiago Etchebehere460fbcb2019-08-20 11:47:30 -0700400 maybeSetUpImageGrid();
Santiago Etchebehere8648bb82019-08-06 17:09:02 -0700401
Jon Miranda16ea1b12017-12-12 14:52:48 -0800402 setUpBottomSheet();
403
Chuck Liao8ec38e02020-02-26 20:59:32 +0800404 if (TEMP_BOTTOM_ACTION_BAR_FEATURE) {
405 mBottomActionBar = getActivity().findViewById(R.id.bottom_actionbar);
406
Chuck Liao69630f12020-03-05 19:01:25 +0800407 mBottomActionBar.setActionClickListener(CANCEL, unused -> {
408 if (mSelectedWallpaperInfo != null) {
409 onWallpaperSelected(null);
410 return;
411 }
412 getActivity().onBackPressed();
413 });
Chuck Liao8ec38e02020-02-26 20:59:32 +0800414 mBottomActionBar.setActionClickListener(ROTATION, unused -> {
415 DialogFragment startRotationDialogFragment = new StartRotationDialogFragment();
416 startRotationDialogFragment.setTargetFragment(
417 IndividualPickerFragment.this, UNUSED_REQUEST_CODE);
418 startRotationDialogFragment.show(getFragmentManager(), TAG_START_ROTATION_DIALOG);
419 });
Chuck Liao69630f12020-03-05 19:01:25 +0800420 mBottomActionBar.setActionClickListener(APPLY, unused -> {
421 mBottomActionBar.disableActions();
422 mWallpaperSetter.requestDestination(getActivity(), getFragmentManager(), this,
423 mSelectedWallpaperInfo instanceof LiveWallpaperInfo);
424 });
Chuck Liao8ec38e02020-02-26 20:59:32 +0800425
426 mBottomActionBar.setVisibility(View.VISIBLE);
427 mBottomActionBar.showActionsOnly(
428 isRotationEnabled() ? EnumSet.of(CANCEL, ROTATION) : EnumSet.of(CANCEL));
429 }
430
Jon Miranda16ea1b12017-12-12 14:52:48 -0800431 return view;
432 }
433
434 @Override
Chuck Liao8ec38e02020-02-26 20:59:32 +0800435 public void onDestroyView() {
436 if (TEMP_BOTTOM_ACTION_BAR_FEATURE) {
437 mBottomActionBar.setVisibility(View.GONE);
438 mBottomActionBar.clearActionClickListeners();
Chuck Liao69630f12020-03-05 19:01:25 +0800439 updateThumbnail(null);
Chuck Liao8ec38e02020-02-26 20:59:32 +0800440 }
441 super.onDestroyView();
442 }
443
444 @Override
Jon Miranda16ea1b12017-12-12 14:52:48 -0800445 public void onClickTryAgain(@Destination int unused) {
446 if (mPendingSetIndividualHolder != null) {
447 mPendingSetIndividualHolder.setWallpaper();
448 }
449 }
450
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800451 void updateImageGridPadding(boolean addExtraBottomSpace) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800452 int gridPaddingPx = getResources().getDimensionPixelSize(R.dimen.grid_padding_desktop);
453 int bottomSheetHeightPx = getResources().getDimensionPixelSize(
454 R.dimen.current_wallpaper_bottom_sheet_layout_height);
455 int paddingBottomPx = addExtraBottomSpace ? bottomSheetHeightPx : 0;
456 // Only left and top may be set in order for the GridMarginDecoration to work properly.
457 mImageGrid.setPadding(
458 gridPaddingPx, gridPaddingPx, 0, paddingBottomPx);
459 }
460
Santiago Etchebehere460fbcb2019-08-20 11:47:30 -0700461 private void maybeSetUpImageGrid() {
Santiago Etchebehere8648bb82019-08-06 17:09:02 -0700462 // Skip if mImageGrid been initialized yet
463 if (mImageGrid == null) {
464 return;
465 }
466 // Skip if category hasn't loaded yet
467 if (mCategory == null) {
468 return;
469 }
470 // Skip if the adapter was already created
471 if (mAdapter != null) {
472 return;
473 }
Santiago Etchebehere460fbcb2019-08-20 11:47:30 -0700474 setUpImageGrid();
475 }
476
477 /**
478 * Create the adapter and assign it to mImageGrid.
479 * Both mImageGrid and mCategory are guaranteed to not be null when this method is called.
480 */
481 void setUpImageGrid() {
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800482 mAdapter = new IndividualAdapter(mWallpapers);
483 mImageGrid.setAdapter(mAdapter);
484 mImageGrid.setLayoutManager(new GridLayoutManager(getActivity(), getNumColumns()));
485 }
486
Jon Miranda16ea1b12017-12-12 14:52:48 -0800487 /**
488 * Enables and populates the "Currently set" wallpaper BottomSheet.
489 */
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800490 void setUpBottomSheet() {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800491 mImageGrid.addOnScrollListener(new OnScrollListener() {
492 @Override
493 public void onScrolled(RecyclerView recyclerView, int dx, final int dy) {
494 if (mCurrentWallpaperBottomSheetPresenter == null) {
495 return;
496 }
497
498 if (mCurrentWallpaperBottomSheetExpandedRunnable != null) {
499 mHandler.removeCallbacks(mCurrentWallpaperBottomSheetExpandedRunnable);
500 }
501 mCurrentWallpaperBottomSheetExpandedRunnable = new Runnable() {
502 @Override
503 public void run() {
504 if (dy > 0) {
505 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(false);
506 } else {
507 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(true);
508 }
509 }
510 };
511 mHandler.postDelayed(mCurrentWallpaperBottomSheetExpandedRunnable, 100);
512 }
513 });
514 }
515
516 @Override
517 public void onResume() {
518 super.onResume();
519
Santiago Etchebehere8cad0dd2019-10-17 10:52:39 -0700520 WallpaperPreferences preferences = InjectorProvider.getInjector()
521 .getPreferences(getActivity());
Jon Miranda16ea1b12017-12-12 14:52:48 -0800522 preferences.setLastAppActiveTimestamp(new Date().getTime());
523
524 // Reset Glide memory settings to a "normal" level of usage since it may have been lowered in
525 // PreviewFragment.
526 Glide.get(getActivity()).setMemoryCategory(MemoryCategory.NORMAL);
527
528 // Show the staged 'start rotation' error dialog fragment if there is one that was unable to be
529 // shown earlier when this fragment's hosting activity didn't allow committing fragment
530 // transactions.
531 if (mStagedStartRotationErrorDialogFragment != null) {
532 mStagedStartRotationErrorDialogFragment.show(
533 getFragmentManager(), TAG_START_ROTATION_ERROR_DIALOG);
534 mStagedStartRotationErrorDialogFragment = null;
535 }
536
537 // Show the staged 'load wallpaper' or 'set wallpaper' error dialog fragments if there is one
538 // that was unable to be shown earlier when this fragment's hosting activity didn't allow
539 // committing fragment transactions.
540 if (mStagedSetWallpaperErrorDialogFragment != null) {
541 mStagedSetWallpaperErrorDialogFragment.show(
542 getFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
543 mStagedSetWallpaperErrorDialogFragment = null;
544 }
545
546 if (isRotationEnabled()) {
547 if (mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
548 // Refresh the state of the "start rotation" in case something changed the current daily
549 // rotation while this fragment was paused.
550 RotationHolder rotationHolder = (RotationHolder) mImageGrid
551 .findViewHolderForAdapterPosition(
552 SPECIAL_FIXED_TILE_ADAPTER_POSITION);
553 // The RotationHolder may be null if the RecyclerView has not created the view
554 // holder yet.
555 if (rotationHolder != null && Flags.dynamicStartRotationTileEnabled) {
556 refreshRotationHolder(rotationHolder);
557 }
558 } else if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
559 if (mWasUpdateRunnableRun && !mWallpapers.isEmpty()) {
560 // Must be resuming from a previously stopped state, so re-schedule the update of the
561 // daily wallpapers tile thumbnail.
562 mUpdateDailyWallpaperThumbRunnable.run();
563 }
564 }
565 }
566
567 }
568
569 @Override
570 public void onStop() {
571 super.onStop();
572 mHandler.removeCallbacks(mUpdateDailyWallpaperThumbRunnable);
573 }
574
575 @Override
576 public void onDestroy() {
577 super.onDestroy();
578 if (mProgressDialog != null) {
579 mProgressDialog.dismiss();
580 }
581 mWallpaperChangedNotifier.unregisterListener(mWallpaperChangedListener);
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700582 if (mAppStatusListener != null) {
583 mPackageStatusNotifier.removeListener(mAppStatusListener);
584 }
Chuck Liao69630f12020-03-05 19:01:25 +0800585 mWallpaperSetter.cleanUp();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800586 }
587
588 @Override
589 public void retryStartRotation(@NetworkPreference int networkPreference) {
590 startRotation(networkPreference);
591 }
592
593 public void setCurrentWallpaperBottomSheetPresenter(
594 CurrentWallpaperBottomSheetPresenter presenter) {
595 mCurrentWallpaperBottomSheetPresenter = presenter;
596 }
597
598 public void setWallpapersUiContainer(WallpapersUiContainer uiContainer) {
599 mWallpapersUiContainer = uiContainer;
600 }
601
602 /**
603 * Enable a test mode of operation -- in which certain UI features are disabled to allow for
604 * UI tests to run correctly. Works around issue in ProgressDialog currently where the dialog
605 * constantly keeps the UI thread alive and blocks a test forever.
606 *
607 * @param testingMode
608 */
609 void setTestingMode(boolean testingMode) {
610 mTestingMode = testingMode;
611 }
612
613 /**
614 * Asynchronously fetches the refreshed rotation initialization state that is up to date with the
615 * state of the user's device and binds the state of the current category's rotation to the "start
616 * rotation" tile.
617 */
Santiago Etchebehere8cad0dd2019-10-17 10:52:39 -0700618 private void refreshRotationHolder(RotationHolder rotationHolder) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800619 mWallpaperRotationInitializer.fetchRotationInitializationState(getContext(),
Santiago Etchebehere8cad0dd2019-10-17 10:52:39 -0700620 rotationState -> {
621 // Update the UI state of the "start rotation" tile displayed on screen.
622 // Do this in a Handler so it is scheduled at the end of the message queue.
623 // This is necessary to ensure we do not remove or add data from the adapter
624 // while the layout is still being computed. RecyclerView documentation
625 // therefore recommends performing such changes in a Handler.
626 new Handler().post(() -> {
627 // A config change may have destroyed the activity since the refresh
628 // started, so check for that to avoid an NPE.
629 if (getActivity() == null) {
630 return;
631 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800632
Santiago Etchebehere8cad0dd2019-10-17 10:52:39 -0700633 rotationHolder.bindRotationInitializationState(rotationState);
634 });
Jon Miranda16ea1b12017-12-12 14:52:48 -0800635 });
636 }
637
638 @Override
639 public void startRotation(@NetworkPreference final int networkPreference) {
640 if (!isRotationEnabled()) {
641 Log.e(TAG, "Rotation is not enabled for this category " + mCategory.getTitle());
642 return;
643 }
644
645 // ProgressDialog endlessly updates the UI thread, keeping it from going idle which therefore
646 // causes Espresso to hang once the dialog is shown.
647 if (mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE && !mTestingMode) {
648 int themeResId;
649 if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
650 themeResId = R.style.ProgressDialogThemePreL;
651 } else {
652 themeResId = R.style.LightDialogTheme;
653 }
654 mProgressDialog = new ProgressDialog(getActivity(), themeResId);
655
656 mProgressDialog.setTitle(PROGRESS_DIALOG_NO_TITLE);
657 mProgressDialog.setMessage(
658 getResources().getString(R.string.start_rotation_progress_message));
659 mProgressDialog.setIndeterminate(PROGRESS_DIALOG_INDETERMINATE);
660 mProgressDialog.show();
661 }
662
663 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
664 mAdapter.mPendingSelectedAdapterPosition = SPECIAL_FIXED_TILE_ADAPTER_POSITION;
665 }
666
667 final Context appContext = getActivity().getApplicationContext();
668
669 mWallpaperRotationInitializer.setFirstWallpaperInRotation(
670 appContext,
671 networkPreference,
672 new Listener() {
673 @Override
674 public void onFirstWallpaperInRotationSet() {
675 if (mProgressDialog != null) {
676 mProgressDialog.dismiss();
677 }
678
679 // The fragment may be detached from its containing activity if the user exits the
680 // app before the first wallpaper image in rotation finishes downloading.
681 Activity activity = getActivity();
682
Jon Miranda16ea1b12017-12-12 14:52:48 -0800683
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700684 if (mWallpaperRotationInitializer.startRotation(appContext)) {
685 if (activity != null
686 && mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
687 try {
688 Toast.makeText(getActivity(),
689 R.string.wallpaper_set_successfully_message,
690 Toast.LENGTH_SHORT).show();
691 } catch (NotFoundException e) {
692 Log.e(TAG, "Could not show toast " + e);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800693 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800694
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700695 activity.setResult(Activity.RESULT_OK);
696 activity.finish();
697 } else if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
698 mAdapter.updateSelectedTile(SPECIAL_FIXED_TILE_ADAPTER_POSITION);
699 }
700 } else { // Failed to start rotation.
701 showStartRotationErrorDialog(networkPreference);
702
703 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
704 DesktopRotationHolder rotationViewHolder =
705 (DesktopRotationHolder)
706 mImageGrid.findViewHolderForAdapterPosition(
707 SPECIAL_FIXED_TILE_ADAPTER_POSITION);
708 rotationViewHolder.setSelectionState(
709 SelectableHolder.SELECTION_STATE_DESELECTED);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800710 }
711 }
712 }
713
714 @Override
715 public void onError() {
716 if (mProgressDialog != null) {
717 mProgressDialog.dismiss();
718 }
719
720 showStartRotationErrorDialog(networkPreference);
721
722 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
723 DesktopRotationHolder rotationViewHolder =
724 (DesktopRotationHolder) mImageGrid.findViewHolderForAdapterPosition(
725 SPECIAL_FIXED_TILE_ADAPTER_POSITION);
726 rotationViewHolder.setSelectionState(SelectableHolder.SELECTION_STATE_DESELECTED);
727 }
728 }
729 });
730 }
731
732 private void showStartRotationErrorDialog(@NetworkPreference int networkPreference) {
733 BaseActivity activity = (BaseActivity) getActivity();
734 if (activity != null) {
735 StartRotationErrorDialogFragment startRotationErrorDialogFragment =
736 StartRotationErrorDialogFragment.newInstance(networkPreference);
737 startRotationErrorDialogFragment.setTargetFragment(
738 IndividualPickerFragment.this, UNUSED_REQUEST_CODE);
739
740 if (activity.isSafeToCommitFragmentTransaction()) {
741 startRotationErrorDialogFragment.show(
742 getFragmentManager(), TAG_START_ROTATION_ERROR_DIALOG);
743 } else {
744 mStagedStartRotationErrorDialogFragment = startRotationErrorDialogFragment;
745 }
746 }
747 }
748
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800749 int getNumColumns() {
Ching-Sung Li9df77a32019-07-10 11:45:30 +0800750 Activity activity = getActivity();
751 return activity == null ? 0 : TileSizeCalculator.getNumIndividualColumns(activity);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800752 }
753
754 /**
755 * Returns whether rotation is enabled for this category.
756 */
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800757 boolean isRotationEnabled() {
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700758 return mWallpaperRotationInitializer != null;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800759 }
760
761 @Override
762 public void onCurrentWallpaperRefreshed() {
763 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(true);
764 }
765
Chuck Liao69630f12020-03-05 19:01:25 +0800766
767 @Override
768 public void onSet(int destination) {
769 if (mSelectedWallpaperInfo == null) {
770 Log.e(TAG, "Unable to set wallpaper since the selected wallpaper info is null");
771 return;
772 }
773
774 if (mSelectedWallpaperInfo instanceof LiveWallpaperInfo) {
775 mWallpaperSetter.setCurrentWallpaper(getActivity(), mSelectedWallpaperInfo, null,
776 destination, 0, null, mSetWallpaperCallback);
777 } else {
778 mWallpaperSetter.setCurrentWallpaper(
779 getActivity(), mSelectedWallpaperInfo, destination, mSetWallpaperCallback);
780 }
781 }
782
783 private WallpaperPersister.SetWallpaperCallback mSetWallpaperCallback =
784 new WallpaperPersister.SetWallpaperCallback() {
785 @Override
786 public void onSuccess() {
787 // TODO(b/150913705): Show the snack bar.
788 mBottomActionBar.enableActions();
789 }
790
791 @Override
792 public void onError(@Nullable Throwable throwable) {
793 Log.e(TAG, "Can't apply the wallpaper.");
794 mBottomActionBar.enableActions();
795 }
796 };
797
798 @Override
799 public void onDialogDismissed(boolean withItemSelected) {
800 if (!withItemSelected) {
801 mBottomActionBar.enableActions();
802 }
803 }
804
Jon Miranda16ea1b12017-12-12 14:52:48 -0800805 /**
806 * Shows a "set wallpaper" error dialog with a failure message and button to try again.
807 */
808 private void showSetWallpaperErrorDialog() {
809 SetWallpaperErrorDialogFragment dialogFragment = SetWallpaperErrorDialogFragment.newInstance(
810 R.string.set_wallpaper_error_message, WallpaperPersister.DEST_BOTH);
811 dialogFragment.setTargetFragment(this, UNUSED_REQUEST_CODE);
812
813 if (((BaseActivity) getActivity()).isSafeToCommitFragmentTransaction()) {
814 dialogFragment.show(getFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
815 } else {
816 mStagedSetWallpaperErrorDialogFragment = dialogFragment;
817 }
818 }
819
Chuck Liao69630f12020-03-05 19:01:25 +0800820 private void updateBottomActions(boolean hasWallpaperSelected) {
821 mBottomActionBar.showActions(
822 hasWallpaperSelected ? EnumSet.of(APPLY) : EnumSet.of(ROTATION));
823 mBottomActionBar.hideActions(
824 hasWallpaperSelected ? EnumSet.of(ROTATION) : EnumSet.of(APPLY));
825 }
826
827 private void updateThumbnail(WallpaperInfo selectedWallpaperInfo) {
Chuck Liaof40063f2020-03-03 18:35:24 +0800828 ThumbnailUpdater thumbnailUpdater = (ThumbnailUpdater) getParentFragment();
Chuck Liao69630f12020-03-05 19:01:25 +0800829 if (thumbnailUpdater == null) {
830 return;
831 }
832
833 if (selectedWallpaperInfo != null) {
834 thumbnailUpdater.updateThumbnail(selectedWallpaperInfo);
835 } else {
836 thumbnailUpdater.restoreThumbnails();
Chuck Liaof40063f2020-03-03 18:35:24 +0800837 }
838 }
839
Chuck Liao69630f12020-03-05 19:01:25 +0800840 private void onWallpaperSelected(WallpaperInfo selectedWallpaperInfo) {
841 updateBottomActions(selectedWallpaperInfo != null);
842 updateThumbnail(selectedWallpaperInfo);
843 mSelectedWallpaperInfo = selectedWallpaperInfo;
Chuck Liaof40063f2020-03-03 18:35:24 +0800844 }
845
Jon Miranda16ea1b12017-12-12 14:52:48 -0800846 /**
847 * ViewHolder subclass for "daily refresh" tile in the RecyclerView, only shown if rotation is
848 * enabled for this category.
849 */
850 private class RotationHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
851
852 private FrameLayout mTileLayout;
853 private TextView mRotationMessage;
854 private TextView mRotationTitle;
855 private ImageView mRefreshIcon;
856
857 RotationHolder(View itemView) {
858 super(itemView);
859 itemView.setOnClickListener(this);
860
Santiago Etchebehere8cad0dd2019-10-17 10:52:39 -0700861 mTileLayout = itemView.findViewById(R.id.daily_refresh);
862 mRotationMessage = itemView.findViewById(R.id.rotation_tile_message);
863 mRotationTitle = itemView.findViewById(R.id.rotation_tile_title);
864 mRefreshIcon = itemView.findViewById(R.id.rotation_tile_refresh_icon);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800865 mTileLayout.getLayoutParams().height = mTileSizePx.y;
866
867 // If the feature flag for "dynamic start rotation tile" is not enabled, fall back to the
868 // static UI with a blue accent color background and "Tap to turn on" text.
869 if (!Flags.dynamicStartRotationTileEnabled) {
870 mTileLayout.setBackgroundColor(
871 getResources().getColor(R.color.rotation_tile_enabled_background_color));
872 mRotationMessage.setText(R.string.daily_refresh_tile_subtitle);
873 mRotationTitle.setTextColor(
874 getResources().getColor(R.color.rotation_tile_enabled_title_text_color));
875 mRotationMessage.setTextColor(
876 getResources().getColor(R.color.rotation_tile_enabled_subtitle_text_color));
877 mRefreshIcon.setColorFilter(
Santiago Etchebehere8cad0dd2019-10-17 10:52:39 -0700878 getResources().getColor(R.color.rotation_tile_enabled_refresh_icon_color),
879 Mode.SRC_IN);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800880 return;
881 }
882
883 // Initialize the state of the "start rotation" tile (i.e., whether it is gray or blue to
884 // indicate if rotation is turned on for the current category) with last-known rotation state
885 // that could be stale. The last-known rotation state is correct in most cases and is a good
886 // starting point but may not be accurate if the user set a wallpaper through a 3rd party app
887 // while this app was paused.
888 int rotationState = mWallpaperRotationInitializer.getRotationInitializationStateDirty(
889 getContext());
890 bindRotationInitializationState(rotationState);
891 }
892
893 @Override
894 public void onClick(View v) {
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700895 DialogFragment startRotationDialogFragment = new StartRotationDialogFragment();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800896 startRotationDialogFragment.setTargetFragment(
897 IndividualPickerFragment.this, UNUSED_REQUEST_CODE);
898 startRotationDialogFragment.show(getFragmentManager(), TAG_START_ROTATION_DIALOG);
899 }
900
901 /**
902 * Binds the provided rotation initialization state to the RotationHolder and updates the tile's
903 * UI to be in sync with the state (i.e., message and color appropriately reflect the state to
904 * the user).
905 */
906 void bindRotationInitializationState(@RotationInitializationState int rotationState) {
907 int newBackgroundColor =
908 (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
909 ? getResources().getColor(R.color.rotation_tile_not_enabled_background_color)
910 : getResources().getColor(R.color.rotation_tile_enabled_background_color);
911 int newTitleTextColor =
912 (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
913 ? getResources().getColor(R.color.rotation_tile_not_enabled_title_text_color)
914 : getResources().getColor(R.color.rotation_tile_enabled_title_text_color);
915 int newSubtitleTextColor =
916 (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
917 ? getResources().getColor(R.color.rotation_tile_not_enabled_subtitle_text_color)
918 : getResources().getColor(R.color.rotation_tile_enabled_subtitle_text_color);
919 int newRefreshIconColor =
920 (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
921 ? getResources().getColor(R.color.rotation_tile_not_enabled_refresh_icon_color)
922 : getResources().getColor(R.color.rotation_tile_enabled_refresh_icon_color);
923
924 mTileLayout.setBackgroundColor(newBackgroundColor);
925 mRotationTitle.setTextColor(newTitleTextColor);
926 mRotationMessage.setText(getResIdForRotationState(rotationState));
927 mRotationMessage.setTextColor(newSubtitleTextColor);
928 mRefreshIcon.setColorFilter(newRefreshIconColor, Mode.SRC_IN);
929 }
930 }
931
932 /**
933 * RecyclerView Adapter subclass for the wallpaper tiles in the RecyclerView.
934 */
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800935 class IndividualAdapter extends RecyclerView.Adapter<ViewHolder> {
936 static final int ITEM_VIEW_TYPE_ROTATION = 1;
937 static final int ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER = 2;
938 static final int ITEM_VIEW_TYPE_MY_PHOTOS = 3;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800939
940 private final List<WallpaperInfo> mWallpapers;
941
942 private int mPendingSelectedAdapterPosition;
943 private int mSelectedAdapterPosition;
944
945 IndividualAdapter(List<WallpaperInfo> wallpapers) {
946 mWallpapers = wallpapers;
947 mPendingSelectedAdapterPosition = -1;
948 mSelectedAdapterPosition = -1;
949 }
950
951 @Override
952 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
953 switch (viewType) {
954 case ITEM_VIEW_TYPE_ROTATION:
955 return createRotationHolder(parent);
956 case ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER:
957 return createIndividualHolder(parent);
958 case ITEM_VIEW_TYPE_MY_PHOTOS:
959 return createMyPhotosHolder(parent);
960 default:
961 Log.e(TAG, "Unsupported viewType " + viewType + " in IndividualAdapter");
962 return null;
963 }
964 }
965
966 @Override
967 public int getItemViewType(int position) {
968 if (isRotationEnabled() && position == SPECIAL_FIXED_TILE_ADAPTER_POSITION) {
969 return ITEM_VIEW_TYPE_ROTATION;
970 }
971
972 // A category cannot have both a "start rotation" tile and a "my photos" tile.
973 if (mCategory.supportsCustomPhotos()
974 && !isRotationEnabled()
975 && position == SPECIAL_FIXED_TILE_ADAPTER_POSITION) {
976 return ITEM_VIEW_TYPE_MY_PHOTOS;
977 }
978
979 return ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER;
980 }
981
982 @Override
983 public void onBindViewHolder(ViewHolder holder, int position) {
984 int viewType = getItemViewType(position);
985
986 switch (viewType) {
987 case ITEM_VIEW_TYPE_ROTATION:
988 onBindRotationHolder(holder, position);
989 break;
990 case ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER:
991 onBindIndividualHolder(holder, position);
992 break;
993 case ITEM_VIEW_TYPE_MY_PHOTOS:
994 ((MyPhotosViewHolder) holder).bind();
995 break;
996 default:
997 Log.e(TAG, "Unsupported viewType " + viewType + " in IndividualAdapter");
998 }
999 }
1000
1001 @Override
1002 public int getItemCount() {
1003 return (isRotationEnabled() || mCategory.supportsCustomPhotos())
1004 ? mWallpapers.size() + 1
1005 : mWallpapers.size();
1006 }
1007
1008 private ViewHolder createRotationHolder(ViewGroup parent) {
1009 LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
1010 View view;
1011
1012 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
1013 view = layoutInflater.inflate(R.layout.grid_item_rotation_desktop, parent, false);
1014 SelectionAnimator selectionAnimator =
1015 new CheckmarkSelectionAnimator(getActivity(), view);
1016 return new DesktopRotationHolder(
1017 getActivity(), mTileSizePx.y, view, selectionAnimator,
1018 IndividualPickerFragment.this);
1019 } else { // MOBILE
1020 view = layoutInflater.inflate(R.layout.grid_item_rotation, parent, false);
1021 return new RotationHolder(view);
1022 }
1023 }
1024
1025 private ViewHolder createIndividualHolder(ViewGroup parent) {
1026 LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
1027 View view = layoutInflater.inflate(R.layout.grid_item_image, parent, false);
1028
1029 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
1030 SelectionAnimator selectionAnimator =
1031 new CheckmarkSelectionAnimator(getActivity(), view);
1032 return new SetIndividualHolder(
1033 getActivity(), mTileSizePx.y, view,
1034 selectionAnimator,
1035 new OnSetListener() {
1036 @Override
1037 public void onPendingWallpaperSet(int adapterPosition) {
1038 // Deselect and hide loading indicator for any previously pending tile.
1039 if (mPendingSelectedAdapterPosition != -1) {
1040 ViewHolder oldViewHolder = mImageGrid.findViewHolderForAdapterPosition(
1041 mPendingSelectedAdapterPosition);
1042 if (oldViewHolder instanceof SelectableHolder) {
1043 ((SelectableHolder) oldViewHolder).setSelectionState(
1044 SelectableHolder.SELECTION_STATE_DESELECTED);
1045 }
1046 }
1047
1048 if (mSelectedAdapterPosition != -1) {
1049 ViewHolder oldViewHolder = mImageGrid.findViewHolderForAdapterPosition(
1050 mSelectedAdapterPosition);
1051 if (oldViewHolder instanceof SelectableHolder) {
1052 ((SelectableHolder) oldViewHolder).setSelectionState(
1053 SelectableHolder.SELECTION_STATE_DESELECTED);
1054 }
1055 }
1056
1057 mPendingSelectedAdapterPosition = adapterPosition;
1058 }
1059
1060 @Override
1061 public void onWallpaperSet(int adapterPosition) {
1062 // No-op -- UI handles a new wallpaper being set by reacting to the
1063 // WallpaperChangedNotifier.
1064 }
1065
1066 @Override
1067 public void onWallpaperSetFailed(SetIndividualHolder holder) {
1068 showSetWallpaperErrorDialog();
1069 mPendingSetIndividualHolder = holder;
1070 }
1071 });
1072 } else { // MOBILE
Chuck Liao3d1a51c2020-02-17 18:29:34 +08001073 return new PreviewIndividualHolder(getActivity(), mTileSizePx.y, view);
Jon Miranda16ea1b12017-12-12 14:52:48 -08001074 }
1075 }
1076
1077 private ViewHolder createMyPhotosHolder(ViewGroup parent) {
1078 LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
1079 View view = layoutInflater.inflate(R.layout.grid_item_my_photos, parent, false);
1080
Santiago Etchebeherefab49612019-01-15 12:22:42 -08001081 return new MyPhotosViewHolder(getActivity(),
1082 ((MyPhotosStarterProvider) getActivity()).getMyPhotosStarter(),
1083 mTileSizePx.y, view);
Jon Miranda16ea1b12017-12-12 14:52:48 -08001084 }
1085
1086 /**
1087 * Marks the tile at the given position as selected with a visual indication. Also updates the
1088 * "currently selected" BottomSheet to reflect the newly selected tile.
1089 */
1090 private void updateSelectedTile(int newlySelectedPosition) {
1091 // Prevent multiple spinners from appearing with a user tapping several tiles in rapid
1092 // succession.
1093 if (mPendingSelectedAdapterPosition == mSelectedAdapterPosition) {
1094 return;
1095 }
1096
1097 if (mCurrentWallpaperBottomSheetPresenter != null) {
1098 mCurrentWallpaperBottomSheetPresenter.refreshCurrentWallpapers(
1099 IndividualPickerFragment.this);
1100
1101 if (mCurrentWallpaperBottomSheetExpandedRunnable != null) {
1102 mHandler.removeCallbacks(mCurrentWallpaperBottomSheetExpandedRunnable);
1103 }
1104 mCurrentWallpaperBottomSheetExpandedRunnable = new Runnable() {
1105 @Override
1106 public void run() {
1107 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(true);
1108 }
1109 };
1110 mHandler.postDelayed(mCurrentWallpaperBottomSheetExpandedRunnable, 100);
1111 }
1112
1113 // User may have switched to another category, thus detaching this fragment, so check here.
1114 // NOTE: We do this check after updating the current wallpaper BottomSheet so that the update
1115 // still occurs in the UI after the user selects that other category.
1116 if (getActivity() == null) {
1117 return;
1118 }
1119
1120 // Update the newly selected wallpaper ViewHolder and the old one so that if
1121 // selection UI state applies (desktop UI), it is updated.
1122 if (mSelectedAdapterPosition >= 0) {
1123 ViewHolder oldViewHolder = mImageGrid.findViewHolderForAdapterPosition(
1124 mSelectedAdapterPosition);
1125 if (oldViewHolder instanceof SelectableHolder) {
1126 ((SelectableHolder) oldViewHolder).setSelectionState(
1127 SelectableHolder.SELECTION_STATE_DESELECTED);
1128 }
1129 }
1130
1131 // Animate selection of newly selected tile.
1132 ViewHolder newViewHolder = mImageGrid
1133 .findViewHolderForAdapterPosition(newlySelectedPosition);
1134 if (newViewHolder instanceof SelectableHolder) {
1135 ((SelectableHolder) newViewHolder).setSelectionState(
1136 SelectableHolder.SELECTION_STATE_SELECTED);
1137 }
1138
1139 mSelectedAdapterPosition = newlySelectedPosition;
1140
1141 // If the tile was in the last row of the grid, add space below it so the user can scroll down
1142 // and up to see the BottomSheet without it fully overlapping the newly selected tile.
1143 int spanCount = ((GridLayoutManager) mImageGrid.getLayoutManager()).getSpanCount();
1144 int numRows = (int) Math.ceil((float) getItemCount() / spanCount);
1145 int rowOfNewlySelectedTile = newlySelectedPosition / spanCount;
1146 boolean isInLastRow = rowOfNewlySelectedTile == numRows - 1;
1147
1148 updateImageGridPadding(isInLastRow /* addExtraBottomSpace */);
1149 }
1150
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +08001151 void onBindRotationHolder(ViewHolder holder, int position) {
Jon Miranda16ea1b12017-12-12 14:52:48 -08001152 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
1153 String collectionId = mCategory.getCollectionId();
1154 ((DesktopRotationHolder) holder).bind(collectionId);
1155
1156 if (mWallpaperPreferences.getWallpaperPresentationMode()
1157 == WallpaperPreferences.PRESENTATION_MODE_ROTATING
1158 && collectionId.equals(mWallpaperPreferences.getHomeWallpaperCollectionId())) {
1159 mSelectedAdapterPosition = position;
1160 }
1161
1162 if (!mWasUpdateRunnableRun && !mWallpapers.isEmpty()) {
1163 updateDesktopDailyRotationThumbnail((DesktopRotationHolder) holder);
1164 mWasUpdateRunnableRun = true;
1165 }
1166 }
1167 }
1168
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +08001169 void onBindIndividualHolder(ViewHolder holder, int position) {
Jon Miranda16ea1b12017-12-12 14:52:48 -08001170 int wallpaperIndex = (isRotationEnabled() || mCategory.supportsCustomPhotos())
1171 ? position - 1 : position;
1172 WallpaperInfo wallpaper = mWallpapers.get(wallpaperIndex);
1173 ((IndividualHolder) holder).bindWallpaper(wallpaper);
1174 WallpaperPreferences prefs = InjectorProvider.getInjector().getPreferences(getContext());
1175
1176 String wallpaperId = wallpaper.getWallpaperId();
1177 if (wallpaperId != null && wallpaperId.equals(prefs.getHomeWallpaperRemoteId())) {
1178 mSelectedAdapterPosition = position;
1179 }
Chuck Liaof40063f2020-03-03 18:35:24 +08001180
1181 if (TEMP_BOTTOM_ACTION_BAR_FEATURE) {
Chuck Liao69630f12020-03-05 19:01:25 +08001182 holder.itemView.findViewById(R.id.tile).setOnClickListener(
1183 view -> onWallpaperSelected(wallpaper));
Chuck Liaof40063f2020-03-03 18:35:24 +08001184 }
Jon Miranda16ea1b12017-12-12 14:52:48 -08001185 }
1186 }
1187}