blob: 506f3a02fe72345aaef92ab9574aaaf5d92645be [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;
“Chuck7ef99722020-03-22 04:34:03 +080025import android.app.WallpaperManager;
Jon Miranda16ea1b12017-12-12 14:52:48 -080026import android.content.Context;
Santiago Etchebehereb1854472019-06-06 17:44:54 -070027import android.content.res.Configuration;
Jon Miranda16ea1b12017-12-12 14:52:48 -080028import android.content.res.Resources.NotFoundException;
29import android.graphics.Point;
30import android.graphics.PorterDuff.Mode;
Chuck Liaoba401232020-03-13 20:11:04 +080031import android.graphics.Rect;
Jon Miranda16ea1b12017-12-12 14:52:48 -080032import android.os.Build.VERSION;
33import android.os.Build.VERSION_CODES;
34import android.os.Bundle;
35import android.os.Handler;
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -070036import android.service.wallpaper.WallpaperService;
Jon Miranda16ea1b12017-12-12 14:52:48 -080037import android.util.Log;
38import android.view.LayoutInflater;
39import android.view.View;
40import android.view.ViewGroup;
Jon Miranda16ea1b12017-12-12 14:52:48 -080041import android.widget.ImageView;
42import android.widget.TextView;
43import android.widget.Toast;
44
Santiago Etchebehereb1854472019-06-06 17:44:54 -070045import androidx.annotation.NonNull;
Chuck Liao69630f12020-03-05 19:01:25 +080046import androidx.annotation.Nullable;
Chuck Liaoba401232020-03-13 20:11:04 +080047import androidx.cardview.widget.CardView;
Santiago Etchebeherefab49612019-01-15 12:22:42 -080048import androidx.fragment.app.DialogFragment;
49import androidx.fragment.app.Fragment;
50import androidx.recyclerview.widget.GridLayoutManager;
51import androidx.recyclerview.widget.RecyclerView;
52import androidx.recyclerview.widget.RecyclerView.OnScrollListener;
53import androidx.recyclerview.widget.RecyclerView.ViewHolder;
54
Jon Miranda16ea1b12017-12-12 14:52:48 -080055import com.android.wallpaper.R;
56import com.android.wallpaper.asset.Asset;
57import com.android.wallpaper.asset.Asset.DrawableLoadedListener;
58import com.android.wallpaper.config.Flags;
Santiago Etchebehere8648bb82019-08-06 17:09:02 -070059import com.android.wallpaper.model.Category;
60import com.android.wallpaper.model.CategoryProvider;
61import com.android.wallpaper.model.CategoryReceiver;
Chuck Liao69630f12020-03-05 19:01:25 +080062import com.android.wallpaper.model.LiveWallpaperInfo;
Jon Miranda16ea1b12017-12-12 14:52:48 -080063import com.android.wallpaper.model.WallpaperCategory;
64import com.android.wallpaper.model.WallpaperInfo;
65import com.android.wallpaper.model.WallpaperReceiver;
66import com.android.wallpaper.model.WallpaperRotationInitializer;
67import com.android.wallpaper.model.WallpaperRotationInitializer.Listener;
68import com.android.wallpaper.model.WallpaperRotationInitializer.NetworkPreference;
69import com.android.wallpaper.model.WallpaperRotationInitializer.RotationInitializationState;
Jon Miranda16ea1b12017-12-12 14:52:48 -080070import com.android.wallpaper.module.FormFactorChecker;
71import com.android.wallpaper.module.FormFactorChecker.FormFactor;
72import com.android.wallpaper.module.Injector;
73import com.android.wallpaper.module.InjectorProvider;
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -070074import com.android.wallpaper.module.PackageStatusNotifier;
Jon Miranda16ea1b12017-12-12 14:52:48 -080075import com.android.wallpaper.module.WallpaperChangedNotifier;
76import com.android.wallpaper.module.WallpaperPersister;
77import com.android.wallpaper.module.WallpaperPersister.Destination;
78import com.android.wallpaper.module.WallpaperPreferences;
Chuck Liao69630f12020-03-05 19:01:25 +080079import com.android.wallpaper.module.WallpaperSetter;
Jon Miranda16ea1b12017-12-12 14:52:48 -080080import com.android.wallpaper.picker.BaseActivity;
81import com.android.wallpaper.picker.CurrentWallpaperBottomSheetPresenter;
Santiago Etchebeherefab49612019-01-15 12:22:42 -080082import com.android.wallpaper.picker.MyPhotosStarter.MyPhotosStarterProvider;
Jon Miranda16ea1b12017-12-12 14:52:48 -080083import com.android.wallpaper.picker.RotationStarter;
Chuck Liao69630f12020-03-05 19:01:25 +080084import com.android.wallpaper.picker.SetWallpaperDialogFragment;
Jon Miranda16ea1b12017-12-12 14:52:48 -080085import com.android.wallpaper.picker.SetWallpaperErrorDialogFragment;
86import com.android.wallpaper.picker.StartRotationDialogFragment;
87import com.android.wallpaper.picker.StartRotationErrorDialogFragment;
88import com.android.wallpaper.picker.WallpapersUiContainer;
89import com.android.wallpaper.picker.individual.SetIndividualHolder.OnSetListener;
90import com.android.wallpaper.util.DiskBasedLogger;
91import com.android.wallpaper.util.TileSizeCalculator;
Chuck Liao8ec38e02020-02-26 20:59:32 +080092import com.android.wallpaper.widget.BottomActionBar;
Sunny Goyal8600a3f2018-08-15 12:48:01 -070093
Jon Miranda16ea1b12017-12-12 14:52:48 -080094import com.bumptech.glide.Glide;
95import com.bumptech.glide.MemoryCategory;
96
97import java.util.ArrayList;
98import java.util.Date;
Chuck Liao8ec38e02020-02-26 20:59:32 +080099import java.util.EnumSet;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800100import java.util.List;
Chuck Liao11f4a762020-04-08 13:24:43 +0800101import java.util.Optional;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800102import java.util.Random;
103
104/**
105 * Displays the Main UI for picking an individual wallpaper image.
106 */
107public class IndividualPickerFragment extends Fragment
108 implements RotationStarter, StartRotationErrorDialogFragment.Listener,
109 CurrentWallpaperBottomSheetPresenter.RefreshListener,
Chuck Liao69630f12020-03-05 19:01:25 +0800110 SetWallpaperErrorDialogFragment.Listener, SetWallpaperDialogFragment.Listener {
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800111 /**
112 * Position of a special tile that doesn't belong to an individual wallpaper of the category,
113 * such as "my photos" or "daily rotation".
114 */
115 static final int SPECIAL_FIXED_TILE_ADAPTER_POSITION = 0;
116 static final String ARG_CATEGORY_COLLECTION_ID = "category_collection_id";
117
Jon Miranda16ea1b12017-12-12 14:52:48 -0800118 private static final String TAG = "IndividualPickerFrgmnt";
Jon Miranda16ea1b12017-12-12 14:52:48 -0800119 private static final int UNUSED_REQUEST_CODE = 1;
120 private static final String TAG_START_ROTATION_DIALOG = "start_rotation_dialog";
121 private static final String TAG_START_ROTATION_ERROR_DIALOG = "start_rotation_error_dialog";
122 private static final String PROGRESS_DIALOG_NO_TITLE = null;
123 private static final boolean PROGRESS_DIALOG_INDETERMINATE = true;
124 private static final String TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT =
125 "individual_set_wallpaper_error_dialog";
Santiago Etchebehereb1854472019-06-06 17:44:54 -0700126 private static final String KEY_NIGHT_MODE = "IndividualPickerFragment.NIGHT_MODE";
Jon Miranda16ea1b12017-12-12 14:52:48 -0800127
Chuck Liao8ec38e02020-02-26 20:59:32 +0800128 /**
129 * A temporary flag to hide the bottom action bar feature.
130 */
131 private static final boolean TEMP_BOTTOM_ACTION_BAR_FEATURE = false;
132
Chuck Liaof40063f2020-03-03 18:35:24 +0800133 /**
134 * An interface for updating the thumbnail with the specific wallpaper.
135 */
136 public interface ThumbnailUpdater {
137 /**
138 * Updates the thumbnail with the specific wallpaper.
139 */
140 void updateThumbnail(WallpaperInfo wallpaperInfo);
141
142 /**
143 * Restores to the thumbnails of the wallpapers which were applied.
144 */
145 void restoreThumbnails();
146 }
147
“Chuck7ef99722020-03-22 04:34:03 +0800148 /**
149 * An interface for receiving the destination of the new applied wallpaper.
150 */
151 public interface WallpaperDestinationCallback {
152 /**
153 * Called when the destination of the wallpaper is set.
154 *
155 * @param destination the destination which a wallpaper may be set.
156 * See {@link Destination} for more details.
157 */
158 void onDestinationSet(@Destination int destination);
159 }
160
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800161 WallpaperPreferences mWallpaperPreferences;
162 WallpaperChangedNotifier mWallpaperChangedNotifier;
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800163 RecyclerView mImageGrid;
164 IndividualAdapter mAdapter;
165 WallpaperCategory mCategory;
166 WallpaperRotationInitializer mWallpaperRotationInitializer;
167 List<WallpaperInfo> mWallpapers;
168 Point mTileSizePx;
169 WallpapersUiContainer mWallpapersUiContainer;
170 @FormFactor
171 int mFormFactor;
172 PackageStatusNotifier mPackageStatusNotifier;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800173
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800174 Handler mHandler;
175 Random mRandom;
176
177 WallpaperChangedNotifier.Listener mWallpaperChangedListener =
178 new WallpaperChangedNotifier.Listener() {
179 @Override
180 public void onWallpaperChanged() {
181 if (mFormFactor != FormFactorChecker.FORM_FACTOR_DESKTOP) {
182 return;
183 }
184
185 ViewHolder selectedViewHolder = mImageGrid.findViewHolderForAdapterPosition(
186 mAdapter.mSelectedAdapterPosition);
187
188 // Null remote ID => My Photos wallpaper, so deselect whatever was previously selected.
189 if (mWallpaperPreferences.getHomeWallpaperRemoteId() == null) {
190 if (selectedViewHolder instanceof SelectableHolder) {
191 ((SelectableHolder) selectedViewHolder).setSelectionState(
192 SelectableHolder.SELECTION_STATE_DESELECTED);
193 }
194 } else {
195 mAdapter.updateSelectedTile(mAdapter.mPendingSelectedAdapterPosition);
196 }
197 }
198 };
199 PackageStatusNotifier.Listener mAppStatusListener;
200
Jon Miranda16ea1b12017-12-12 14:52:48 -0800201 private ProgressDialog mProgressDialog;
202 private boolean mTestingMode;
203 private CurrentWallpaperBottomSheetPresenter mCurrentWallpaperBottomSheetPresenter;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800204 private SetIndividualHolder mPendingSetIndividualHolder;
205
206 /**
207 * Staged error dialog fragments that were unable to be shown when the activity didn't allow
208 * committing fragment transactions.
209 */
210 private SetWallpaperErrorDialogFragment mStagedSetWallpaperErrorDialogFragment;
211 private StartRotationErrorDialogFragment mStagedStartRotationErrorDialogFragment;
212
Jon Miranda16ea1b12017-12-12 14:52:48 -0800213 private Runnable mCurrentWallpaperBottomSheetExpandedRunnable;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800214
215 /**
216 * Whether {@code mUpdateDailyWallpaperThumbRunnable} has been run at least once in this
217 * invocation of the fragment.
218 */
219 private boolean mWasUpdateRunnableRun;
220
221 /**
222 * A Runnable which regularly updates the thumbnail for the "Daily wallpapers" tile in desktop
223 * mode.
224 */
225 private Runnable mUpdateDailyWallpaperThumbRunnable = new Runnable() {
226 @Override
227 public void run() {
228 ViewHolder viewHolder = mImageGrid.findViewHolderForAdapterPosition(
229 SPECIAL_FIXED_TILE_ADAPTER_POSITION);
230 if (viewHolder instanceof DesktopRotationHolder) {
231 updateDesktopDailyRotationThumbnail((DesktopRotationHolder) viewHolder);
232 } else { // viewHolder is null
233 // If the rotation tile is unavailable (because user has scrolled down, causing the
234 // ViewHolder to be recycled), schedule the update for some time later. Once user scrolls up
235 // again, the ViewHolder will be re-bound and its thumbnail will be updated.
236 mHandler.postDelayed(mUpdateDailyWallpaperThumbRunnable,
237 DesktopRotationHolder.CROSSFADE_DURATION_MILLIS
238 + DesktopRotationHolder.CROSSFADE_DURATION_PAUSE_MILLIS);
239 }
240 }
241 };
242
Chuck Liao8ec38e02020-02-26 20:59:32 +0800243 private BottomActionBar mBottomActionBar;
Chuck Liao69630f12020-03-05 19:01:25 +0800244 private WallpaperSetter mWallpaperSetter;
“Chuckffd832c2020-03-22 02:15:58 +0800245 private WallpaperPersister mWallpaperPersister;
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800246 @Nullable private WallpaperInfo mSelectedWallpaperInfo;
“Chuckffd832c2020-03-22 02:15:58 +0800247 private WallpaperInfo mAppliedWallpaperInfo;
“Chuck7ef99722020-03-22 04:34:03 +0800248 private WallpaperManager mWallpaperManager;
249 private int mWallpaperDestination;
Chuck Liao8ec38e02020-02-26 20:59:32 +0800250
Jon Miranda16ea1b12017-12-12 14:52:48 -0800251 public static IndividualPickerFragment newInstance(String collectionId) {
252 Bundle args = new Bundle();
253 args.putString(ARG_CATEGORY_COLLECTION_ID, collectionId);
254
255 IndividualPickerFragment fragment = new IndividualPickerFragment();
256 fragment.setArguments(args);
257 return fragment;
258 }
259
“Chuck7ef99722020-03-22 04:34:03 +0800260 /**
261 * Highlights the applied wallpaper (if it exists) according to the destination a wallpaper
262 * would be set.
263 *
264 * @param wallpaperDestination the destination a wallpaper would be set.
265 * It will be either {@link Destination#DEST_HOME_SCREEN}
266 * or {@link Destination#DEST_LOCK_SCREEN}.
267 */
268 public void highlightAppliedWallpaper(@Destination int wallpaperDestination) {
269 mWallpaperDestination = wallpaperDestination;
Chuck Liao11f4a762020-04-08 13:24:43 +0800270 if (mWallpapers != null) {
271 refreshAppliedWallpaper();
“Chuck7ef99722020-03-22 04:34:03 +0800272 }
273 }
274
Jon Miranda16ea1b12017-12-12 14:52:48 -0800275 private static int getResIdForRotationState(@RotationInitializationState int rotationState) {
276 switch (rotationState) {
277 case WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED:
278 return R.string.daily_refresh_tile_subtitle;
279 case WallpaperRotationInitializer.ROTATION_HOME_ONLY:
280 return R.string.home_screen_message;
281 case WallpaperRotationInitializer.ROTATION_HOME_AND_LOCK:
282 return R.string.home_and_lock_short_label;
283 default:
284 Log.e(TAG, "Unknown rotation intialization state: " + rotationState);
285 return R.string.home_screen_message;
286 }
287 }
288
289 private void updateDesktopDailyRotationThumbnail(DesktopRotationHolder holder) {
290 int wallpapersIndex = mRandom.nextInt(mWallpapers.size());
291 Asset newThumbnailAsset = mWallpapers.get(wallpapersIndex).getThumbAsset(
292 getActivity());
293 holder.updateThumbnail(newThumbnailAsset, new DrawableLoadedListener() {
294 @Override
295 public void onDrawableLoaded() {
296 if (getActivity() == null) {
297 return;
298 }
299
300 // Schedule the next update of the thumbnail.
301 int delayMillis = DesktopRotationHolder.CROSSFADE_DURATION_MILLIS
302 + DesktopRotationHolder.CROSSFADE_DURATION_PAUSE_MILLIS;
303 mHandler.postDelayed(mUpdateDailyWallpaperThumbRunnable, delayMillis);
304 }
305 });
306 }
307
308 @Override
309 public void onCreate(Bundle savedInstanceState) {
310 super.onCreate(savedInstanceState);
311
312 Injector injector = InjectorProvider.getInjector();
313 Context appContext = getContext().getApplicationContext();
314 mWallpaperPreferences = injector.getPreferences(appContext);
315
316 mWallpaperChangedNotifier = WallpaperChangedNotifier.getInstance();
317 mWallpaperChangedNotifier.registerListener(mWallpaperChangedListener);
318
“Chuck7ef99722020-03-22 04:34:03 +0800319 mWallpaperManager = WallpaperManager.getInstance(appContext);
320
Jon Miranda16ea1b12017-12-12 14:52:48 -0800321 mFormFactor = injector.getFormFactorChecker(appContext).getFormFactor();
322
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700323 mPackageStatusNotifier = injector.getPackageStatusNotifier(appContext);
324
“Chuckffd832c2020-03-22 02:15:58 +0800325 mWallpaperPersister = injector.getWallpaperPersister(appContext);
Chuck Liao69630f12020-03-05 19:01:25 +0800326 mWallpaperSetter = new WallpaperSetter(
“Chuckffd832c2020-03-22 02:15:58 +0800327 mWallpaperPersister,
Chuck Liao69630f12020-03-05 19:01:25 +0800328 injector.getPreferences(appContext),
329 injector.getUserEventLogger(appContext),
330 false);
331
Jon Miranda16ea1b12017-12-12 14:52:48 -0800332 mWallpapers = new ArrayList<>();
333 mRandom = new Random();
334 mHandler = new Handler();
335
Santiago Etchebehereb1854472019-06-06 17:44:54 -0700336 // Clear Glide's cache if night-mode changed to ensure thumbnails are reloaded
337 if (savedInstanceState != null && (savedInstanceState.getInt(KEY_NIGHT_MODE)
338 != (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK))) {
339 Glide.get(getContext()).clearMemory();
340 }
341
Santiago Etchebehere8648bb82019-08-06 17:09:02 -0700342 CategoryProvider categoryProvider = injector.getCategoryProvider(appContext);
343 categoryProvider.fetchCategories(new CategoryReceiver() {
344 @Override
345 public void onCategoryReceived(Category category) {
346 // Do nothing.
347 }
348
349 @Override
350 public void doneFetchingCategories() {
351 mCategory = (WallpaperCategory) categoryProvider.getCategory(
352 getArguments().getString(ARG_CATEGORY_COLLECTION_ID));
353 if (mCategory == null) {
354 DiskBasedLogger.e(TAG, "Failed to find the category.", getContext());
355
356 // The absence of this category in the CategoryProvider indicates a broken
357 // state, see b/38030129. Hence, finish the activity and return.
358 getActivity().finish();
359 return;
360 }
361 onCategoryLoaded();
362 }
363 }, false);
364 }
365
366 protected void onCategoryLoaded() {
367 mWallpaperRotationInitializer = mCategory.getWallpaperRotationInitializer();
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700368 fetchWallpapers(false);
369
370 if (mCategory.supportsThirdParty()) {
371 mAppStatusListener = (packageName, status) -> {
372 if (status != PackageStatusNotifier.PackageStatus.REMOVED ||
373 mCategory.containsThirdParty(packageName)) {
374 fetchWallpapers(true);
375 }
376 };
377 mPackageStatusNotifier.addListener(mAppStatusListener,
378 WallpaperService.SERVICE_INTERFACE);
379 }
Santiago Etchebehere8648bb82019-08-06 17:09:02 -0700380
Santiago Etchebehere460fbcb2019-08-20 11:47:30 -0700381 maybeSetUpImageGrid();
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700382 }
383
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800384 void fetchWallpapers(boolean forceReload) {
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700385 mWallpapers.clear();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800386 mCategory.fetchWallpapers(getActivity().getApplicationContext(), new WallpaperReceiver() {
387 @Override
388 public void onWallpapersReceived(List<WallpaperInfo> wallpapers) {
389 for (WallpaperInfo wallpaper : wallpapers) {
390 mWallpapers.add(wallpaper);
391 }
392
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700393 // Wallpapers may load after the adapter is initialized, in which case we have
394 // to explicitly notify that the data set has changed.
Jon Miranda16ea1b12017-12-12 14:52:48 -0800395 if (mAdapter != null) {
396 mAdapter.notifyDataSetChanged();
397 }
398
399 if (mWallpapersUiContainer != null) {
400 mWallpapersUiContainer.onWallpapersReady();
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700401 } else {
402 if (wallpapers.isEmpty()) {
403 // If there are no more wallpapers and we're on phone, just finish the
404 // Activity.
405 Activity activity = getActivity();
406 if (activity != null
407 && mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
408 activity.finish();
409 }
410 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800411 }
412 }
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700413 }, forceReload);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800414 }
415
416 @Override
Santiago Etchebehereb1854472019-06-06 17:44:54 -0700417 public void onSaveInstanceState(@NonNull Bundle outState) {
418 super.onSaveInstanceState(outState);
Santiago Etchebehere0ec065c2019-06-13 11:30:21 -0700419 outState.putInt(KEY_NIGHT_MODE,
Santiago Etchebehereb1854472019-06-06 17:44:54 -0700420 getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK);
421 }
422
423 @Override
Jon Miranda16ea1b12017-12-12 14:52:48 -0800424 public View onCreateView(LayoutInflater inflater, ViewGroup container,
425 Bundle savedInstanceState) {
426 View view = inflater.inflate(R.layout.fragment_individual_picker, container, false);
427
428 mTileSizePx = TileSizeCalculator.getIndividualTileSize(getActivity());
429
430 mImageGrid = (RecyclerView) view.findViewById(R.id.wallpaper_grid);
431 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
432 int gridPaddingPx = getResources().getDimensionPixelSize(R.dimen.grid_padding_desktop);
433 updateImageGridPadding(false /* addExtraBottomSpace */);
434 mImageGrid.setScrollBarSize(gridPaddingPx);
435 }
Chuck Liaoba401232020-03-13 20:11:04 +0800436 mImageGrid.addItemDecoration(new GridPaddingDecoration(
437 getResources().getDimensionPixelSize(R.dimen.grid_padding)));
Jon Miranda16ea1b12017-12-12 14:52:48 -0800438
Santiago Etchebehere460fbcb2019-08-20 11:47:30 -0700439 maybeSetUpImageGrid();
Santiago Etchebehere8648bb82019-08-06 17:09:02 -0700440
Jon Miranda16ea1b12017-12-12 14:52:48 -0800441 setUpBottomSheet();
442
Chuck Liao8ec38e02020-02-26 20:59:32 +0800443 if (TEMP_BOTTOM_ACTION_BAR_FEATURE) {
444 mBottomActionBar = getActivity().findViewById(R.id.bottom_actionbar);
445
Chuck Liao69630f12020-03-05 19:01:25 +0800446 mBottomActionBar.setActionClickListener(CANCEL, unused -> {
447 if (mSelectedWallpaperInfo != null) {
448 onWallpaperSelected(null);
449 return;
450 }
451 getActivity().onBackPressed();
452 });
Chuck Liao8ec38e02020-02-26 20:59:32 +0800453 mBottomActionBar.setActionClickListener(ROTATION, unused -> {
454 DialogFragment startRotationDialogFragment = new StartRotationDialogFragment();
455 startRotationDialogFragment.setTargetFragment(
456 IndividualPickerFragment.this, UNUSED_REQUEST_CODE);
457 startRotationDialogFragment.show(getFragmentManager(), TAG_START_ROTATION_DIALOG);
458 });
Chuck Liao69630f12020-03-05 19:01:25 +0800459 mBottomActionBar.setActionClickListener(APPLY, unused -> {
460 mBottomActionBar.disableActions();
461 mWallpaperSetter.requestDestination(getActivity(), getFragmentManager(), this,
462 mSelectedWallpaperInfo instanceof LiveWallpaperInfo);
463 });
Chuck Liao8ec38e02020-02-26 20:59:32 +0800464
Chuck Liao4f059312020-03-13 23:09:46 +0800465 mBottomActionBar.show();
Chuck Liao8ec38e02020-02-26 20:59:32 +0800466 mBottomActionBar.showActionsOnly(
467 isRotationEnabled() ? EnumSet.of(CANCEL, ROTATION) : EnumSet.of(CANCEL));
468 }
469
Jon Miranda16ea1b12017-12-12 14:52:48 -0800470 return view;
471 }
472
473 @Override
Chuck Liao8ec38e02020-02-26 20:59:32 +0800474 public void onDestroyView() {
475 if (TEMP_BOTTOM_ACTION_BAR_FEATURE) {
Chuck Liao4f059312020-03-13 23:09:46 +0800476 mBottomActionBar.hide();
Chuck Liao8ec38e02020-02-26 20:59:32 +0800477 mBottomActionBar.clearActionClickListeners();
Chuck Liao69630f12020-03-05 19:01:25 +0800478 updateThumbnail(null);
Chuck Liao8ec38e02020-02-26 20:59:32 +0800479 }
480 super.onDestroyView();
481 }
482
483 @Override
Jon Miranda16ea1b12017-12-12 14:52:48 -0800484 public void onClickTryAgain(@Destination int unused) {
485 if (mPendingSetIndividualHolder != null) {
486 mPendingSetIndividualHolder.setWallpaper();
487 }
488 }
489
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800490 void updateImageGridPadding(boolean addExtraBottomSpace) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800491 int gridPaddingPx = getResources().getDimensionPixelSize(R.dimen.grid_padding_desktop);
492 int bottomSheetHeightPx = getResources().getDimensionPixelSize(
493 R.dimen.current_wallpaper_bottom_sheet_layout_height);
494 int paddingBottomPx = addExtraBottomSpace ? bottomSheetHeightPx : 0;
495 // Only left and top may be set in order for the GridMarginDecoration to work properly.
496 mImageGrid.setPadding(
497 gridPaddingPx, gridPaddingPx, 0, paddingBottomPx);
498 }
499
Santiago Etchebehere460fbcb2019-08-20 11:47:30 -0700500 private void maybeSetUpImageGrid() {
Santiago Etchebehere8648bb82019-08-06 17:09:02 -0700501 // Skip if mImageGrid been initialized yet
502 if (mImageGrid == null) {
503 return;
504 }
505 // Skip if category hasn't loaded yet
506 if (mCategory == null) {
507 return;
508 }
509 // Skip if the adapter was already created
510 if (mAdapter != null) {
511 return;
512 }
Santiago Etchebehere460fbcb2019-08-20 11:47:30 -0700513 setUpImageGrid();
514 }
515
516 /**
517 * Create the adapter and assign it to mImageGrid.
518 * Both mImageGrid and mCategory are guaranteed to not be null when this method is called.
519 */
520 void setUpImageGrid() {
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800521 mAdapter = new IndividualAdapter(mWallpapers);
522 mImageGrid.setAdapter(mAdapter);
523 mImageGrid.setLayoutManager(new GridLayoutManager(getActivity(), getNumColumns()));
524 }
525
Jon Miranda16ea1b12017-12-12 14:52:48 -0800526 /**
527 * Enables and populates the "Currently set" wallpaper BottomSheet.
528 */
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800529 void setUpBottomSheet() {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800530 mImageGrid.addOnScrollListener(new OnScrollListener() {
531 @Override
532 public void onScrolled(RecyclerView recyclerView, int dx, final int dy) {
533 if (mCurrentWallpaperBottomSheetPresenter == null) {
534 return;
535 }
536
537 if (mCurrentWallpaperBottomSheetExpandedRunnable != null) {
538 mHandler.removeCallbacks(mCurrentWallpaperBottomSheetExpandedRunnable);
539 }
540 mCurrentWallpaperBottomSheetExpandedRunnable = new Runnable() {
541 @Override
542 public void run() {
543 if (dy > 0) {
544 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(false);
545 } else {
546 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(true);
547 }
548 }
549 };
550 mHandler.postDelayed(mCurrentWallpaperBottomSheetExpandedRunnable, 100);
551 }
552 });
553 }
554
555 @Override
556 public void onResume() {
557 super.onResume();
558
Santiago Etchebehere8cad0dd2019-10-17 10:52:39 -0700559 WallpaperPreferences preferences = InjectorProvider.getInjector()
560 .getPreferences(getActivity());
Jon Miranda16ea1b12017-12-12 14:52:48 -0800561 preferences.setLastAppActiveTimestamp(new Date().getTime());
562
563 // Reset Glide memory settings to a "normal" level of usage since it may have been lowered in
564 // PreviewFragment.
565 Glide.get(getActivity()).setMemoryCategory(MemoryCategory.NORMAL);
566
567 // Show the staged 'start rotation' error dialog fragment if there is one that was unable to be
568 // shown earlier when this fragment's hosting activity didn't allow committing fragment
569 // transactions.
570 if (mStagedStartRotationErrorDialogFragment != null) {
571 mStagedStartRotationErrorDialogFragment.show(
572 getFragmentManager(), TAG_START_ROTATION_ERROR_DIALOG);
573 mStagedStartRotationErrorDialogFragment = null;
574 }
575
576 // Show the staged 'load wallpaper' or 'set wallpaper' error dialog fragments if there is one
577 // that was unable to be shown earlier when this fragment's hosting activity didn't allow
578 // committing fragment transactions.
579 if (mStagedSetWallpaperErrorDialogFragment != null) {
580 mStagedSetWallpaperErrorDialogFragment.show(
581 getFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
582 mStagedSetWallpaperErrorDialogFragment = null;
583 }
584
585 if (isRotationEnabled()) {
586 if (mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
587 // Refresh the state of the "start rotation" in case something changed the current daily
588 // rotation while this fragment was paused.
589 RotationHolder rotationHolder = (RotationHolder) mImageGrid
590 .findViewHolderForAdapterPosition(
591 SPECIAL_FIXED_TILE_ADAPTER_POSITION);
592 // The RotationHolder may be null if the RecyclerView has not created the view
593 // holder yet.
594 if (rotationHolder != null && Flags.dynamicStartRotationTileEnabled) {
595 refreshRotationHolder(rotationHolder);
596 }
597 } else if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
598 if (mWasUpdateRunnableRun && !mWallpapers.isEmpty()) {
599 // Must be resuming from a previously stopped state, so re-schedule the update of the
600 // daily wallpapers tile thumbnail.
601 mUpdateDailyWallpaperThumbRunnable.run();
602 }
603 }
604 }
605
606 }
607
608 @Override
609 public void onStop() {
610 super.onStop();
611 mHandler.removeCallbacks(mUpdateDailyWallpaperThumbRunnable);
612 }
613
614 @Override
615 public void onDestroy() {
616 super.onDestroy();
617 if (mProgressDialog != null) {
618 mProgressDialog.dismiss();
619 }
620 mWallpaperChangedNotifier.unregisterListener(mWallpaperChangedListener);
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700621 if (mAppStatusListener != null) {
622 mPackageStatusNotifier.removeListener(mAppStatusListener);
623 }
Chuck Liao69630f12020-03-05 19:01:25 +0800624 mWallpaperSetter.cleanUp();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800625 }
626
627 @Override
628 public void retryStartRotation(@NetworkPreference int networkPreference) {
629 startRotation(networkPreference);
630 }
631
632 public void setCurrentWallpaperBottomSheetPresenter(
633 CurrentWallpaperBottomSheetPresenter presenter) {
634 mCurrentWallpaperBottomSheetPresenter = presenter;
635 }
636
637 public void setWallpapersUiContainer(WallpapersUiContainer uiContainer) {
638 mWallpapersUiContainer = uiContainer;
639 }
640
641 /**
642 * Enable a test mode of operation -- in which certain UI features are disabled to allow for
643 * UI tests to run correctly. Works around issue in ProgressDialog currently where the dialog
644 * constantly keeps the UI thread alive and blocks a test forever.
645 *
646 * @param testingMode
647 */
648 void setTestingMode(boolean testingMode) {
649 mTestingMode = testingMode;
650 }
651
652 /**
653 * Asynchronously fetches the refreshed rotation initialization state that is up to date with the
654 * state of the user's device and binds the state of the current category's rotation to the "start
655 * rotation" tile.
656 */
Santiago Etchebehere8cad0dd2019-10-17 10:52:39 -0700657 private void refreshRotationHolder(RotationHolder rotationHolder) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800658 mWallpaperRotationInitializer.fetchRotationInitializationState(getContext(),
Santiago Etchebehere8cad0dd2019-10-17 10:52:39 -0700659 rotationState -> {
660 // Update the UI state of the "start rotation" tile displayed on screen.
661 // Do this in a Handler so it is scheduled at the end of the message queue.
662 // This is necessary to ensure we do not remove or add data from the adapter
663 // while the layout is still being computed. RecyclerView documentation
664 // therefore recommends performing such changes in a Handler.
665 new Handler().post(() -> {
666 // A config change may have destroyed the activity since the refresh
667 // started, so check for that to avoid an NPE.
668 if (getActivity() == null) {
669 return;
670 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800671
Santiago Etchebehere8cad0dd2019-10-17 10:52:39 -0700672 rotationHolder.bindRotationInitializationState(rotationState);
673 });
Jon Miranda16ea1b12017-12-12 14:52:48 -0800674 });
675 }
676
677 @Override
678 public void startRotation(@NetworkPreference final int networkPreference) {
679 if (!isRotationEnabled()) {
680 Log.e(TAG, "Rotation is not enabled for this category " + mCategory.getTitle());
681 return;
682 }
683
684 // ProgressDialog endlessly updates the UI thread, keeping it from going idle which therefore
685 // causes Espresso to hang once the dialog is shown.
686 if (mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE && !mTestingMode) {
687 int themeResId;
688 if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
689 themeResId = R.style.ProgressDialogThemePreL;
690 } else {
691 themeResId = R.style.LightDialogTheme;
692 }
693 mProgressDialog = new ProgressDialog(getActivity(), themeResId);
694
695 mProgressDialog.setTitle(PROGRESS_DIALOG_NO_TITLE);
696 mProgressDialog.setMessage(
697 getResources().getString(R.string.start_rotation_progress_message));
698 mProgressDialog.setIndeterminate(PROGRESS_DIALOG_INDETERMINATE);
699 mProgressDialog.show();
700 }
701
702 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
703 mAdapter.mPendingSelectedAdapterPosition = SPECIAL_FIXED_TILE_ADAPTER_POSITION;
704 }
705
706 final Context appContext = getActivity().getApplicationContext();
707
708 mWallpaperRotationInitializer.setFirstWallpaperInRotation(
709 appContext,
710 networkPreference,
711 new Listener() {
712 @Override
713 public void onFirstWallpaperInRotationSet() {
714 if (mProgressDialog != null) {
715 mProgressDialog.dismiss();
716 }
717
718 // The fragment may be detached from its containing activity if the user exits the
719 // app before the first wallpaper image in rotation finishes downloading.
720 Activity activity = getActivity();
721
Jon Miranda16ea1b12017-12-12 14:52:48 -0800722
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700723 if (mWallpaperRotationInitializer.startRotation(appContext)) {
724 if (activity != null
725 && mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
726 try {
727 Toast.makeText(getActivity(),
728 R.string.wallpaper_set_successfully_message,
729 Toast.LENGTH_SHORT).show();
730 } catch (NotFoundException e) {
731 Log.e(TAG, "Could not show toast " + e);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800732 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800733
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700734 activity.setResult(Activity.RESULT_OK);
735 activity.finish();
736 } else if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
737 mAdapter.updateSelectedTile(SPECIAL_FIXED_TILE_ADAPTER_POSITION);
738 }
739 } else { // Failed to start rotation.
740 showStartRotationErrorDialog(networkPreference);
741
742 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
743 DesktopRotationHolder rotationViewHolder =
744 (DesktopRotationHolder)
745 mImageGrid.findViewHolderForAdapterPosition(
746 SPECIAL_FIXED_TILE_ADAPTER_POSITION);
747 rotationViewHolder.setSelectionState(
748 SelectableHolder.SELECTION_STATE_DESELECTED);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800749 }
750 }
751 }
752
753 @Override
754 public void onError() {
755 if (mProgressDialog != null) {
756 mProgressDialog.dismiss();
757 }
758
759 showStartRotationErrorDialog(networkPreference);
760
761 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
762 DesktopRotationHolder rotationViewHolder =
763 (DesktopRotationHolder) mImageGrid.findViewHolderForAdapterPosition(
764 SPECIAL_FIXED_TILE_ADAPTER_POSITION);
765 rotationViewHolder.setSelectionState(SelectableHolder.SELECTION_STATE_DESELECTED);
766 }
767 }
768 });
769 }
770
771 private void showStartRotationErrorDialog(@NetworkPreference int networkPreference) {
772 BaseActivity activity = (BaseActivity) getActivity();
773 if (activity != null) {
774 StartRotationErrorDialogFragment startRotationErrorDialogFragment =
775 StartRotationErrorDialogFragment.newInstance(networkPreference);
776 startRotationErrorDialogFragment.setTargetFragment(
777 IndividualPickerFragment.this, UNUSED_REQUEST_CODE);
778
779 if (activity.isSafeToCommitFragmentTransaction()) {
780 startRotationErrorDialogFragment.show(
781 getFragmentManager(), TAG_START_ROTATION_ERROR_DIALOG);
782 } else {
783 mStagedStartRotationErrorDialogFragment = startRotationErrorDialogFragment;
784 }
785 }
786 }
787
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800788 int getNumColumns() {
Ching-Sung Li9df77a32019-07-10 11:45:30 +0800789 Activity activity = getActivity();
790 return activity == null ? 0 : TileSizeCalculator.getNumIndividualColumns(activity);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800791 }
792
793 /**
794 * Returns whether rotation is enabled for this category.
795 */
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800796 boolean isRotationEnabled() {
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700797 return mWallpaperRotationInitializer != null;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800798 }
799
800 @Override
801 public void onCurrentWallpaperRefreshed() {
802 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(true);
803 }
804
Chuck Liao69630f12020-03-05 19:01:25 +0800805
806 @Override
807 public void onSet(int destination) {
808 if (mSelectedWallpaperInfo == null) {
809 Log.e(TAG, "Unable to set wallpaper since the selected wallpaper info is null");
810 return;
811 }
812
“Chuckffd832c2020-03-22 02:15:58 +0800813 mWallpaperPersister.setWallpaperInfoInPreview(mSelectedWallpaperInfo);
Chuck Liao69630f12020-03-05 19:01:25 +0800814 if (mSelectedWallpaperInfo instanceof LiveWallpaperInfo) {
815 mWallpaperSetter.setCurrentWallpaper(getActivity(), mSelectedWallpaperInfo, null,
816 destination, 0, null, mSetWallpaperCallback);
817 } else {
818 mWallpaperSetter.setCurrentWallpaper(
819 getActivity(), mSelectedWallpaperInfo, destination, mSetWallpaperCallback);
820 }
“Chuck7ef99722020-03-22 04:34:03 +0800821 onWallpaperDestinationSet(destination);
Chuck Liao69630f12020-03-05 19:01:25 +0800822 }
823
824 private WallpaperPersister.SetWallpaperCallback mSetWallpaperCallback =
825 new WallpaperPersister.SetWallpaperCallback() {
826 @Override
“Chuckffd832c2020-03-22 02:15:58 +0800827 public void onSuccess(WallpaperInfo wallpaperInfo) {
Chuck Liao69630f12020-03-05 19:01:25 +0800828 // TODO(b/150913705): Show the snack bar.
829 mBottomActionBar.enableActions();
Chuck Liao11f4a762020-04-08 13:24:43 +0800830 refreshAppliedWallpaper();
“Chuckffd832c2020-03-22 02:15:58 +0800831
832 mWallpaperPersister.onLiveWallpaperSet();
Chuck Liao69630f12020-03-05 19:01:25 +0800833 }
834
835 @Override
836 public void onError(@Nullable Throwable throwable) {
837 Log.e(TAG, "Can't apply the wallpaper.");
838 mBottomActionBar.enableActions();
839 }
840 };
841
842 @Override
843 public void onDialogDismissed(boolean withItemSelected) {
844 if (!withItemSelected) {
845 mBottomActionBar.enableActions();
846 }
847 }
848
Jon Miranda16ea1b12017-12-12 14:52:48 -0800849 /**
850 * Shows a "set wallpaper" error dialog with a failure message and button to try again.
851 */
852 private void showSetWallpaperErrorDialog() {
853 SetWallpaperErrorDialogFragment dialogFragment = SetWallpaperErrorDialogFragment.newInstance(
854 R.string.set_wallpaper_error_message, WallpaperPersister.DEST_BOTH);
855 dialogFragment.setTargetFragment(this, UNUSED_REQUEST_CODE);
856
857 if (((BaseActivity) getActivity()).isSafeToCommitFragmentTransaction()) {
858 dialogFragment.show(getFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
859 } else {
860 mStagedSetWallpaperErrorDialogFragment = dialogFragment;
861 }
862 }
863
Chuck Liao69630f12020-03-05 19:01:25 +0800864 private void updateBottomActions(boolean hasWallpaperSelected) {
865 mBottomActionBar.showActions(
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800866 hasWallpaperSelected ? EnumSet.of(APPLY, INFORMATION) : EnumSet.of(ROTATION));
Chuck Liao69630f12020-03-05 19:01:25 +0800867 mBottomActionBar.hideActions(
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800868 hasWallpaperSelected ? EnumSet.of(ROTATION) : EnumSet.of(APPLY, INFORMATION));
Chuck Liao69630f12020-03-05 19:01:25 +0800869 }
870
871 private void updateThumbnail(WallpaperInfo selectedWallpaperInfo) {
Chuck Liaof40063f2020-03-03 18:35:24 +0800872 ThumbnailUpdater thumbnailUpdater = (ThumbnailUpdater) getParentFragment();
Chuck Liao69630f12020-03-05 19:01:25 +0800873 if (thumbnailUpdater == null) {
874 return;
875 }
876
877 if (selectedWallpaperInfo != null) {
878 thumbnailUpdater.updateThumbnail(selectedWallpaperInfo);
879 } else {
880 thumbnailUpdater.restoreThumbnails();
Chuck Liaof40063f2020-03-03 18:35:24 +0800881 }
882 }
883
“Chuck7ef99722020-03-22 04:34:03 +0800884 private void onWallpaperDestinationSet(int destination) {
885 WallpaperDestinationCallback wallpaperDestinationCallback =
886 (WallpaperDestinationCallback) getParentFragment();
887 if (wallpaperDestinationCallback == null) {
888 return;
889 }
890
891 wallpaperDestinationCallback.onDestinationSet(destination);
892 }
893
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800894 private void onWallpaperSelected(@Nullable WallpaperInfo newSelectedWallpaperInfo) {
895 if (mSelectedWallpaperInfo == newSelectedWallpaperInfo) {
“Chuckffd832c2020-03-22 02:15:58 +0800896 return;
897 }
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800898 // Update current wallpaper.
“Chuckffd832c2020-03-22 02:15:58 +0800899 updateActivatedStatus(mSelectedWallpaperInfo == null
900 ? mAppliedWallpaperInfo : mSelectedWallpaperInfo, false);
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800901 // Update new selected wallpaper.
902 updateActivatedStatus(newSelectedWallpaperInfo == null
903 ? mAppliedWallpaperInfo : newSelectedWallpaperInfo, true);
904
905 mSelectedWallpaperInfo = newSelectedWallpaperInfo;
906 updateBottomActions(mSelectedWallpaperInfo != null);
907 updateThumbnail(mSelectedWallpaperInfo);
908 // Populate wallpaper info to bottom sheet page.
909 if (mSelectedWallpaperInfo != null) {
910 mBottomActionBar.populateInfoPage(
911 mSelectedWallpaperInfo.getAttributions(getContext()),
912 shouldShowMetadataInPreview(mSelectedWallpaperInfo));
913 }
Chuck Liaof40063f2020-03-03 18:35:24 +0800914 }
915
Chuck Liao6a37a1c2020-03-07 03:39:43 +0800916 private void updateActivatedStatus(WallpaperInfo wallpaperInfo, boolean isActivated) {
“Chuckffd832c2020-03-22 02:15:58 +0800917 if (wallpaperInfo == null) {
918 return;
919 }
Chuck Liao6a37a1c2020-03-07 03:39:43 +0800920 int index = mWallpapers.indexOf(wallpaperInfo);
921 index = (isRotationEnabled() || mCategory.supportsCustomPhotos())
922 ? index + 1 : index;
923 ViewHolder holder = mImageGrid.findViewHolderForAdapterPosition(index);
924 if (holder != null) {
925 holder.itemView.setActivated(isActivated);
926 } else {
927 // Item is not visible, make sure the item is re-bound when it becomes visible.
928 mAdapter.notifyItemChanged(index);
929 }
930 }
931
“Chuckffd832c2020-03-22 02:15:58 +0800932 private void updateAppliedStatus(WallpaperInfo wallpaperInfo, boolean isApplied) {
933 if (wallpaperInfo == null) {
934 return;
935 }
936 int index = mWallpapers.indexOf(wallpaperInfo);
937 index = (isRotationEnabled() || mCategory.supportsCustomPhotos())
938 ? index + 1 : index;
939 ViewHolder holder = mImageGrid.findViewHolderForAdapterPosition(index);
940 if (holder != null) {
941 holder.itemView.findViewById(R.id.check_circle)
942 .setVisibility(isApplied ? View.VISIBLE : View.GONE);
943 } else {
944 // Item is not visible, make sure the item is re-bound when it becomes visible.
945 mAdapter.notifyItemChanged(index);
946 }
947 }
948
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800949 private static boolean shouldShowMetadataInPreview(WallpaperInfo wallpaperInfo) {
950 android.app.WallpaperInfo wallpaperComponent = wallpaperInfo.getWallpaperComponent();
951 return wallpaperComponent == null || wallpaperComponent.getShowMetadataInPreview();
952 }
953
Chuck Liao11f4a762020-04-08 13:24:43 +0800954 private void refreshAppliedWallpaper() {
955 // Clear the check mark and blue border(if it shows) of the old applied wallpaper.
956 showCheckMarkAndBorderForAppliedWallpaper(false);
957
958 // Update to the new applied wallpaper.
959 String appliedWallpaperId = getAppliedWallpaperId();
960 Optional<WallpaperInfo> wallpaperInfoOptional = mWallpapers
961 .stream()
962 .filter(wallpaper -> wallpaper.getWallpaperId() != null)
963 .filter(wallpaper -> wallpaper.getWallpaperId().equals(appliedWallpaperId))
964 .findFirst();
965 mAppliedWallpaperInfo = wallpaperInfoOptional.orElse(null);
966
967 // Set the check mark and blue border(if user doesn't select) of the new applied wallpaper.
968 showCheckMarkAndBorderForAppliedWallpaper(true);
969 }
970
971 private String getAppliedWallpaperId() {
972 WallpaperPreferences prefs =
973 InjectorProvider.getInjector().getPreferences(getContext());
974 android.app.WallpaperInfo wallpaperInfo = mWallpaperManager.getWallpaperInfo();
975 boolean isDestinationBoth =
976 mWallpaperManager.getWallpaperId(WallpaperManager.FLAG_LOCK) < 0;
977
978 if (isDestinationBoth || mWallpaperDestination == WallpaperPersister.DEST_HOME_SCREEN) {
979 return wallpaperInfo != null
980 ? wallpaperInfo.getServiceName() : prefs.getHomeWallpaperRemoteId();
981 } else {
982 return prefs.getLockWallpaperRemoteId();
983 }
984 }
985
986 private void showCheckMarkAndBorderForAppliedWallpaper(boolean show) {
987 updateAppliedStatus(mAppliedWallpaperInfo, show);
988 if (mSelectedWallpaperInfo == null) {
989 updateActivatedStatus(mAppliedWallpaperInfo, show);
990 }
991 }
992
Jon Miranda16ea1b12017-12-12 14:52:48 -0800993 /**
994 * ViewHolder subclass for "daily refresh" tile in the RecyclerView, only shown if rotation is
995 * enabled for this category.
996 */
997 private class RotationHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
998
Chuck Liaoba401232020-03-13 20:11:04 +0800999 private CardView mTileLayout;
Jon Miranda16ea1b12017-12-12 14:52:48 -08001000 private TextView mRotationMessage;
1001 private TextView mRotationTitle;
1002 private ImageView mRefreshIcon;
1003
1004 RotationHolder(View itemView) {
1005 super(itemView);
1006 itemView.setOnClickListener(this);
1007
Santiago Etchebehere8cad0dd2019-10-17 10:52:39 -07001008 mTileLayout = itemView.findViewById(R.id.daily_refresh);
1009 mRotationMessage = itemView.findViewById(R.id.rotation_tile_message);
1010 mRotationTitle = itemView.findViewById(R.id.rotation_tile_title);
1011 mRefreshIcon = itemView.findViewById(R.id.rotation_tile_refresh_icon);
Jon Miranda16ea1b12017-12-12 14:52:48 -08001012 mTileLayout.getLayoutParams().height = mTileSizePx.y;
1013
1014 // If the feature flag for "dynamic start rotation tile" is not enabled, fall back to the
1015 // static UI with a blue accent color background and "Tap to turn on" text.
1016 if (!Flags.dynamicStartRotationTileEnabled) {
1017 mTileLayout.setBackgroundColor(
1018 getResources().getColor(R.color.rotation_tile_enabled_background_color));
1019 mRotationMessage.setText(R.string.daily_refresh_tile_subtitle);
1020 mRotationTitle.setTextColor(
1021 getResources().getColor(R.color.rotation_tile_enabled_title_text_color));
1022 mRotationMessage.setTextColor(
1023 getResources().getColor(R.color.rotation_tile_enabled_subtitle_text_color));
1024 mRefreshIcon.setColorFilter(
Santiago Etchebehere8cad0dd2019-10-17 10:52:39 -07001025 getResources().getColor(R.color.rotation_tile_enabled_refresh_icon_color),
1026 Mode.SRC_IN);
Jon Miranda16ea1b12017-12-12 14:52:48 -08001027 return;
1028 }
1029
1030 // Initialize the state of the "start rotation" tile (i.e., whether it is gray or blue to
1031 // indicate if rotation is turned on for the current category) with last-known rotation state
1032 // that could be stale. The last-known rotation state is correct in most cases and is a good
1033 // starting point but may not be accurate if the user set a wallpaper through a 3rd party app
1034 // while this app was paused.
1035 int rotationState = mWallpaperRotationInitializer.getRotationInitializationStateDirty(
1036 getContext());
1037 bindRotationInitializationState(rotationState);
1038 }
1039
1040 @Override
1041 public void onClick(View v) {
Samuel Fufa03bc6ac2019-09-19 12:01:50 -07001042 DialogFragment startRotationDialogFragment = new StartRotationDialogFragment();
Jon Miranda16ea1b12017-12-12 14:52:48 -08001043 startRotationDialogFragment.setTargetFragment(
1044 IndividualPickerFragment.this, UNUSED_REQUEST_CODE);
1045 startRotationDialogFragment.show(getFragmentManager(), TAG_START_ROTATION_DIALOG);
1046 }
1047
1048 /**
1049 * Binds the provided rotation initialization state to the RotationHolder and updates the tile's
1050 * UI to be in sync with the state (i.e., message and color appropriately reflect the state to
1051 * the user).
1052 */
1053 void bindRotationInitializationState(@RotationInitializationState int rotationState) {
1054 int newBackgroundColor =
1055 (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
1056 ? getResources().getColor(R.color.rotation_tile_not_enabled_background_color)
1057 : getResources().getColor(R.color.rotation_tile_enabled_background_color);
1058 int newTitleTextColor =
1059 (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
1060 ? getResources().getColor(R.color.rotation_tile_not_enabled_title_text_color)
1061 : getResources().getColor(R.color.rotation_tile_enabled_title_text_color);
1062 int newSubtitleTextColor =
1063 (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
1064 ? getResources().getColor(R.color.rotation_tile_not_enabled_subtitle_text_color)
1065 : getResources().getColor(R.color.rotation_tile_enabled_subtitle_text_color);
1066 int newRefreshIconColor =
1067 (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
1068 ? getResources().getColor(R.color.rotation_tile_not_enabled_refresh_icon_color)
1069 : getResources().getColor(R.color.rotation_tile_enabled_refresh_icon_color);
1070
Chuck Liaoba401232020-03-13 20:11:04 +08001071 mTileLayout.setCardBackgroundColor(newBackgroundColor);
Jon Miranda16ea1b12017-12-12 14:52:48 -08001072 mRotationTitle.setTextColor(newTitleTextColor);
1073 mRotationMessage.setText(getResIdForRotationState(rotationState));
1074 mRotationMessage.setTextColor(newSubtitleTextColor);
1075 mRefreshIcon.setColorFilter(newRefreshIconColor, Mode.SRC_IN);
1076 }
1077 }
1078
1079 /**
1080 * RecyclerView Adapter subclass for the wallpaper tiles in the RecyclerView.
1081 */
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +08001082 class IndividualAdapter extends RecyclerView.Adapter<ViewHolder> {
1083 static final int ITEM_VIEW_TYPE_ROTATION = 1;
1084 static final int ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER = 2;
1085 static final int ITEM_VIEW_TYPE_MY_PHOTOS = 3;
Jon Miranda16ea1b12017-12-12 14:52:48 -08001086
1087 private final List<WallpaperInfo> mWallpapers;
1088
1089 private int mPendingSelectedAdapterPosition;
1090 private int mSelectedAdapterPosition;
1091
1092 IndividualAdapter(List<WallpaperInfo> wallpapers) {
1093 mWallpapers = wallpapers;
1094 mPendingSelectedAdapterPosition = -1;
1095 mSelectedAdapterPosition = -1;
1096 }
1097
1098 @Override
1099 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
1100 switch (viewType) {
1101 case ITEM_VIEW_TYPE_ROTATION:
1102 return createRotationHolder(parent);
1103 case ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER:
1104 return createIndividualHolder(parent);
1105 case ITEM_VIEW_TYPE_MY_PHOTOS:
1106 return createMyPhotosHolder(parent);
1107 default:
1108 Log.e(TAG, "Unsupported viewType " + viewType + " in IndividualAdapter");
1109 return null;
1110 }
1111 }
1112
1113 @Override
1114 public int getItemViewType(int position) {
1115 if (isRotationEnabled() && position == SPECIAL_FIXED_TILE_ADAPTER_POSITION) {
1116 return ITEM_VIEW_TYPE_ROTATION;
1117 }
1118
1119 // A category cannot have both a "start rotation" tile and a "my photos" tile.
1120 if (mCategory.supportsCustomPhotos()
1121 && !isRotationEnabled()
1122 && position == SPECIAL_FIXED_TILE_ADAPTER_POSITION) {
1123 return ITEM_VIEW_TYPE_MY_PHOTOS;
1124 }
1125
1126 return ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER;
1127 }
1128
1129 @Override
1130 public void onBindViewHolder(ViewHolder holder, int position) {
1131 int viewType = getItemViewType(position);
1132
1133 switch (viewType) {
1134 case ITEM_VIEW_TYPE_ROTATION:
1135 onBindRotationHolder(holder, position);
1136 break;
1137 case ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER:
1138 onBindIndividualHolder(holder, position);
1139 break;
1140 case ITEM_VIEW_TYPE_MY_PHOTOS:
1141 ((MyPhotosViewHolder) holder).bind();
1142 break;
1143 default:
1144 Log.e(TAG, "Unsupported viewType " + viewType + " in IndividualAdapter");
1145 }
1146 }
1147
1148 @Override
1149 public int getItemCount() {
1150 return (isRotationEnabled() || mCategory.supportsCustomPhotos())
1151 ? mWallpapers.size() + 1
1152 : mWallpapers.size();
1153 }
1154
1155 private ViewHolder createRotationHolder(ViewGroup parent) {
1156 LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
1157 View view;
1158
1159 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
1160 view = layoutInflater.inflate(R.layout.grid_item_rotation_desktop, parent, false);
1161 SelectionAnimator selectionAnimator =
1162 new CheckmarkSelectionAnimator(getActivity(), view);
1163 return new DesktopRotationHolder(
1164 getActivity(), mTileSizePx.y, view, selectionAnimator,
1165 IndividualPickerFragment.this);
1166 } else { // MOBILE
1167 view = layoutInflater.inflate(R.layout.grid_item_rotation, parent, false);
1168 return new RotationHolder(view);
1169 }
1170 }
1171
1172 private ViewHolder createIndividualHolder(ViewGroup parent) {
1173 LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
1174 View view = layoutInflater.inflate(R.layout.grid_item_image, parent, false);
Chuck Liao6a37a1c2020-03-07 03:39:43 +08001175 if (!TEMP_BOTTOM_ACTION_BAR_FEATURE) {
1176 view.findViewById(R.id.tile).setPadding(0, 0, 0, 0);
1177 }
Jon Miranda16ea1b12017-12-12 14:52:48 -08001178
1179 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
1180 SelectionAnimator selectionAnimator =
1181 new CheckmarkSelectionAnimator(getActivity(), view);
1182 return new SetIndividualHolder(
1183 getActivity(), mTileSizePx.y, view,
1184 selectionAnimator,
1185 new OnSetListener() {
1186 @Override
1187 public void onPendingWallpaperSet(int adapterPosition) {
1188 // Deselect and hide loading indicator for any previously pending tile.
1189 if (mPendingSelectedAdapterPosition != -1) {
1190 ViewHolder oldViewHolder = mImageGrid.findViewHolderForAdapterPosition(
1191 mPendingSelectedAdapterPosition);
1192 if (oldViewHolder instanceof SelectableHolder) {
1193 ((SelectableHolder) oldViewHolder).setSelectionState(
1194 SelectableHolder.SELECTION_STATE_DESELECTED);
1195 }
1196 }
1197
1198 if (mSelectedAdapterPosition != -1) {
1199 ViewHolder oldViewHolder = mImageGrid.findViewHolderForAdapterPosition(
1200 mSelectedAdapterPosition);
1201 if (oldViewHolder instanceof SelectableHolder) {
1202 ((SelectableHolder) oldViewHolder).setSelectionState(
1203 SelectableHolder.SELECTION_STATE_DESELECTED);
1204 }
1205 }
1206
1207 mPendingSelectedAdapterPosition = adapterPosition;
1208 }
1209
1210 @Override
1211 public void onWallpaperSet(int adapterPosition) {
1212 // No-op -- UI handles a new wallpaper being set by reacting to the
1213 // WallpaperChangedNotifier.
1214 }
1215
1216 @Override
1217 public void onWallpaperSetFailed(SetIndividualHolder holder) {
1218 showSetWallpaperErrorDialog();
1219 mPendingSetIndividualHolder = holder;
1220 }
1221 });
1222 } else { // MOBILE
Chuck Liao3d1a51c2020-02-17 18:29:34 +08001223 return new PreviewIndividualHolder(getActivity(), mTileSizePx.y, view);
Jon Miranda16ea1b12017-12-12 14:52:48 -08001224 }
1225 }
1226
1227 private ViewHolder createMyPhotosHolder(ViewGroup parent) {
1228 LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
1229 View view = layoutInflater.inflate(R.layout.grid_item_my_photos, parent, false);
1230
Santiago Etchebeherefab49612019-01-15 12:22:42 -08001231 return new MyPhotosViewHolder(getActivity(),
1232 ((MyPhotosStarterProvider) getActivity()).getMyPhotosStarter(),
1233 mTileSizePx.y, view);
Jon Miranda16ea1b12017-12-12 14:52:48 -08001234 }
1235
1236 /**
1237 * Marks the tile at the given position as selected with a visual indication. Also updates the
1238 * "currently selected" BottomSheet to reflect the newly selected tile.
1239 */
1240 private void updateSelectedTile(int newlySelectedPosition) {
1241 // Prevent multiple spinners from appearing with a user tapping several tiles in rapid
1242 // succession.
1243 if (mPendingSelectedAdapterPosition == mSelectedAdapterPosition) {
1244 return;
1245 }
1246
1247 if (mCurrentWallpaperBottomSheetPresenter != null) {
1248 mCurrentWallpaperBottomSheetPresenter.refreshCurrentWallpapers(
1249 IndividualPickerFragment.this);
1250
1251 if (mCurrentWallpaperBottomSheetExpandedRunnable != null) {
1252 mHandler.removeCallbacks(mCurrentWallpaperBottomSheetExpandedRunnable);
1253 }
1254 mCurrentWallpaperBottomSheetExpandedRunnable = new Runnable() {
1255 @Override
1256 public void run() {
1257 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(true);
1258 }
1259 };
1260 mHandler.postDelayed(mCurrentWallpaperBottomSheetExpandedRunnable, 100);
1261 }
1262
1263 // User may have switched to another category, thus detaching this fragment, so check here.
1264 // NOTE: We do this check after updating the current wallpaper BottomSheet so that the update
1265 // still occurs in the UI after the user selects that other category.
1266 if (getActivity() == null) {
1267 return;
1268 }
1269
1270 // Update the newly selected wallpaper ViewHolder and the old one so that if
1271 // selection UI state applies (desktop UI), it is updated.
1272 if (mSelectedAdapterPosition >= 0) {
1273 ViewHolder oldViewHolder = mImageGrid.findViewHolderForAdapterPosition(
1274 mSelectedAdapterPosition);
1275 if (oldViewHolder instanceof SelectableHolder) {
1276 ((SelectableHolder) oldViewHolder).setSelectionState(
1277 SelectableHolder.SELECTION_STATE_DESELECTED);
1278 }
1279 }
1280
1281 // Animate selection of newly selected tile.
1282 ViewHolder newViewHolder = mImageGrid
1283 .findViewHolderForAdapterPosition(newlySelectedPosition);
1284 if (newViewHolder instanceof SelectableHolder) {
1285 ((SelectableHolder) newViewHolder).setSelectionState(
1286 SelectableHolder.SELECTION_STATE_SELECTED);
1287 }
1288
1289 mSelectedAdapterPosition = newlySelectedPosition;
1290
1291 // If the tile was in the last row of the grid, add space below it so the user can scroll down
1292 // and up to see the BottomSheet without it fully overlapping the newly selected tile.
1293 int spanCount = ((GridLayoutManager) mImageGrid.getLayoutManager()).getSpanCount();
1294 int numRows = (int) Math.ceil((float) getItemCount() / spanCount);
1295 int rowOfNewlySelectedTile = newlySelectedPosition / spanCount;
1296 boolean isInLastRow = rowOfNewlySelectedTile == numRows - 1;
1297
1298 updateImageGridPadding(isInLastRow /* addExtraBottomSpace */);
1299 }
1300
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +08001301 void onBindRotationHolder(ViewHolder holder, int position) {
Jon Miranda16ea1b12017-12-12 14:52:48 -08001302 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
1303 String collectionId = mCategory.getCollectionId();
1304 ((DesktopRotationHolder) holder).bind(collectionId);
1305
1306 if (mWallpaperPreferences.getWallpaperPresentationMode()
1307 == WallpaperPreferences.PRESENTATION_MODE_ROTATING
1308 && collectionId.equals(mWallpaperPreferences.getHomeWallpaperCollectionId())) {
1309 mSelectedAdapterPosition = position;
1310 }
1311
1312 if (!mWasUpdateRunnableRun && !mWallpapers.isEmpty()) {
1313 updateDesktopDailyRotationThumbnail((DesktopRotationHolder) holder);
1314 mWasUpdateRunnableRun = true;
1315 }
1316 }
1317 }
1318
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +08001319 void onBindIndividualHolder(ViewHolder holder, int position) {
Jon Miranda16ea1b12017-12-12 14:52:48 -08001320 int wallpaperIndex = (isRotationEnabled() || mCategory.supportsCustomPhotos())
1321 ? position - 1 : position;
1322 WallpaperInfo wallpaper = mWallpapers.get(wallpaperIndex);
1323 ((IndividualHolder) holder).bindWallpaper(wallpaper);
“Chuck7ef99722020-03-22 04:34:03 +08001324 String appliedWallpaperId = getAppliedWallpaperId();
“Chuckffd832c2020-03-22 02:15:58 +08001325 boolean isWallpaperApplied = wallpaper.getWallpaperId().equals(appliedWallpaperId);
1326 boolean isWallpaperSelected = wallpaper.equals(mSelectedWallpaperInfo);
1327 boolean hasUserSelectedWallpaper = mSelectedWallpaperInfo != null;
Jon Miranda16ea1b12017-12-12 14:52:48 -08001328
“Chuckffd832c2020-03-22 02:15:58 +08001329 if (isWallpaperApplied) {
Jon Miranda16ea1b12017-12-12 14:52:48 -08001330 mSelectedAdapterPosition = position;
“Chuckffd832c2020-03-22 02:15:58 +08001331 mAppliedWallpaperInfo = wallpaper;
Jon Miranda16ea1b12017-12-12 14:52:48 -08001332 }
Chuck Liaof40063f2020-03-03 18:35:24 +08001333
1334 if (TEMP_BOTTOM_ACTION_BAR_FEATURE) {
“Chuckffd832c2020-03-22 02:15:58 +08001335 holder.itemView.setActivated(
1336 (isWallpaperApplied && !hasUserSelectedWallpaper) || isWallpaperSelected);
1337 holder.itemView.findViewById(R.id.check_circle).setVisibility(
1338 isWallpaperApplied ? View.VISIBLE : View.GONE);
Chuck Liao69630f12020-03-05 19:01:25 +08001339 holder.itemView.findViewById(R.id.tile).setOnClickListener(
1340 view -> onWallpaperSelected(wallpaper));
Chuck Liaof40063f2020-03-03 18:35:24 +08001341 }
Jon Miranda16ea1b12017-12-12 14:52:48 -08001342 }
1343 }
Chuck Liaoba401232020-03-13 20:11:04 +08001344
1345 private class GridPaddingDecoration extends RecyclerView.ItemDecoration {
1346
1347 private int mPadding;
1348
1349 GridPaddingDecoration(int padding) {
1350 mPadding = padding;
1351 }
1352
1353 @Override
1354 public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
1355 RecyclerView.State state) {
1356 int position = parent.getChildAdapterPosition(view);
1357 if (position >= 0) {
1358 outRect.left = mPadding;
1359 outRect.right = mPadding;
1360 }
1361 }
1362 }
Jon Miranda16ea1b12017-12-12 14:52:48 -08001363}