blob: 4392ca80c1714933dc88507c2629918a682a0f37 [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;
chihhangchuang22aa0cc2020-03-25 19:12:42 +080020import static com.android.wallpaper.widget.BottomActionBar.BottomAction.INFORMATION;
Chuck Liao8ec38e02020-02-26 20:59:32 +080021import static com.android.wallpaper.widget.BottomActionBar.BottomAction.ROTATION;
22
Jon Miranda16ea1b12017-12-12 14:52:48 -080023import android.app.Activity;
24import android.app.ProgressDialog;
25import android.content.Context;
Santiago Etchebehereb1854472019-06-06 17:44:54 -070026import android.content.res.Configuration;
Jon Miranda16ea1b12017-12-12 14:52:48 -080027import android.content.res.Resources.NotFoundException;
28import android.graphics.Point;
29import android.graphics.PorterDuff.Mode;
Chuck Liaoba401232020-03-13 20:11:04 +080030import android.graphics.Rect;
Jon Miranda16ea1b12017-12-12 14:52:48 -080031import android.os.Build.VERSION;
32import android.os.Build.VERSION_CODES;
33import android.os.Bundle;
34import android.os.Handler;
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -070035import android.service.wallpaper.WallpaperService;
Jon Miranda16ea1b12017-12-12 14:52:48 -080036import android.util.Log;
37import android.view.LayoutInflater;
38import android.view.View;
39import android.view.ViewGroup;
Jon Miranda16ea1b12017-12-12 14:52:48 -080040import android.widget.ImageView;
41import android.widget.TextView;
42import android.widget.Toast;
43
Santiago Etchebehereb1854472019-06-06 17:44:54 -070044import androidx.annotation.NonNull;
Chuck Liao69630f12020-03-05 19:01:25 +080045import androidx.annotation.Nullable;
Chuck Liaoba401232020-03-13 20:11:04 +080046import androidx.cardview.widget.CardView;
Santiago Etchebeherefab49612019-01-15 12:22:42 -080047import androidx.fragment.app.DialogFragment;
48import androidx.fragment.app.Fragment;
49import androidx.recyclerview.widget.GridLayoutManager;
50import androidx.recyclerview.widget.RecyclerView;
51import androidx.recyclerview.widget.RecyclerView.OnScrollListener;
52import androidx.recyclerview.widget.RecyclerView.ViewHolder;
53
Jon Miranda16ea1b12017-12-12 14:52:48 -080054import com.android.wallpaper.R;
55import com.android.wallpaper.asset.Asset;
56import com.android.wallpaper.asset.Asset.DrawableLoadedListener;
57import com.android.wallpaper.config.Flags;
Santiago Etchebehere8648bb82019-08-06 17:09:02 -070058import com.android.wallpaper.model.Category;
59import com.android.wallpaper.model.CategoryProvider;
60import com.android.wallpaper.model.CategoryReceiver;
Chuck Liao69630f12020-03-05 19:01:25 +080061import com.android.wallpaper.model.LiveWallpaperInfo;
Jon Miranda16ea1b12017-12-12 14:52:48 -080062import com.android.wallpaper.model.WallpaperCategory;
63import com.android.wallpaper.model.WallpaperInfo;
64import com.android.wallpaper.model.WallpaperReceiver;
65import com.android.wallpaper.model.WallpaperRotationInitializer;
66import com.android.wallpaper.model.WallpaperRotationInitializer.Listener;
67import com.android.wallpaper.model.WallpaperRotationInitializer.NetworkPreference;
68import com.android.wallpaper.model.WallpaperRotationInitializer.RotationInitializationState;
Jon Miranda16ea1b12017-12-12 14:52:48 -080069import com.android.wallpaper.module.FormFactorChecker;
70import com.android.wallpaper.module.FormFactorChecker.FormFactor;
71import com.android.wallpaper.module.Injector;
72import com.android.wallpaper.module.InjectorProvider;
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -070073import com.android.wallpaper.module.PackageStatusNotifier;
Jon Miranda16ea1b12017-12-12 14:52:48 -080074import com.android.wallpaper.module.WallpaperChangedNotifier;
75import com.android.wallpaper.module.WallpaperPersister;
76import com.android.wallpaper.module.WallpaperPersister.Destination;
77import com.android.wallpaper.module.WallpaperPreferences;
Chuck Liao69630f12020-03-05 19:01:25 +080078import com.android.wallpaper.module.WallpaperSetter;
Jon Miranda16ea1b12017-12-12 14:52:48 -080079import com.android.wallpaper.picker.BaseActivity;
80import com.android.wallpaper.picker.CurrentWallpaperBottomSheetPresenter;
Santiago Etchebeherefab49612019-01-15 12:22:42 -080081import com.android.wallpaper.picker.MyPhotosStarter.MyPhotosStarterProvider;
Jon Miranda16ea1b12017-12-12 14:52:48 -080082import com.android.wallpaper.picker.RotationStarter;
Chuck Liao69630f12020-03-05 19:01:25 +080083import com.android.wallpaper.picker.SetWallpaperDialogFragment;
Jon Miranda16ea1b12017-12-12 14:52:48 -080084import com.android.wallpaper.picker.SetWallpaperErrorDialogFragment;
85import com.android.wallpaper.picker.StartRotationDialogFragment;
86import com.android.wallpaper.picker.StartRotationErrorDialogFragment;
87import com.android.wallpaper.picker.WallpapersUiContainer;
88import com.android.wallpaper.picker.individual.SetIndividualHolder.OnSetListener;
89import com.android.wallpaper.util.DiskBasedLogger;
90import com.android.wallpaper.util.TileSizeCalculator;
Chuck Liao8ec38e02020-02-26 20:59:32 +080091import com.android.wallpaper.widget.BottomActionBar;
Sunny Goyal8600a3f2018-08-15 12:48:01 -070092
Jon Miranda16ea1b12017-12-12 14:52:48 -080093import com.bumptech.glide.Glide;
94import com.bumptech.glide.MemoryCategory;
95
96import java.util.ArrayList;
97import java.util.Date;
Chuck Liao8ec38e02020-02-26 20:59:32 +080098import java.util.EnumSet;
Jon Miranda16ea1b12017-12-12 14:52:48 -080099import java.util.List;
100import java.util.Random;
101
102/**
103 * Displays the Main UI for picking an individual wallpaper image.
104 */
105public class IndividualPickerFragment extends Fragment
106 implements RotationStarter, StartRotationErrorDialogFragment.Listener,
107 CurrentWallpaperBottomSheetPresenter.RefreshListener,
Chuck Liao69630f12020-03-05 19:01:25 +0800108 SetWallpaperErrorDialogFragment.Listener, SetWallpaperDialogFragment.Listener {
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800109 /**
110 * Position of a special tile that doesn't belong to an individual wallpaper of the category,
111 * such as "my photos" or "daily rotation".
112 */
113 static final int SPECIAL_FIXED_TILE_ADAPTER_POSITION = 0;
114 static final String ARG_CATEGORY_COLLECTION_ID = "category_collection_id";
115
Jon Miranda16ea1b12017-12-12 14:52:48 -0800116 private static final String TAG = "IndividualPickerFrgmnt";
Jon Miranda16ea1b12017-12-12 14:52:48 -0800117 private static final int UNUSED_REQUEST_CODE = 1;
118 private static final String TAG_START_ROTATION_DIALOG = "start_rotation_dialog";
119 private static final String TAG_START_ROTATION_ERROR_DIALOG = "start_rotation_error_dialog";
120 private static final String PROGRESS_DIALOG_NO_TITLE = null;
121 private static final boolean PROGRESS_DIALOG_INDETERMINATE = true;
122 private static final String TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT =
123 "individual_set_wallpaper_error_dialog";
Santiago Etchebehereb1854472019-06-06 17:44:54 -0700124 private static final String KEY_NIGHT_MODE = "IndividualPickerFragment.NIGHT_MODE";
Jon Miranda16ea1b12017-12-12 14:52:48 -0800125
Chuck Liao8ec38e02020-02-26 20:59:32 +0800126 /**
127 * A temporary flag to hide the bottom action bar feature.
128 */
129 private static final boolean TEMP_BOTTOM_ACTION_BAR_FEATURE = false;
130
Chuck Liaof40063f2020-03-03 18:35:24 +0800131 /**
132 * An interface for updating the thumbnail with the specific wallpaper.
133 */
134 public interface ThumbnailUpdater {
135 /**
136 * Updates the thumbnail with the specific wallpaper.
137 */
138 void updateThumbnail(WallpaperInfo wallpaperInfo);
139
140 /**
141 * Restores to the thumbnails of the wallpapers which were applied.
142 */
143 void restoreThumbnails();
144 }
145
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800146 WallpaperPreferences mWallpaperPreferences;
147 WallpaperChangedNotifier mWallpaperChangedNotifier;
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800148 RecyclerView mImageGrid;
149 IndividualAdapter mAdapter;
150 WallpaperCategory mCategory;
151 WallpaperRotationInitializer mWallpaperRotationInitializer;
152 List<WallpaperInfo> mWallpapers;
153 Point mTileSizePx;
154 WallpapersUiContainer mWallpapersUiContainer;
155 @FormFactor
156 int mFormFactor;
157 PackageStatusNotifier mPackageStatusNotifier;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800158
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800159 Handler mHandler;
160 Random mRandom;
161
162 WallpaperChangedNotifier.Listener mWallpaperChangedListener =
163 new WallpaperChangedNotifier.Listener() {
164 @Override
165 public void onWallpaperChanged() {
166 if (mFormFactor != FormFactorChecker.FORM_FACTOR_DESKTOP) {
167 return;
168 }
169
170 ViewHolder selectedViewHolder = mImageGrid.findViewHolderForAdapterPosition(
171 mAdapter.mSelectedAdapterPosition);
172
173 // Null remote ID => My Photos wallpaper, so deselect whatever was previously selected.
174 if (mWallpaperPreferences.getHomeWallpaperRemoteId() == null) {
175 if (selectedViewHolder instanceof SelectableHolder) {
176 ((SelectableHolder) selectedViewHolder).setSelectionState(
177 SelectableHolder.SELECTION_STATE_DESELECTED);
178 }
179 } else {
180 mAdapter.updateSelectedTile(mAdapter.mPendingSelectedAdapterPosition);
181 }
182 }
183 };
184 PackageStatusNotifier.Listener mAppStatusListener;
185
Jon Miranda16ea1b12017-12-12 14:52:48 -0800186 private ProgressDialog mProgressDialog;
187 private boolean mTestingMode;
188 private CurrentWallpaperBottomSheetPresenter mCurrentWallpaperBottomSheetPresenter;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800189 private SetIndividualHolder mPendingSetIndividualHolder;
190
191 /**
192 * Staged error dialog fragments that were unable to be shown when the activity didn't allow
193 * committing fragment transactions.
194 */
195 private SetWallpaperErrorDialogFragment mStagedSetWallpaperErrorDialogFragment;
196 private StartRotationErrorDialogFragment mStagedStartRotationErrorDialogFragment;
197
Jon Miranda16ea1b12017-12-12 14:52:48 -0800198 private Runnable mCurrentWallpaperBottomSheetExpandedRunnable;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800199
200 /**
201 * Whether {@code mUpdateDailyWallpaperThumbRunnable} has been run at least once in this
202 * invocation of the fragment.
203 */
204 private boolean mWasUpdateRunnableRun;
205
206 /**
207 * A Runnable which regularly updates the thumbnail for the "Daily wallpapers" tile in desktop
208 * mode.
209 */
210 private Runnable mUpdateDailyWallpaperThumbRunnable = new Runnable() {
211 @Override
212 public void run() {
213 ViewHolder viewHolder = mImageGrid.findViewHolderForAdapterPosition(
214 SPECIAL_FIXED_TILE_ADAPTER_POSITION);
215 if (viewHolder instanceof DesktopRotationHolder) {
216 updateDesktopDailyRotationThumbnail((DesktopRotationHolder) viewHolder);
217 } else { // viewHolder is null
218 // If the rotation tile is unavailable (because user has scrolled down, causing the
219 // ViewHolder to be recycled), schedule the update for some time later. Once user scrolls up
220 // again, the ViewHolder will be re-bound and its thumbnail will be updated.
221 mHandler.postDelayed(mUpdateDailyWallpaperThumbRunnable,
222 DesktopRotationHolder.CROSSFADE_DURATION_MILLIS
223 + DesktopRotationHolder.CROSSFADE_DURATION_PAUSE_MILLIS);
224 }
225 }
226 };
227
Chuck Liao8ec38e02020-02-26 20:59:32 +0800228 private BottomActionBar mBottomActionBar;
Chuck Liao69630f12020-03-05 19:01:25 +0800229 private WallpaperSetter mWallpaperSetter;
“Chuckffd832c2020-03-22 02:15:58 +0800230 private WallpaperPersister mWallpaperPersister;
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800231 @Nullable private WallpaperInfo mSelectedWallpaperInfo;
“Chuckffd832c2020-03-22 02:15:58 +0800232 private WallpaperInfo mAppliedWallpaperInfo;
Chuck Liao8ec38e02020-02-26 20:59:32 +0800233
Jon Miranda16ea1b12017-12-12 14:52:48 -0800234 public static IndividualPickerFragment newInstance(String collectionId) {
235 Bundle args = new Bundle();
236 args.putString(ARG_CATEGORY_COLLECTION_ID, collectionId);
237
238 IndividualPickerFragment fragment = new IndividualPickerFragment();
239 fragment.setArguments(args);
240 return fragment;
241 }
242
243 private static int getResIdForRotationState(@RotationInitializationState int rotationState) {
244 switch (rotationState) {
245 case WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED:
246 return R.string.daily_refresh_tile_subtitle;
247 case WallpaperRotationInitializer.ROTATION_HOME_ONLY:
248 return R.string.home_screen_message;
249 case WallpaperRotationInitializer.ROTATION_HOME_AND_LOCK:
250 return R.string.home_and_lock_short_label;
251 default:
252 Log.e(TAG, "Unknown rotation intialization state: " + rotationState);
253 return R.string.home_screen_message;
254 }
255 }
256
257 private void updateDesktopDailyRotationThumbnail(DesktopRotationHolder holder) {
258 int wallpapersIndex = mRandom.nextInt(mWallpapers.size());
259 Asset newThumbnailAsset = mWallpapers.get(wallpapersIndex).getThumbAsset(
260 getActivity());
261 holder.updateThumbnail(newThumbnailAsset, new DrawableLoadedListener() {
262 @Override
263 public void onDrawableLoaded() {
264 if (getActivity() == null) {
265 return;
266 }
267
268 // Schedule the next update of the thumbnail.
269 int delayMillis = DesktopRotationHolder.CROSSFADE_DURATION_MILLIS
270 + DesktopRotationHolder.CROSSFADE_DURATION_PAUSE_MILLIS;
271 mHandler.postDelayed(mUpdateDailyWallpaperThumbRunnable, delayMillis);
272 }
273 });
274 }
275
276 @Override
277 public void onCreate(Bundle savedInstanceState) {
278 super.onCreate(savedInstanceState);
279
280 Injector injector = InjectorProvider.getInjector();
281 Context appContext = getContext().getApplicationContext();
282 mWallpaperPreferences = injector.getPreferences(appContext);
283
284 mWallpaperChangedNotifier = WallpaperChangedNotifier.getInstance();
285 mWallpaperChangedNotifier.registerListener(mWallpaperChangedListener);
286
Jon Miranda16ea1b12017-12-12 14:52:48 -0800287 mFormFactor = injector.getFormFactorChecker(appContext).getFormFactor();
288
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700289 mPackageStatusNotifier = injector.getPackageStatusNotifier(appContext);
290
“Chuckffd832c2020-03-22 02:15:58 +0800291 mWallpaperPersister = injector.getWallpaperPersister(appContext);
Chuck Liao69630f12020-03-05 19:01:25 +0800292 mWallpaperSetter = new WallpaperSetter(
“Chuckffd832c2020-03-22 02:15:58 +0800293 mWallpaperPersister,
Chuck Liao69630f12020-03-05 19:01:25 +0800294 injector.getPreferences(appContext),
295 injector.getUserEventLogger(appContext),
296 false);
297
Jon Miranda16ea1b12017-12-12 14:52:48 -0800298 mWallpapers = new ArrayList<>();
299 mRandom = new Random();
300 mHandler = new Handler();
301
Santiago Etchebehereb1854472019-06-06 17:44:54 -0700302 // Clear Glide's cache if night-mode changed to ensure thumbnails are reloaded
303 if (savedInstanceState != null && (savedInstanceState.getInt(KEY_NIGHT_MODE)
304 != (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK))) {
305 Glide.get(getContext()).clearMemory();
306 }
307
Santiago Etchebehere8648bb82019-08-06 17:09:02 -0700308 CategoryProvider categoryProvider = injector.getCategoryProvider(appContext);
309 categoryProvider.fetchCategories(new CategoryReceiver() {
310 @Override
311 public void onCategoryReceived(Category category) {
312 // Do nothing.
313 }
314
315 @Override
316 public void doneFetchingCategories() {
317 mCategory = (WallpaperCategory) categoryProvider.getCategory(
318 getArguments().getString(ARG_CATEGORY_COLLECTION_ID));
319 if (mCategory == null) {
320 DiskBasedLogger.e(TAG, "Failed to find the category.", getContext());
321
322 // The absence of this category in the CategoryProvider indicates a broken
323 // state, see b/38030129. Hence, finish the activity and return.
324 getActivity().finish();
325 return;
326 }
327 onCategoryLoaded();
328 }
329 }, false);
330 }
331
332 protected void onCategoryLoaded() {
333 mWallpaperRotationInitializer = mCategory.getWallpaperRotationInitializer();
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700334 fetchWallpapers(false);
335
336 if (mCategory.supportsThirdParty()) {
337 mAppStatusListener = (packageName, status) -> {
338 if (status != PackageStatusNotifier.PackageStatus.REMOVED ||
339 mCategory.containsThirdParty(packageName)) {
340 fetchWallpapers(true);
341 }
342 };
343 mPackageStatusNotifier.addListener(mAppStatusListener,
344 WallpaperService.SERVICE_INTERFACE);
345 }
Santiago Etchebehere8648bb82019-08-06 17:09:02 -0700346
Santiago Etchebehere460fbcb2019-08-20 11:47:30 -0700347 maybeSetUpImageGrid();
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700348 }
349
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800350 void fetchWallpapers(boolean forceReload) {
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700351 mWallpapers.clear();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800352 mCategory.fetchWallpapers(getActivity().getApplicationContext(), new WallpaperReceiver() {
353 @Override
354 public void onWallpapersReceived(List<WallpaperInfo> wallpapers) {
355 for (WallpaperInfo wallpaper : wallpapers) {
356 mWallpapers.add(wallpaper);
357 }
358
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700359 // Wallpapers may load after the adapter is initialized, in which case we have
360 // to explicitly notify that the data set has changed.
Jon Miranda16ea1b12017-12-12 14:52:48 -0800361 if (mAdapter != null) {
362 mAdapter.notifyDataSetChanged();
363 }
364
365 if (mWallpapersUiContainer != null) {
366 mWallpapersUiContainer.onWallpapersReady();
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700367 } else {
368 if (wallpapers.isEmpty()) {
369 // If there are no more wallpapers and we're on phone, just finish the
370 // Activity.
371 Activity activity = getActivity();
372 if (activity != null
373 && mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
374 activity.finish();
375 }
376 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800377 }
378 }
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700379 }, forceReload);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800380 }
381
382 @Override
Santiago Etchebehereb1854472019-06-06 17:44:54 -0700383 public void onSaveInstanceState(@NonNull Bundle outState) {
384 super.onSaveInstanceState(outState);
Santiago Etchebehere0ec065c2019-06-13 11:30:21 -0700385 outState.putInt(KEY_NIGHT_MODE,
Santiago Etchebehereb1854472019-06-06 17:44:54 -0700386 getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK);
387 }
388
389 @Override
Jon Miranda16ea1b12017-12-12 14:52:48 -0800390 public View onCreateView(LayoutInflater inflater, ViewGroup container,
391 Bundle savedInstanceState) {
392 View view = inflater.inflate(R.layout.fragment_individual_picker, container, false);
393
394 mTileSizePx = TileSizeCalculator.getIndividualTileSize(getActivity());
395
396 mImageGrid = (RecyclerView) view.findViewById(R.id.wallpaper_grid);
397 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
398 int gridPaddingPx = getResources().getDimensionPixelSize(R.dimen.grid_padding_desktop);
399 updateImageGridPadding(false /* addExtraBottomSpace */);
400 mImageGrid.setScrollBarSize(gridPaddingPx);
401 }
Chuck Liaoba401232020-03-13 20:11:04 +0800402 mImageGrid.addItemDecoration(new GridPaddingDecoration(
403 getResources().getDimensionPixelSize(R.dimen.grid_padding)));
Jon Miranda16ea1b12017-12-12 14:52:48 -0800404
Santiago Etchebehere460fbcb2019-08-20 11:47:30 -0700405 maybeSetUpImageGrid();
Santiago Etchebehere8648bb82019-08-06 17:09:02 -0700406
Jon Miranda16ea1b12017-12-12 14:52:48 -0800407 setUpBottomSheet();
408
Chuck Liao8ec38e02020-02-26 20:59:32 +0800409 if (TEMP_BOTTOM_ACTION_BAR_FEATURE) {
410 mBottomActionBar = getActivity().findViewById(R.id.bottom_actionbar);
411
Chuck Liao69630f12020-03-05 19:01:25 +0800412 mBottomActionBar.setActionClickListener(CANCEL, unused -> {
413 if (mSelectedWallpaperInfo != null) {
414 onWallpaperSelected(null);
415 return;
416 }
417 getActivity().onBackPressed();
418 });
Chuck Liao8ec38e02020-02-26 20:59:32 +0800419 mBottomActionBar.setActionClickListener(ROTATION, unused -> {
420 DialogFragment startRotationDialogFragment = new StartRotationDialogFragment();
421 startRotationDialogFragment.setTargetFragment(
422 IndividualPickerFragment.this, UNUSED_REQUEST_CODE);
423 startRotationDialogFragment.show(getFragmentManager(), TAG_START_ROTATION_DIALOG);
424 });
Chuck Liao69630f12020-03-05 19:01:25 +0800425 mBottomActionBar.setActionClickListener(APPLY, unused -> {
426 mBottomActionBar.disableActions();
427 mWallpaperSetter.requestDestination(getActivity(), getFragmentManager(), this,
428 mSelectedWallpaperInfo instanceof LiveWallpaperInfo);
429 });
Chuck Liao8ec38e02020-02-26 20:59:32 +0800430
Chuck Liao4f059312020-03-13 23:09:46 +0800431 mBottomActionBar.show();
Chuck Liao8ec38e02020-02-26 20:59:32 +0800432 mBottomActionBar.showActionsOnly(
433 isRotationEnabled() ? EnumSet.of(CANCEL, ROTATION) : EnumSet.of(CANCEL));
434 }
435
Jon Miranda16ea1b12017-12-12 14:52:48 -0800436 return view;
437 }
438
439 @Override
Chuck Liao8ec38e02020-02-26 20:59:32 +0800440 public void onDestroyView() {
441 if (TEMP_BOTTOM_ACTION_BAR_FEATURE) {
Chuck Liao4f059312020-03-13 23:09:46 +0800442 mBottomActionBar.hide();
Chuck Liao8ec38e02020-02-26 20:59:32 +0800443 mBottomActionBar.clearActionClickListeners();
Chuck Liao69630f12020-03-05 19:01:25 +0800444 updateThumbnail(null);
Chuck Liao8ec38e02020-02-26 20:59:32 +0800445 }
446 super.onDestroyView();
447 }
448
449 @Override
Jon Miranda16ea1b12017-12-12 14:52:48 -0800450 public void onClickTryAgain(@Destination int unused) {
451 if (mPendingSetIndividualHolder != null) {
452 mPendingSetIndividualHolder.setWallpaper();
453 }
454 }
455
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800456 void updateImageGridPadding(boolean addExtraBottomSpace) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800457 int gridPaddingPx = getResources().getDimensionPixelSize(R.dimen.grid_padding_desktop);
458 int bottomSheetHeightPx = getResources().getDimensionPixelSize(
459 R.dimen.current_wallpaper_bottom_sheet_layout_height);
460 int paddingBottomPx = addExtraBottomSpace ? bottomSheetHeightPx : 0;
461 // Only left and top may be set in order for the GridMarginDecoration to work properly.
462 mImageGrid.setPadding(
463 gridPaddingPx, gridPaddingPx, 0, paddingBottomPx);
464 }
465
Santiago Etchebehere460fbcb2019-08-20 11:47:30 -0700466 private void maybeSetUpImageGrid() {
Santiago Etchebehere8648bb82019-08-06 17:09:02 -0700467 // Skip if mImageGrid been initialized yet
468 if (mImageGrid == null) {
469 return;
470 }
471 // Skip if category hasn't loaded yet
472 if (mCategory == null) {
473 return;
474 }
475 // Skip if the adapter was already created
476 if (mAdapter != null) {
477 return;
478 }
Santiago Etchebehere460fbcb2019-08-20 11:47:30 -0700479 setUpImageGrid();
480 }
481
482 /**
483 * Create the adapter and assign it to mImageGrid.
484 * Both mImageGrid and mCategory are guaranteed to not be null when this method is called.
485 */
486 void setUpImageGrid() {
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800487 mAdapter = new IndividualAdapter(mWallpapers);
488 mImageGrid.setAdapter(mAdapter);
489 mImageGrid.setLayoutManager(new GridLayoutManager(getActivity(), getNumColumns()));
490 }
491
Jon Miranda16ea1b12017-12-12 14:52:48 -0800492 /**
493 * Enables and populates the "Currently set" wallpaper BottomSheet.
494 */
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800495 void setUpBottomSheet() {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800496 mImageGrid.addOnScrollListener(new OnScrollListener() {
497 @Override
498 public void onScrolled(RecyclerView recyclerView, int dx, final int dy) {
499 if (mCurrentWallpaperBottomSheetPresenter == null) {
500 return;
501 }
502
503 if (mCurrentWallpaperBottomSheetExpandedRunnable != null) {
504 mHandler.removeCallbacks(mCurrentWallpaperBottomSheetExpandedRunnable);
505 }
506 mCurrentWallpaperBottomSheetExpandedRunnable = new Runnable() {
507 @Override
508 public void run() {
509 if (dy > 0) {
510 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(false);
511 } else {
512 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(true);
513 }
514 }
515 };
516 mHandler.postDelayed(mCurrentWallpaperBottomSheetExpandedRunnable, 100);
517 }
518 });
519 }
520
521 @Override
522 public void onResume() {
523 super.onResume();
524
Santiago Etchebehere8cad0dd2019-10-17 10:52:39 -0700525 WallpaperPreferences preferences = InjectorProvider.getInjector()
526 .getPreferences(getActivity());
Jon Miranda16ea1b12017-12-12 14:52:48 -0800527 preferences.setLastAppActiveTimestamp(new Date().getTime());
528
529 // Reset Glide memory settings to a "normal" level of usage since it may have been lowered in
530 // PreviewFragment.
531 Glide.get(getActivity()).setMemoryCategory(MemoryCategory.NORMAL);
532
533 // Show the staged 'start rotation' error dialog fragment if there is one that was unable to be
534 // shown earlier when this fragment's hosting activity didn't allow committing fragment
535 // transactions.
536 if (mStagedStartRotationErrorDialogFragment != null) {
537 mStagedStartRotationErrorDialogFragment.show(
538 getFragmentManager(), TAG_START_ROTATION_ERROR_DIALOG);
539 mStagedStartRotationErrorDialogFragment = null;
540 }
541
542 // Show the staged 'load wallpaper' or 'set wallpaper' error dialog fragments if there is one
543 // that was unable to be shown earlier when this fragment's hosting activity didn't allow
544 // committing fragment transactions.
545 if (mStagedSetWallpaperErrorDialogFragment != null) {
546 mStagedSetWallpaperErrorDialogFragment.show(
547 getFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
548 mStagedSetWallpaperErrorDialogFragment = null;
549 }
550
551 if (isRotationEnabled()) {
552 if (mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
553 // Refresh the state of the "start rotation" in case something changed the current daily
554 // rotation while this fragment was paused.
555 RotationHolder rotationHolder = (RotationHolder) mImageGrid
556 .findViewHolderForAdapterPosition(
557 SPECIAL_FIXED_TILE_ADAPTER_POSITION);
558 // The RotationHolder may be null if the RecyclerView has not created the view
559 // holder yet.
560 if (rotationHolder != null && Flags.dynamicStartRotationTileEnabled) {
561 refreshRotationHolder(rotationHolder);
562 }
563 } else if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
564 if (mWasUpdateRunnableRun && !mWallpapers.isEmpty()) {
565 // Must be resuming from a previously stopped state, so re-schedule the update of the
566 // daily wallpapers tile thumbnail.
567 mUpdateDailyWallpaperThumbRunnable.run();
568 }
569 }
570 }
571
572 }
573
574 @Override
575 public void onStop() {
576 super.onStop();
577 mHandler.removeCallbacks(mUpdateDailyWallpaperThumbRunnable);
578 }
579
580 @Override
581 public void onDestroy() {
582 super.onDestroy();
583 if (mProgressDialog != null) {
584 mProgressDialog.dismiss();
585 }
586 mWallpaperChangedNotifier.unregisterListener(mWallpaperChangedListener);
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700587 if (mAppStatusListener != null) {
588 mPackageStatusNotifier.removeListener(mAppStatusListener);
589 }
Chuck Liao69630f12020-03-05 19:01:25 +0800590 mWallpaperSetter.cleanUp();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800591 }
592
593 @Override
594 public void retryStartRotation(@NetworkPreference int networkPreference) {
595 startRotation(networkPreference);
596 }
597
598 public void setCurrentWallpaperBottomSheetPresenter(
599 CurrentWallpaperBottomSheetPresenter presenter) {
600 mCurrentWallpaperBottomSheetPresenter = presenter;
601 }
602
603 public void setWallpapersUiContainer(WallpapersUiContainer uiContainer) {
604 mWallpapersUiContainer = uiContainer;
605 }
606
607 /**
608 * Enable a test mode of operation -- in which certain UI features are disabled to allow for
609 * UI tests to run correctly. Works around issue in ProgressDialog currently where the dialog
610 * constantly keeps the UI thread alive and blocks a test forever.
611 *
612 * @param testingMode
613 */
614 void setTestingMode(boolean testingMode) {
615 mTestingMode = testingMode;
616 }
617
618 /**
619 * Asynchronously fetches the refreshed rotation initialization state that is up to date with the
620 * state of the user's device and binds the state of the current category's rotation to the "start
621 * rotation" tile.
622 */
Santiago Etchebehere8cad0dd2019-10-17 10:52:39 -0700623 private void refreshRotationHolder(RotationHolder rotationHolder) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800624 mWallpaperRotationInitializer.fetchRotationInitializationState(getContext(),
Santiago Etchebehere8cad0dd2019-10-17 10:52:39 -0700625 rotationState -> {
626 // Update the UI state of the "start rotation" tile displayed on screen.
627 // Do this in a Handler so it is scheduled at the end of the message queue.
628 // This is necessary to ensure we do not remove or add data from the adapter
629 // while the layout is still being computed. RecyclerView documentation
630 // therefore recommends performing such changes in a Handler.
631 new Handler().post(() -> {
632 // A config change may have destroyed the activity since the refresh
633 // started, so check for that to avoid an NPE.
634 if (getActivity() == null) {
635 return;
636 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800637
Santiago Etchebehere8cad0dd2019-10-17 10:52:39 -0700638 rotationHolder.bindRotationInitializationState(rotationState);
639 });
Jon Miranda16ea1b12017-12-12 14:52:48 -0800640 });
641 }
642
643 @Override
644 public void startRotation(@NetworkPreference final int networkPreference) {
645 if (!isRotationEnabled()) {
646 Log.e(TAG, "Rotation is not enabled for this category " + mCategory.getTitle());
647 return;
648 }
649
650 // ProgressDialog endlessly updates the UI thread, keeping it from going idle which therefore
651 // causes Espresso to hang once the dialog is shown.
652 if (mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE && !mTestingMode) {
653 int themeResId;
654 if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
655 themeResId = R.style.ProgressDialogThemePreL;
656 } else {
657 themeResId = R.style.LightDialogTheme;
658 }
659 mProgressDialog = new ProgressDialog(getActivity(), themeResId);
660
661 mProgressDialog.setTitle(PROGRESS_DIALOG_NO_TITLE);
662 mProgressDialog.setMessage(
663 getResources().getString(R.string.start_rotation_progress_message));
664 mProgressDialog.setIndeterminate(PROGRESS_DIALOG_INDETERMINATE);
665 mProgressDialog.show();
666 }
667
668 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
669 mAdapter.mPendingSelectedAdapterPosition = SPECIAL_FIXED_TILE_ADAPTER_POSITION;
670 }
671
672 final Context appContext = getActivity().getApplicationContext();
673
674 mWallpaperRotationInitializer.setFirstWallpaperInRotation(
675 appContext,
676 networkPreference,
677 new Listener() {
678 @Override
679 public void onFirstWallpaperInRotationSet() {
680 if (mProgressDialog != null) {
681 mProgressDialog.dismiss();
682 }
683
684 // The fragment may be detached from its containing activity if the user exits the
685 // app before the first wallpaper image in rotation finishes downloading.
686 Activity activity = getActivity();
687
Jon Miranda16ea1b12017-12-12 14:52:48 -0800688
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700689 if (mWallpaperRotationInitializer.startRotation(appContext)) {
690 if (activity != null
691 && mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
692 try {
693 Toast.makeText(getActivity(),
694 R.string.wallpaper_set_successfully_message,
695 Toast.LENGTH_SHORT).show();
696 } catch (NotFoundException e) {
697 Log.e(TAG, "Could not show toast " + e);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800698 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800699
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700700 activity.setResult(Activity.RESULT_OK);
701 activity.finish();
702 } else if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
703 mAdapter.updateSelectedTile(SPECIAL_FIXED_TILE_ADAPTER_POSITION);
704 }
705 } else { // Failed to start rotation.
706 showStartRotationErrorDialog(networkPreference);
707
708 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
709 DesktopRotationHolder rotationViewHolder =
710 (DesktopRotationHolder)
711 mImageGrid.findViewHolderForAdapterPosition(
712 SPECIAL_FIXED_TILE_ADAPTER_POSITION);
713 rotationViewHolder.setSelectionState(
714 SelectableHolder.SELECTION_STATE_DESELECTED);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800715 }
716 }
717 }
718
719 @Override
720 public void onError() {
721 if (mProgressDialog != null) {
722 mProgressDialog.dismiss();
723 }
724
725 showStartRotationErrorDialog(networkPreference);
726
727 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
728 DesktopRotationHolder rotationViewHolder =
729 (DesktopRotationHolder) mImageGrid.findViewHolderForAdapterPosition(
730 SPECIAL_FIXED_TILE_ADAPTER_POSITION);
731 rotationViewHolder.setSelectionState(SelectableHolder.SELECTION_STATE_DESELECTED);
732 }
733 }
734 });
735 }
736
737 private void showStartRotationErrorDialog(@NetworkPreference int networkPreference) {
738 BaseActivity activity = (BaseActivity) getActivity();
739 if (activity != null) {
740 StartRotationErrorDialogFragment startRotationErrorDialogFragment =
741 StartRotationErrorDialogFragment.newInstance(networkPreference);
742 startRotationErrorDialogFragment.setTargetFragment(
743 IndividualPickerFragment.this, UNUSED_REQUEST_CODE);
744
745 if (activity.isSafeToCommitFragmentTransaction()) {
746 startRotationErrorDialogFragment.show(
747 getFragmentManager(), TAG_START_ROTATION_ERROR_DIALOG);
748 } else {
749 mStagedStartRotationErrorDialogFragment = startRotationErrorDialogFragment;
750 }
751 }
752 }
753
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800754 int getNumColumns() {
Ching-Sung Li9df77a32019-07-10 11:45:30 +0800755 Activity activity = getActivity();
756 return activity == null ? 0 : TileSizeCalculator.getNumIndividualColumns(activity);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800757 }
758
759 /**
760 * Returns whether rotation is enabled for this category.
761 */
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800762 boolean isRotationEnabled() {
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700763 return mWallpaperRotationInitializer != null;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800764 }
765
766 @Override
767 public void onCurrentWallpaperRefreshed() {
768 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(true);
769 }
770
Chuck Liao69630f12020-03-05 19:01:25 +0800771
772 @Override
773 public void onSet(int destination) {
774 if (mSelectedWallpaperInfo == null) {
775 Log.e(TAG, "Unable to set wallpaper since the selected wallpaper info is null");
776 return;
777 }
778
“Chuckffd832c2020-03-22 02:15:58 +0800779 mWallpaperPersister.setWallpaperInfoInPreview(mSelectedWallpaperInfo);
Chuck Liao69630f12020-03-05 19:01:25 +0800780 if (mSelectedWallpaperInfo instanceof LiveWallpaperInfo) {
781 mWallpaperSetter.setCurrentWallpaper(getActivity(), mSelectedWallpaperInfo, null,
782 destination, 0, null, mSetWallpaperCallback);
783 } else {
784 mWallpaperSetter.setCurrentWallpaper(
785 getActivity(), mSelectedWallpaperInfo, destination, mSetWallpaperCallback);
786 }
787 }
788
789 private WallpaperPersister.SetWallpaperCallback mSetWallpaperCallback =
790 new WallpaperPersister.SetWallpaperCallback() {
791 @Override
“Chuckffd832c2020-03-22 02:15:58 +0800792 public void onSuccess(WallpaperInfo wallpaperInfo) {
Chuck Liao69630f12020-03-05 19:01:25 +0800793 // TODO(b/150913705): Show the snack bar.
794 mBottomActionBar.enableActions();
“Chuckffd832c2020-03-22 02:15:58 +0800795 updateAppliedStatus(mAppliedWallpaperInfo, false);
796 updateAppliedStatus(wallpaperInfo, true);
797 mAppliedWallpaperInfo = wallpaperInfo;
798
799 mWallpaperPersister.onLiveWallpaperSet();
Chuck Liao69630f12020-03-05 19:01:25 +0800800 }
801
802 @Override
803 public void onError(@Nullable Throwable throwable) {
804 Log.e(TAG, "Can't apply the wallpaper.");
805 mBottomActionBar.enableActions();
806 }
807 };
808
809 @Override
810 public void onDialogDismissed(boolean withItemSelected) {
811 if (!withItemSelected) {
812 mBottomActionBar.enableActions();
813 }
814 }
815
Jon Miranda16ea1b12017-12-12 14:52:48 -0800816 /**
817 * Shows a "set wallpaper" error dialog with a failure message and button to try again.
818 */
819 private void showSetWallpaperErrorDialog() {
820 SetWallpaperErrorDialogFragment dialogFragment = SetWallpaperErrorDialogFragment.newInstance(
821 R.string.set_wallpaper_error_message, WallpaperPersister.DEST_BOTH);
822 dialogFragment.setTargetFragment(this, UNUSED_REQUEST_CODE);
823
824 if (((BaseActivity) getActivity()).isSafeToCommitFragmentTransaction()) {
825 dialogFragment.show(getFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
826 } else {
827 mStagedSetWallpaperErrorDialogFragment = dialogFragment;
828 }
829 }
830
Chuck Liao69630f12020-03-05 19:01:25 +0800831 private void updateBottomActions(boolean hasWallpaperSelected) {
832 mBottomActionBar.showActions(
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800833 hasWallpaperSelected ? EnumSet.of(APPLY, INFORMATION) : EnumSet.of(ROTATION));
Chuck Liao69630f12020-03-05 19:01:25 +0800834 mBottomActionBar.hideActions(
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800835 hasWallpaperSelected ? EnumSet.of(ROTATION) : EnumSet.of(APPLY, INFORMATION));
Chuck Liao69630f12020-03-05 19:01:25 +0800836 }
837
838 private void updateThumbnail(WallpaperInfo selectedWallpaperInfo) {
Chuck Liaof40063f2020-03-03 18:35:24 +0800839 ThumbnailUpdater thumbnailUpdater = (ThumbnailUpdater) getParentFragment();
Chuck Liao69630f12020-03-05 19:01:25 +0800840 if (thumbnailUpdater == null) {
841 return;
842 }
843
844 if (selectedWallpaperInfo != null) {
845 thumbnailUpdater.updateThumbnail(selectedWallpaperInfo);
846 } else {
847 thumbnailUpdater.restoreThumbnails();
Chuck Liaof40063f2020-03-03 18:35:24 +0800848 }
849 }
850
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800851 private void onWallpaperSelected(@Nullable WallpaperInfo newSelectedWallpaperInfo) {
852 if (mSelectedWallpaperInfo == newSelectedWallpaperInfo) {
“Chuckffd832c2020-03-22 02:15:58 +0800853 return;
854 }
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800855 // Update current wallpaper.
“Chuckffd832c2020-03-22 02:15:58 +0800856 updateActivatedStatus(mSelectedWallpaperInfo == null
857 ? mAppliedWallpaperInfo : mSelectedWallpaperInfo, false);
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800858 // Update new selected wallpaper.
859 updateActivatedStatus(newSelectedWallpaperInfo == null
860 ? mAppliedWallpaperInfo : newSelectedWallpaperInfo, true);
861
862 mSelectedWallpaperInfo = newSelectedWallpaperInfo;
863 updateBottomActions(mSelectedWallpaperInfo != null);
864 updateThumbnail(mSelectedWallpaperInfo);
865 // Populate wallpaper info to bottom sheet page.
866 if (mSelectedWallpaperInfo != null) {
867 mBottomActionBar.populateInfoPage(
868 mSelectedWallpaperInfo.getAttributions(getContext()),
869 shouldShowMetadataInPreview(mSelectedWallpaperInfo));
870 }
Chuck Liaof40063f2020-03-03 18:35:24 +0800871 }
872
Chuck Liao6a37a1c2020-03-07 03:39:43 +0800873 private void updateActivatedStatus(WallpaperInfo wallpaperInfo, boolean isActivated) {
“Chuckffd832c2020-03-22 02:15:58 +0800874 if (wallpaperInfo == null) {
875 return;
876 }
Chuck Liao6a37a1c2020-03-07 03:39:43 +0800877 int index = mWallpapers.indexOf(wallpaperInfo);
878 index = (isRotationEnabled() || mCategory.supportsCustomPhotos())
879 ? index + 1 : index;
880 ViewHolder holder = mImageGrid.findViewHolderForAdapterPosition(index);
881 if (holder != null) {
882 holder.itemView.setActivated(isActivated);
883 } else {
884 // Item is not visible, make sure the item is re-bound when it becomes visible.
885 mAdapter.notifyItemChanged(index);
886 }
887 }
888
“Chuckffd832c2020-03-22 02:15:58 +0800889 private void updateAppliedStatus(WallpaperInfo wallpaperInfo, boolean isApplied) {
890 if (wallpaperInfo == null) {
891 return;
892 }
893 int index = mWallpapers.indexOf(wallpaperInfo);
894 index = (isRotationEnabled() || mCategory.supportsCustomPhotos())
895 ? index + 1 : index;
896 ViewHolder holder = mImageGrid.findViewHolderForAdapterPosition(index);
897 if (holder != null) {
898 holder.itemView.findViewById(R.id.check_circle)
899 .setVisibility(isApplied ? View.VISIBLE : View.GONE);
900 } else {
901 // Item is not visible, make sure the item is re-bound when it becomes visible.
902 mAdapter.notifyItemChanged(index);
903 }
904 }
905
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800906 private static boolean shouldShowMetadataInPreview(WallpaperInfo wallpaperInfo) {
907 android.app.WallpaperInfo wallpaperComponent = wallpaperInfo.getWallpaperComponent();
908 return wallpaperComponent == null || wallpaperComponent.getShowMetadataInPreview();
909 }
910
Jon Miranda16ea1b12017-12-12 14:52:48 -0800911 /**
912 * ViewHolder subclass for "daily refresh" tile in the RecyclerView, only shown if rotation is
913 * enabled for this category.
914 */
915 private class RotationHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
916
Chuck Liaoba401232020-03-13 20:11:04 +0800917 private CardView mTileLayout;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800918 private TextView mRotationMessage;
919 private TextView mRotationTitle;
920 private ImageView mRefreshIcon;
921
922 RotationHolder(View itemView) {
923 super(itemView);
924 itemView.setOnClickListener(this);
925
Santiago Etchebehere8cad0dd2019-10-17 10:52:39 -0700926 mTileLayout = itemView.findViewById(R.id.daily_refresh);
927 mRotationMessage = itemView.findViewById(R.id.rotation_tile_message);
928 mRotationTitle = itemView.findViewById(R.id.rotation_tile_title);
929 mRefreshIcon = itemView.findViewById(R.id.rotation_tile_refresh_icon);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800930 mTileLayout.getLayoutParams().height = mTileSizePx.y;
931
932 // If the feature flag for "dynamic start rotation tile" is not enabled, fall back to the
933 // static UI with a blue accent color background and "Tap to turn on" text.
934 if (!Flags.dynamicStartRotationTileEnabled) {
935 mTileLayout.setBackgroundColor(
936 getResources().getColor(R.color.rotation_tile_enabled_background_color));
937 mRotationMessage.setText(R.string.daily_refresh_tile_subtitle);
938 mRotationTitle.setTextColor(
939 getResources().getColor(R.color.rotation_tile_enabled_title_text_color));
940 mRotationMessage.setTextColor(
941 getResources().getColor(R.color.rotation_tile_enabled_subtitle_text_color));
942 mRefreshIcon.setColorFilter(
Santiago Etchebehere8cad0dd2019-10-17 10:52:39 -0700943 getResources().getColor(R.color.rotation_tile_enabled_refresh_icon_color),
944 Mode.SRC_IN);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800945 return;
946 }
947
948 // Initialize the state of the "start rotation" tile (i.e., whether it is gray or blue to
949 // indicate if rotation is turned on for the current category) with last-known rotation state
950 // that could be stale. The last-known rotation state is correct in most cases and is a good
951 // starting point but may not be accurate if the user set a wallpaper through a 3rd party app
952 // while this app was paused.
953 int rotationState = mWallpaperRotationInitializer.getRotationInitializationStateDirty(
954 getContext());
955 bindRotationInitializationState(rotationState);
956 }
957
958 @Override
959 public void onClick(View v) {
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700960 DialogFragment startRotationDialogFragment = new StartRotationDialogFragment();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800961 startRotationDialogFragment.setTargetFragment(
962 IndividualPickerFragment.this, UNUSED_REQUEST_CODE);
963 startRotationDialogFragment.show(getFragmentManager(), TAG_START_ROTATION_DIALOG);
964 }
965
966 /**
967 * Binds the provided rotation initialization state to the RotationHolder and updates the tile's
968 * UI to be in sync with the state (i.e., message and color appropriately reflect the state to
969 * the user).
970 */
971 void bindRotationInitializationState(@RotationInitializationState int rotationState) {
972 int newBackgroundColor =
973 (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
974 ? getResources().getColor(R.color.rotation_tile_not_enabled_background_color)
975 : getResources().getColor(R.color.rotation_tile_enabled_background_color);
976 int newTitleTextColor =
977 (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
978 ? getResources().getColor(R.color.rotation_tile_not_enabled_title_text_color)
979 : getResources().getColor(R.color.rotation_tile_enabled_title_text_color);
980 int newSubtitleTextColor =
981 (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
982 ? getResources().getColor(R.color.rotation_tile_not_enabled_subtitle_text_color)
983 : getResources().getColor(R.color.rotation_tile_enabled_subtitle_text_color);
984 int newRefreshIconColor =
985 (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
986 ? getResources().getColor(R.color.rotation_tile_not_enabled_refresh_icon_color)
987 : getResources().getColor(R.color.rotation_tile_enabled_refresh_icon_color);
988
Chuck Liaoba401232020-03-13 20:11:04 +0800989 mTileLayout.setCardBackgroundColor(newBackgroundColor);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800990 mRotationTitle.setTextColor(newTitleTextColor);
991 mRotationMessage.setText(getResIdForRotationState(rotationState));
992 mRotationMessage.setTextColor(newSubtitleTextColor);
993 mRefreshIcon.setColorFilter(newRefreshIconColor, Mode.SRC_IN);
994 }
995 }
996
997 /**
998 * RecyclerView Adapter subclass for the wallpaper tiles in the RecyclerView.
999 */
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +08001000 class IndividualAdapter extends RecyclerView.Adapter<ViewHolder> {
1001 static final int ITEM_VIEW_TYPE_ROTATION = 1;
1002 static final int ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER = 2;
1003 static final int ITEM_VIEW_TYPE_MY_PHOTOS = 3;
Jon Miranda16ea1b12017-12-12 14:52:48 -08001004
1005 private final List<WallpaperInfo> mWallpapers;
1006
1007 private int mPendingSelectedAdapterPosition;
1008 private int mSelectedAdapterPosition;
1009
1010 IndividualAdapter(List<WallpaperInfo> wallpapers) {
1011 mWallpapers = wallpapers;
1012 mPendingSelectedAdapterPosition = -1;
1013 mSelectedAdapterPosition = -1;
1014 }
1015
1016 @Override
1017 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
1018 switch (viewType) {
1019 case ITEM_VIEW_TYPE_ROTATION:
1020 return createRotationHolder(parent);
1021 case ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER:
1022 return createIndividualHolder(parent);
1023 case ITEM_VIEW_TYPE_MY_PHOTOS:
1024 return createMyPhotosHolder(parent);
1025 default:
1026 Log.e(TAG, "Unsupported viewType " + viewType + " in IndividualAdapter");
1027 return null;
1028 }
1029 }
1030
1031 @Override
1032 public int getItemViewType(int position) {
1033 if (isRotationEnabled() && position == SPECIAL_FIXED_TILE_ADAPTER_POSITION) {
1034 return ITEM_VIEW_TYPE_ROTATION;
1035 }
1036
1037 // A category cannot have both a "start rotation" tile and a "my photos" tile.
1038 if (mCategory.supportsCustomPhotos()
1039 && !isRotationEnabled()
1040 && position == SPECIAL_FIXED_TILE_ADAPTER_POSITION) {
1041 return ITEM_VIEW_TYPE_MY_PHOTOS;
1042 }
1043
1044 return ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER;
1045 }
1046
1047 @Override
1048 public void onBindViewHolder(ViewHolder holder, int position) {
1049 int viewType = getItemViewType(position);
1050
1051 switch (viewType) {
1052 case ITEM_VIEW_TYPE_ROTATION:
1053 onBindRotationHolder(holder, position);
1054 break;
1055 case ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER:
1056 onBindIndividualHolder(holder, position);
1057 break;
1058 case ITEM_VIEW_TYPE_MY_PHOTOS:
1059 ((MyPhotosViewHolder) holder).bind();
1060 break;
1061 default:
1062 Log.e(TAG, "Unsupported viewType " + viewType + " in IndividualAdapter");
1063 }
1064 }
1065
1066 @Override
1067 public int getItemCount() {
1068 return (isRotationEnabled() || mCategory.supportsCustomPhotos())
1069 ? mWallpapers.size() + 1
1070 : mWallpapers.size();
1071 }
1072
1073 private ViewHolder createRotationHolder(ViewGroup parent) {
1074 LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
1075 View view;
1076
1077 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
1078 view = layoutInflater.inflate(R.layout.grid_item_rotation_desktop, parent, false);
1079 SelectionAnimator selectionAnimator =
1080 new CheckmarkSelectionAnimator(getActivity(), view);
1081 return new DesktopRotationHolder(
1082 getActivity(), mTileSizePx.y, view, selectionAnimator,
1083 IndividualPickerFragment.this);
1084 } else { // MOBILE
1085 view = layoutInflater.inflate(R.layout.grid_item_rotation, parent, false);
1086 return new RotationHolder(view);
1087 }
1088 }
1089
1090 private ViewHolder createIndividualHolder(ViewGroup parent) {
1091 LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
1092 View view = layoutInflater.inflate(R.layout.grid_item_image, parent, false);
Chuck Liao6a37a1c2020-03-07 03:39:43 +08001093 if (!TEMP_BOTTOM_ACTION_BAR_FEATURE) {
1094 view.findViewById(R.id.tile).setPadding(0, 0, 0, 0);
1095 }
Jon Miranda16ea1b12017-12-12 14:52:48 -08001096
1097 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
1098 SelectionAnimator selectionAnimator =
1099 new CheckmarkSelectionAnimator(getActivity(), view);
1100 return new SetIndividualHolder(
1101 getActivity(), mTileSizePx.y, view,
1102 selectionAnimator,
1103 new OnSetListener() {
1104 @Override
1105 public void onPendingWallpaperSet(int adapterPosition) {
1106 // Deselect and hide loading indicator for any previously pending tile.
1107 if (mPendingSelectedAdapterPosition != -1) {
1108 ViewHolder oldViewHolder = mImageGrid.findViewHolderForAdapterPosition(
1109 mPendingSelectedAdapterPosition);
1110 if (oldViewHolder instanceof SelectableHolder) {
1111 ((SelectableHolder) oldViewHolder).setSelectionState(
1112 SelectableHolder.SELECTION_STATE_DESELECTED);
1113 }
1114 }
1115
1116 if (mSelectedAdapterPosition != -1) {
1117 ViewHolder oldViewHolder = mImageGrid.findViewHolderForAdapterPosition(
1118 mSelectedAdapterPosition);
1119 if (oldViewHolder instanceof SelectableHolder) {
1120 ((SelectableHolder) oldViewHolder).setSelectionState(
1121 SelectableHolder.SELECTION_STATE_DESELECTED);
1122 }
1123 }
1124
1125 mPendingSelectedAdapterPosition = adapterPosition;
1126 }
1127
1128 @Override
1129 public void onWallpaperSet(int adapterPosition) {
1130 // No-op -- UI handles a new wallpaper being set by reacting to the
1131 // WallpaperChangedNotifier.
1132 }
1133
1134 @Override
1135 public void onWallpaperSetFailed(SetIndividualHolder holder) {
1136 showSetWallpaperErrorDialog();
1137 mPendingSetIndividualHolder = holder;
1138 }
1139 });
1140 } else { // MOBILE
Chuck Liao3d1a51c2020-02-17 18:29:34 +08001141 return new PreviewIndividualHolder(getActivity(), mTileSizePx.y, view);
Jon Miranda16ea1b12017-12-12 14:52:48 -08001142 }
1143 }
1144
1145 private ViewHolder createMyPhotosHolder(ViewGroup parent) {
1146 LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
1147 View view = layoutInflater.inflate(R.layout.grid_item_my_photos, parent, false);
1148
Santiago Etchebeherefab49612019-01-15 12:22:42 -08001149 return new MyPhotosViewHolder(getActivity(),
1150 ((MyPhotosStarterProvider) getActivity()).getMyPhotosStarter(),
1151 mTileSizePx.y, view);
Jon Miranda16ea1b12017-12-12 14:52:48 -08001152 }
1153
1154 /**
1155 * Marks the tile at the given position as selected with a visual indication. Also updates the
1156 * "currently selected" BottomSheet to reflect the newly selected tile.
1157 */
1158 private void updateSelectedTile(int newlySelectedPosition) {
1159 // Prevent multiple spinners from appearing with a user tapping several tiles in rapid
1160 // succession.
1161 if (mPendingSelectedAdapterPosition == mSelectedAdapterPosition) {
1162 return;
1163 }
1164
1165 if (mCurrentWallpaperBottomSheetPresenter != null) {
1166 mCurrentWallpaperBottomSheetPresenter.refreshCurrentWallpapers(
1167 IndividualPickerFragment.this);
1168
1169 if (mCurrentWallpaperBottomSheetExpandedRunnable != null) {
1170 mHandler.removeCallbacks(mCurrentWallpaperBottomSheetExpandedRunnable);
1171 }
1172 mCurrentWallpaperBottomSheetExpandedRunnable = new Runnable() {
1173 @Override
1174 public void run() {
1175 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(true);
1176 }
1177 };
1178 mHandler.postDelayed(mCurrentWallpaperBottomSheetExpandedRunnable, 100);
1179 }
1180
1181 // User may have switched to another category, thus detaching this fragment, so check here.
1182 // NOTE: We do this check after updating the current wallpaper BottomSheet so that the update
1183 // still occurs in the UI after the user selects that other category.
1184 if (getActivity() == null) {
1185 return;
1186 }
1187
1188 // Update the newly selected wallpaper ViewHolder and the old one so that if
1189 // selection UI state applies (desktop UI), it is updated.
1190 if (mSelectedAdapterPosition >= 0) {
1191 ViewHolder oldViewHolder = mImageGrid.findViewHolderForAdapterPosition(
1192 mSelectedAdapterPosition);
1193 if (oldViewHolder instanceof SelectableHolder) {
1194 ((SelectableHolder) oldViewHolder).setSelectionState(
1195 SelectableHolder.SELECTION_STATE_DESELECTED);
1196 }
1197 }
1198
1199 // Animate selection of newly selected tile.
1200 ViewHolder newViewHolder = mImageGrid
1201 .findViewHolderForAdapterPosition(newlySelectedPosition);
1202 if (newViewHolder instanceof SelectableHolder) {
1203 ((SelectableHolder) newViewHolder).setSelectionState(
1204 SelectableHolder.SELECTION_STATE_SELECTED);
1205 }
1206
1207 mSelectedAdapterPosition = newlySelectedPosition;
1208
1209 // If the tile was in the last row of the grid, add space below it so the user can scroll down
1210 // and up to see the BottomSheet without it fully overlapping the newly selected tile.
1211 int spanCount = ((GridLayoutManager) mImageGrid.getLayoutManager()).getSpanCount();
1212 int numRows = (int) Math.ceil((float) getItemCount() / spanCount);
1213 int rowOfNewlySelectedTile = newlySelectedPosition / spanCount;
1214 boolean isInLastRow = rowOfNewlySelectedTile == numRows - 1;
1215
1216 updateImageGridPadding(isInLastRow /* addExtraBottomSpace */);
1217 }
1218
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +08001219 void onBindRotationHolder(ViewHolder holder, int position) {
Jon Miranda16ea1b12017-12-12 14:52:48 -08001220 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
1221 String collectionId = mCategory.getCollectionId();
1222 ((DesktopRotationHolder) holder).bind(collectionId);
1223
1224 if (mWallpaperPreferences.getWallpaperPresentationMode()
1225 == WallpaperPreferences.PRESENTATION_MODE_ROTATING
1226 && collectionId.equals(mWallpaperPreferences.getHomeWallpaperCollectionId())) {
1227 mSelectedAdapterPosition = position;
1228 }
1229
1230 if (!mWasUpdateRunnableRun && !mWallpapers.isEmpty()) {
1231 updateDesktopDailyRotationThumbnail((DesktopRotationHolder) holder);
1232 mWasUpdateRunnableRun = true;
1233 }
1234 }
1235 }
1236
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +08001237 void onBindIndividualHolder(ViewHolder holder, int position) {
Jon Miranda16ea1b12017-12-12 14:52:48 -08001238 int wallpaperIndex = (isRotationEnabled() || mCategory.supportsCustomPhotos())
1239 ? position - 1 : position;
1240 WallpaperInfo wallpaper = mWallpapers.get(wallpaperIndex);
1241 ((IndividualHolder) holder).bindWallpaper(wallpaper);
1242 WallpaperPreferences prefs = InjectorProvider.getInjector().getPreferences(getContext());
“Chuckffd832c2020-03-22 02:15:58 +08001243 String appliedWallpaperId = prefs.getHomeWallpaperRemoteId();
1244 boolean isWallpaperApplied = wallpaper.getWallpaperId().equals(appliedWallpaperId);
1245 boolean isWallpaperSelected = wallpaper.equals(mSelectedWallpaperInfo);
1246 boolean hasUserSelectedWallpaper = mSelectedWallpaperInfo != null;
Jon Miranda16ea1b12017-12-12 14:52:48 -08001247
“Chuckffd832c2020-03-22 02:15:58 +08001248 if (isWallpaperApplied) {
Jon Miranda16ea1b12017-12-12 14:52:48 -08001249 mSelectedAdapterPosition = position;
“Chuckffd832c2020-03-22 02:15:58 +08001250 mAppliedWallpaperInfo = wallpaper;
Jon Miranda16ea1b12017-12-12 14:52:48 -08001251 }
Chuck Liaof40063f2020-03-03 18:35:24 +08001252
1253 if (TEMP_BOTTOM_ACTION_BAR_FEATURE) {
“Chuckffd832c2020-03-22 02:15:58 +08001254 holder.itemView.setActivated(
1255 (isWallpaperApplied && !hasUserSelectedWallpaper) || isWallpaperSelected);
1256 holder.itemView.findViewById(R.id.check_circle).setVisibility(
1257 isWallpaperApplied ? View.VISIBLE : View.GONE);
Chuck Liao69630f12020-03-05 19:01:25 +08001258 holder.itemView.findViewById(R.id.tile).setOnClickListener(
1259 view -> onWallpaperSelected(wallpaper));
Chuck Liaof40063f2020-03-03 18:35:24 +08001260 }
Jon Miranda16ea1b12017-12-12 14:52:48 -08001261 }
1262 }
Chuck Liaoba401232020-03-13 20:11:04 +08001263
1264 private class GridPaddingDecoration extends RecyclerView.ItemDecoration {
1265
1266 private int mPadding;
1267
1268 GridPaddingDecoration(int padding) {
1269 mPadding = padding;
1270 }
1271
1272 @Override
1273 public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
1274 RecyclerView.State state) {
1275 int position = parent.getChildAdapterPosition(view);
1276 if (position >= 0) {
1277 outRect.left = mPadding;
1278 outRect.right = mPadding;
1279 }
1280 }
1281 }
Jon Miranda16ea1b12017-12-12 14:52:48 -08001282}