blob: 22309b653885350896949f402b2540e27ba7f6a0 [file] [log] [blame]
Jon Miranda16ea1b12017-12-12 14:52:48 -08001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.wallpaper.picker.individual;
17
18import android.app.Activity;
19import android.app.ProgressDialog;
20import android.content.Context;
21import android.content.res.Resources.NotFoundException;
22import android.graphics.Point;
23import android.graphics.PorterDuff.Mode;
24import android.os.Build.VERSION;
25import android.os.Build.VERSION_CODES;
26import android.os.Bundle;
27import android.os.Handler;
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -070028import android.service.wallpaper.WallpaperService;
Jon Miranda16ea1b12017-12-12 14:52:48 -080029import android.util.Log;
30import android.view.LayoutInflater;
31import android.view.View;
32import android.view.ViewGroup;
33import android.widget.FrameLayout;
34import android.widget.ImageView;
35import android.widget.TextView;
36import android.widget.Toast;
37
Santiago Etchebeherefab49612019-01-15 12:22:42 -080038import androidx.fragment.app.DialogFragment;
39import androidx.fragment.app.Fragment;
40import androidx.recyclerview.widget.GridLayoutManager;
41import androidx.recyclerview.widget.RecyclerView;
42import androidx.recyclerview.widget.RecyclerView.OnScrollListener;
43import androidx.recyclerview.widget.RecyclerView.ViewHolder;
44
Jon Miranda16ea1b12017-12-12 14:52:48 -080045import com.android.wallpaper.R;
46import com.android.wallpaper.asset.Asset;
47import com.android.wallpaper.asset.Asset.DrawableLoadedListener;
48import com.android.wallpaper.config.Flags;
49import com.android.wallpaper.model.WallpaperCategory;
50import com.android.wallpaper.model.WallpaperInfo;
51import com.android.wallpaper.model.WallpaperReceiver;
52import com.android.wallpaper.model.WallpaperRotationInitializer;
53import com.android.wallpaper.model.WallpaperRotationInitializer.Listener;
54import com.android.wallpaper.model.WallpaperRotationInitializer.NetworkPreference;
55import com.android.wallpaper.model.WallpaperRotationInitializer.RotationInitializationState;
56import com.android.wallpaper.model.WallpaperRotationInitializer.RotationStateListener;
57import com.android.wallpaper.module.FormFactorChecker;
58import com.android.wallpaper.module.FormFactorChecker.FormFactor;
59import com.android.wallpaper.module.Injector;
60import com.android.wallpaper.module.InjectorProvider;
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -070061import com.android.wallpaper.module.PackageStatusNotifier;
Jon Miranda16ea1b12017-12-12 14:52:48 -080062import com.android.wallpaper.module.RotatingWallpaperComponentChecker;
63import com.android.wallpaper.module.WallpaperChangedNotifier;
64import com.android.wallpaper.module.WallpaperPersister;
65import com.android.wallpaper.module.WallpaperPersister.Destination;
66import com.android.wallpaper.module.WallpaperPreferences;
67import com.android.wallpaper.picker.BaseActivity;
68import com.android.wallpaper.picker.CurrentWallpaperBottomSheetPresenter;
Santiago Etchebeherefab49612019-01-15 12:22:42 -080069import com.android.wallpaper.picker.MyPhotosStarter.MyPhotosStarterProvider;
Jon Miranda16ea1b12017-12-12 14:52:48 -080070import com.android.wallpaper.picker.RotationStarter;
71import com.android.wallpaper.picker.SetWallpaperErrorDialogFragment;
72import com.android.wallpaper.picker.StartRotationDialogFragment;
73import com.android.wallpaper.picker.StartRotationErrorDialogFragment;
74import com.android.wallpaper.picker.WallpapersUiContainer;
75import com.android.wallpaper.picker.individual.SetIndividualHolder.OnSetListener;
76import com.android.wallpaper.util.DiskBasedLogger;
77import com.android.wallpaper.util.TileSizeCalculator;
78import com.android.wallpaper.widget.GridMarginDecoration;
Sunny Goyal8600a3f2018-08-15 12:48:01 -070079
Jon Miranda16ea1b12017-12-12 14:52:48 -080080import com.bumptech.glide.Glide;
81import com.bumptech.glide.MemoryCategory;
82
83import java.util.ArrayList;
84import java.util.Date;
85import java.util.List;
86import java.util.Random;
87
88/**
89 * Displays the Main UI for picking an individual wallpaper image.
90 */
91public class IndividualPickerFragment extends Fragment
92 implements RotationStarter, StartRotationErrorDialogFragment.Listener,
93 CurrentWallpaperBottomSheetPresenter.RefreshListener,
94 SetWallpaperErrorDialogFragment.Listener {
95 private static final String TAG = "IndividualPickerFrgmnt";
96 private static final String ARG_CATEGORY_COLLECTION_ID = "category_collection_id";
97 private static final int UNUSED_REQUEST_CODE = 1;
98 private static final String TAG_START_ROTATION_DIALOG = "start_rotation_dialog";
99 private static final String TAG_START_ROTATION_ERROR_DIALOG = "start_rotation_error_dialog";
100 private static final String PROGRESS_DIALOG_NO_TITLE = null;
101 private static final boolean PROGRESS_DIALOG_INDETERMINATE = true;
102 private static final String TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT =
103 "individual_set_wallpaper_error_dialog";
104
105 /**
106 * Position of a special tile that doesn't belong to an individual wallpaper of the category,
107 * such as "my photos" or "daily rotation".
108 */
109 private static final int SPECIAL_FIXED_TILE_ADAPTER_POSITION = 0;
110
111 private WallpaperPreferences mWallpaperPreferences;
112 private WallpaperChangedNotifier mWallpaperChangedNotifier;
113 private RotatingWallpaperComponentChecker mRotatingWallpaperComponentChecker;
114 private RecyclerView mImageGrid;
115 private IndividualAdapter mAdapter;
116 private WallpaperCategory mCategory;
117 private WallpaperRotationInitializer mWallpaperRotationInitializer;
118 private List<WallpaperInfo> mWallpapers;
119 private Point mTileSizePx;
120 private ProgressDialog mProgressDialog;
121 private boolean mTestingMode;
122 private CurrentWallpaperBottomSheetPresenter mCurrentWallpaperBottomSheetPresenter;
123 private WallpapersUiContainer mWallpapersUiContainer;
124 @FormFactor
125 private int mFormFactor;
126 private SetIndividualHolder mPendingSetIndividualHolder;
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700127 private PackageStatusNotifier mPackageStatusNotifier;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800128
129 /**
130 * Staged error dialog fragments that were unable to be shown when the activity didn't allow
131 * committing fragment transactions.
132 */
133 private SetWallpaperErrorDialogFragment mStagedSetWallpaperErrorDialogFragment;
134 private StartRotationErrorDialogFragment mStagedStartRotationErrorDialogFragment;
135
136 private Handler mHandler;
137 private Runnable mCurrentWallpaperBottomSheetExpandedRunnable;
138 private Random mRandom;
139
140 /**
141 * Whether {@code mUpdateDailyWallpaperThumbRunnable} has been run at least once in this
142 * invocation of the fragment.
143 */
144 private boolean mWasUpdateRunnableRun;
145
146 /**
147 * A Runnable which regularly updates the thumbnail for the "Daily wallpapers" tile in desktop
148 * mode.
149 */
150 private Runnable mUpdateDailyWallpaperThumbRunnable = new Runnable() {
151 @Override
152 public void run() {
153 ViewHolder viewHolder = mImageGrid.findViewHolderForAdapterPosition(
154 SPECIAL_FIXED_TILE_ADAPTER_POSITION);
155 if (viewHolder instanceof DesktopRotationHolder) {
156 updateDesktopDailyRotationThumbnail((DesktopRotationHolder) viewHolder);
157 } else { // viewHolder is null
158 // If the rotation tile is unavailable (because user has scrolled down, causing the
159 // ViewHolder to be recycled), schedule the update for some time later. Once user scrolls up
160 // again, the ViewHolder will be re-bound and its thumbnail will be updated.
161 mHandler.postDelayed(mUpdateDailyWallpaperThumbRunnable,
162 DesktopRotationHolder.CROSSFADE_DURATION_MILLIS
163 + DesktopRotationHolder.CROSSFADE_DURATION_PAUSE_MILLIS);
164 }
165 }
166 };
167
168 private WallpaperChangedNotifier.Listener mWallpaperChangedListener = new WallpaperChangedNotifier.Listener() {
169 @Override
170 public void onWallpaperChanged() {
171 if (mFormFactor != FormFactorChecker.FORM_FACTOR_DESKTOP) {
172 return;
173 }
174
175 ViewHolder selectedViewHolder = mImageGrid.findViewHolderForAdapterPosition(
176 mAdapter.mSelectedAdapterPosition);
177
178 // Null remote ID => My Photos wallpaper, so deselect whatever was previously selected.
179 if (mWallpaperPreferences.getHomeWallpaperRemoteId() == null) {
180 if (selectedViewHolder instanceof SelectableHolder) {
181 ((SelectableHolder) selectedViewHolder).setSelectionState(
182 SelectableHolder.SELECTION_STATE_DESELECTED);
183 }
184 } else {
185 mAdapter.updateSelectedTile(mAdapter.mPendingSelectedAdapterPosition);
186 }
187 }
188 };
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700189 private PackageStatusNotifier.Listener mAppStatusListener;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800190
191 public static IndividualPickerFragment newInstance(String collectionId) {
192 Bundle args = new Bundle();
193 args.putString(ARG_CATEGORY_COLLECTION_ID, collectionId);
194
195 IndividualPickerFragment fragment = new IndividualPickerFragment();
196 fragment.setArguments(args);
197 return fragment;
198 }
199
200 private static int getResIdForRotationState(@RotationInitializationState int rotationState) {
201 switch (rotationState) {
202 case WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED:
203 return R.string.daily_refresh_tile_subtitle;
204 case WallpaperRotationInitializer.ROTATION_HOME_ONLY:
205 return R.string.home_screen_message;
206 case WallpaperRotationInitializer.ROTATION_HOME_AND_LOCK:
207 return R.string.home_and_lock_short_label;
208 default:
209 Log.e(TAG, "Unknown rotation intialization state: " + rotationState);
210 return R.string.home_screen_message;
211 }
212 }
213
214 private void updateDesktopDailyRotationThumbnail(DesktopRotationHolder holder) {
215 int wallpapersIndex = mRandom.nextInt(mWallpapers.size());
216 Asset newThumbnailAsset = mWallpapers.get(wallpapersIndex).getThumbAsset(
217 getActivity());
218 holder.updateThumbnail(newThumbnailAsset, new DrawableLoadedListener() {
219 @Override
220 public void onDrawableLoaded() {
221 if (getActivity() == null) {
222 return;
223 }
224
225 // Schedule the next update of the thumbnail.
226 int delayMillis = DesktopRotationHolder.CROSSFADE_DURATION_MILLIS
227 + DesktopRotationHolder.CROSSFADE_DURATION_PAUSE_MILLIS;
228 mHandler.postDelayed(mUpdateDailyWallpaperThumbRunnable, delayMillis);
229 }
230 });
231 }
232
233 @Override
234 public void onCreate(Bundle savedInstanceState) {
235 super.onCreate(savedInstanceState);
236
237 Injector injector = InjectorProvider.getInjector();
238 Context appContext = getContext().getApplicationContext();
239 mWallpaperPreferences = injector.getPreferences(appContext);
240
241 mWallpaperChangedNotifier = WallpaperChangedNotifier.getInstance();
242 mWallpaperChangedNotifier.registerListener(mWallpaperChangedListener);
243
244 mRotatingWallpaperComponentChecker = injector.getRotatingWallpaperComponentChecker();
245
246 mFormFactor = injector.getFormFactorChecker(appContext).getFormFactor();
247
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700248 mPackageStatusNotifier = injector.getPackageStatusNotifier(appContext);
249
Jon Miranda16ea1b12017-12-12 14:52:48 -0800250 mWallpapers = new ArrayList<>();
251 mRandom = new Random();
252 mHandler = new Handler();
253
254 String collectionId = getArguments().getString(ARG_CATEGORY_COLLECTION_ID);
255 mCategory = (WallpaperCategory) injector.getCategoryProvider(appContext).getCategory(
256 collectionId);
257 if (mCategory == null) {
258 DiskBasedLogger.e(TAG, "Failed to find the category.", appContext);
259
260 // The absence of this category in the CategoryProvider indicates a broken state, probably due
261 // to a relaunch into this activity/fragment following a crash immediately prior; see
262 // b//38030129. Hence, finish the activity and return.
263 getActivity().finish();
264 return;
265 }
266
267 mWallpaperRotationInitializer = mCategory.getWallpaperRotationInitializer();
268
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700269 fetchWallpapers(false);
270
271 if (mCategory.supportsThirdParty()) {
272 mAppStatusListener = (packageName, status) -> {
273 if (status != PackageStatusNotifier.PackageStatus.REMOVED ||
274 mCategory.containsThirdParty(packageName)) {
275 fetchWallpapers(true);
276 }
277 };
278 mPackageStatusNotifier.addListener(mAppStatusListener,
279 WallpaperService.SERVICE_INTERFACE);
280 }
281 }
282
283 private void fetchWallpapers(boolean forceReload) {
284 mWallpapers.clear();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800285 mCategory.fetchWallpapers(getActivity().getApplicationContext(), new WallpaperReceiver() {
286 @Override
287 public void onWallpapersReceived(List<WallpaperInfo> wallpapers) {
288 for (WallpaperInfo wallpaper : wallpapers) {
289 mWallpapers.add(wallpaper);
290 }
291
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700292 // Wallpapers may load after the adapter is initialized, in which case we have
293 // to explicitly notify that the data set has changed.
Jon Miranda16ea1b12017-12-12 14:52:48 -0800294 if (mAdapter != null) {
295 mAdapter.notifyDataSetChanged();
296 }
297
298 if (mWallpapersUiContainer != null) {
299 mWallpapersUiContainer.onWallpapersReady();
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700300 } else {
301 if (wallpapers.isEmpty()) {
302 // If there are no more wallpapers and we're on phone, just finish the
303 // Activity.
304 Activity activity = getActivity();
305 if (activity != null
306 && mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
307 activity.finish();
308 }
309 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800310 }
311 }
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700312 }, forceReload);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800313 }
314
315 @Override
316 public View onCreateView(LayoutInflater inflater, ViewGroup container,
317 Bundle savedInstanceState) {
318 View view = inflater.inflate(R.layout.fragment_individual_picker, container, false);
319
320 mTileSizePx = TileSizeCalculator.getIndividualTileSize(getActivity());
321
322 mImageGrid = (RecyclerView) view.findViewById(R.id.wallpaper_grid);
323 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
324 int gridPaddingPx = getResources().getDimensionPixelSize(R.dimen.grid_padding_desktop);
325 updateImageGridPadding(false /* addExtraBottomSpace */);
326 mImageGrid.setScrollBarSize(gridPaddingPx);
327 }
328 GridMarginDecoration.applyTo(mImageGrid);
329
330 mAdapter = new IndividualAdapter(mWallpapers);
331 mImageGrid.setAdapter(mAdapter);
332 mImageGrid.setLayoutManager(new GridLayoutManager(getActivity(), getNumColumns()));
333
334 setUpBottomSheet();
335
336 return view;
337 }
338
339 @Override
340 public void onClickTryAgain(@Destination int unused) {
341 if (mPendingSetIndividualHolder != null) {
342 mPendingSetIndividualHolder.setWallpaper();
343 }
344 }
345
346 private void updateImageGridPadding(boolean addExtraBottomSpace) {
347 int gridPaddingPx = getResources().getDimensionPixelSize(R.dimen.grid_padding_desktop);
348 int bottomSheetHeightPx = getResources().getDimensionPixelSize(
349 R.dimen.current_wallpaper_bottom_sheet_layout_height);
350 int paddingBottomPx = addExtraBottomSpace ? bottomSheetHeightPx : 0;
351 // Only left and top may be set in order for the GridMarginDecoration to work properly.
352 mImageGrid.setPadding(
353 gridPaddingPx, gridPaddingPx, 0, paddingBottomPx);
354 }
355
356 /**
357 * Enables and populates the "Currently set" wallpaper BottomSheet.
358 */
359 private void setUpBottomSheet() {
360 mImageGrid.addOnScrollListener(new OnScrollListener() {
361 @Override
362 public void onScrolled(RecyclerView recyclerView, int dx, final int dy) {
363 if (mCurrentWallpaperBottomSheetPresenter == null) {
364 return;
365 }
366
367 if (mCurrentWallpaperBottomSheetExpandedRunnable != null) {
368 mHandler.removeCallbacks(mCurrentWallpaperBottomSheetExpandedRunnable);
369 }
370 mCurrentWallpaperBottomSheetExpandedRunnable = new Runnable() {
371 @Override
372 public void run() {
373 if (dy > 0) {
374 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(false);
375 } else {
376 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(true);
377 }
378 }
379 };
380 mHandler.postDelayed(mCurrentWallpaperBottomSheetExpandedRunnable, 100);
381 }
382 });
383 }
384
385 @Override
386 public void onResume() {
387 super.onResume();
388
389 WallpaperPreferences preferences = InjectorProvider.getInjector().getPreferences(getActivity());
390 preferences.setLastAppActiveTimestamp(new Date().getTime());
391
392 // Reset Glide memory settings to a "normal" level of usage since it may have been lowered in
393 // PreviewFragment.
394 Glide.get(getActivity()).setMemoryCategory(MemoryCategory.NORMAL);
395
396 // Show the staged 'start rotation' error dialog fragment if there is one that was unable to be
397 // shown earlier when this fragment's hosting activity didn't allow committing fragment
398 // transactions.
399 if (mStagedStartRotationErrorDialogFragment != null) {
400 mStagedStartRotationErrorDialogFragment.show(
401 getFragmentManager(), TAG_START_ROTATION_ERROR_DIALOG);
402 mStagedStartRotationErrorDialogFragment = null;
403 }
404
405 // Show the staged 'load wallpaper' or 'set wallpaper' error dialog fragments if there is one
406 // that was unable to be shown earlier when this fragment's hosting activity didn't allow
407 // committing fragment transactions.
408 if (mStagedSetWallpaperErrorDialogFragment != null) {
409 mStagedSetWallpaperErrorDialogFragment.show(
410 getFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
411 mStagedSetWallpaperErrorDialogFragment = null;
412 }
413
414 if (isRotationEnabled()) {
415 if (mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
416 // Refresh the state of the "start rotation" in case something changed the current daily
417 // rotation while this fragment was paused.
418 RotationHolder rotationHolder = (RotationHolder) mImageGrid
419 .findViewHolderForAdapterPosition(
420 SPECIAL_FIXED_TILE_ADAPTER_POSITION);
421 // The RotationHolder may be null if the RecyclerView has not created the view
422 // holder yet.
423 if (rotationHolder != null && Flags.dynamicStartRotationTileEnabled) {
424 refreshRotationHolder(rotationHolder);
425 }
426 } else if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
427 if (mWasUpdateRunnableRun && !mWallpapers.isEmpty()) {
428 // Must be resuming from a previously stopped state, so re-schedule the update of the
429 // daily wallpapers tile thumbnail.
430 mUpdateDailyWallpaperThumbRunnable.run();
431 }
432 }
433 }
434
435 }
436
437 @Override
438 public void onStop() {
439 super.onStop();
440 mHandler.removeCallbacks(mUpdateDailyWallpaperThumbRunnable);
441 }
442
443 @Override
444 public void onDestroy() {
445 super.onDestroy();
446 if (mProgressDialog != null) {
447 mProgressDialog.dismiss();
448 }
449 mWallpaperChangedNotifier.unregisterListener(mWallpaperChangedListener);
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700450 if (mAppStatusListener != null) {
451 mPackageStatusNotifier.removeListener(mAppStatusListener);
452 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800453 }
454
455 @Override
456 public void retryStartRotation(@NetworkPreference int networkPreference) {
457 startRotation(networkPreference);
458 }
459
460 public void setCurrentWallpaperBottomSheetPresenter(
461 CurrentWallpaperBottomSheetPresenter presenter) {
462 mCurrentWallpaperBottomSheetPresenter = presenter;
463 }
464
465 public void setWallpapersUiContainer(WallpapersUiContainer uiContainer) {
466 mWallpapersUiContainer = uiContainer;
467 }
468
469 /**
470 * Enable a test mode of operation -- in which certain UI features are disabled to allow for
471 * UI tests to run correctly. Works around issue in ProgressDialog currently where the dialog
472 * constantly keeps the UI thread alive and blocks a test forever.
473 *
474 * @param testingMode
475 */
476 void setTestingMode(boolean testingMode) {
477 mTestingMode = testingMode;
478 }
479
480 /**
481 * Asynchronously fetches the refreshed rotation initialization state that is up to date with the
482 * state of the user's device and binds the state of the current category's rotation to the "start
483 * rotation" tile.
484 */
485 private void refreshRotationHolder(final RotationHolder rotationHolder) {
486 mWallpaperRotationInitializer.fetchRotationInitializationState(getContext(),
487 new RotationStateListener() {
488 @Override
489 public void onRotationStateReceived(
490 @RotationInitializationState final int rotationInitializationState) {
491
492 // Update the UI state of the "start rotation" tile displayed on screen. Do this in a
493 // Handler so it is scheduled at the end of the message queue. This is necessary to
494 // ensure we do not remove or add data from the adapter while the layout is still being
495 // computed. RecyclerView documentation therefore recommends performing such changes in
496 // a Handler.
497 new android.os.Handler().post(new Runnable() {
498 @Override
499 public void run() {
500 // A config change may have destroyed the activity since the refresh started, so
501 // check for that to avoid an NPE.
502 if (getActivity() == null) {
503 return;
504 }
505
506 rotationHolder.bindRotationInitializationState(rotationInitializationState);
507 }
508 });
509 }
510 });
511 }
512
513 @Override
514 public void startRotation(@NetworkPreference final int networkPreference) {
515 if (!isRotationEnabled()) {
516 Log.e(TAG, "Rotation is not enabled for this category " + mCategory.getTitle());
517 return;
518 }
519
520 // ProgressDialog endlessly updates the UI thread, keeping it from going idle which therefore
521 // causes Espresso to hang once the dialog is shown.
522 if (mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE && !mTestingMode) {
523 int themeResId;
524 if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
525 themeResId = R.style.ProgressDialogThemePreL;
526 } else {
527 themeResId = R.style.LightDialogTheme;
528 }
529 mProgressDialog = new ProgressDialog(getActivity(), themeResId);
530
531 mProgressDialog.setTitle(PROGRESS_DIALOG_NO_TITLE);
532 mProgressDialog.setMessage(
533 getResources().getString(R.string.start_rotation_progress_message));
534 mProgressDialog.setIndeterminate(PROGRESS_DIALOG_INDETERMINATE);
535 mProgressDialog.show();
536 }
537
538 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
539 mAdapter.mPendingSelectedAdapterPosition = SPECIAL_FIXED_TILE_ADAPTER_POSITION;
540 }
541
542 final Context appContext = getActivity().getApplicationContext();
543
544 mWallpaperRotationInitializer.setFirstWallpaperInRotation(
545 appContext,
546 networkPreference,
547 new Listener() {
548 @Override
549 public void onFirstWallpaperInRotationSet() {
550 if (mProgressDialog != null) {
551 mProgressDialog.dismiss();
552 }
553
554 // The fragment may be detached from its containing activity if the user exits the
555 // app before the first wallpaper image in rotation finishes downloading.
556 Activity activity = getActivity();
557
558 if (activity != null
559 && mWallpaperRotationInitializer
560 .isNoBackupImageWallpaperPreviewNeeded(appContext)) {
561 ((IndividualPickerActivity) activity).showNoBackupImageWallpaperPreview();
562 } else {
563 if (mWallpaperRotationInitializer.startRotation(appContext)) {
564 if (activity != null && mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
565 try {
566 Toast.makeText(getActivity(), R.string.wallpaper_set_successfully_message,
567 Toast.LENGTH_SHORT).show();
568 } catch (NotFoundException e) {
569 Log.e(TAG, "Could not show toast " + e);
570 }
571
572 activity.setResult(Activity.RESULT_OK);
573 activity.finish();
574 } else if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
575 mAdapter.updateSelectedTile(SPECIAL_FIXED_TILE_ADAPTER_POSITION);
576 }
577 } else { // Failed to start rotation.
578 showStartRotationErrorDialog(networkPreference);
579
580 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
581 DesktopRotationHolder rotationViewHolder =
582 (DesktopRotationHolder) mImageGrid.findViewHolderForAdapterPosition(
583 SPECIAL_FIXED_TILE_ADAPTER_POSITION);
584 rotationViewHolder.setSelectionState(SelectableHolder.SELECTION_STATE_DESELECTED);
585 }
586 }
587 }
588 }
589
590 @Override
591 public void onError() {
592 if (mProgressDialog != null) {
593 mProgressDialog.dismiss();
594 }
595
596 showStartRotationErrorDialog(networkPreference);
597
598 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
599 DesktopRotationHolder rotationViewHolder =
600 (DesktopRotationHolder) mImageGrid.findViewHolderForAdapterPosition(
601 SPECIAL_FIXED_TILE_ADAPTER_POSITION);
602 rotationViewHolder.setSelectionState(SelectableHolder.SELECTION_STATE_DESELECTED);
603 }
604 }
605 });
606 }
607
608 private void showStartRotationErrorDialog(@NetworkPreference int networkPreference) {
609 BaseActivity activity = (BaseActivity) getActivity();
610 if (activity != null) {
611 StartRotationErrorDialogFragment startRotationErrorDialogFragment =
612 StartRotationErrorDialogFragment.newInstance(networkPreference);
613 startRotationErrorDialogFragment.setTargetFragment(
614 IndividualPickerFragment.this, UNUSED_REQUEST_CODE);
615
616 if (activity.isSafeToCommitFragmentTransaction()) {
617 startRotationErrorDialogFragment.show(
618 getFragmentManager(), TAG_START_ROTATION_ERROR_DIALOG);
619 } else {
620 mStagedStartRotationErrorDialogFragment = startRotationErrorDialogFragment;
621 }
622 }
623 }
624
625 private int getNumColumns() {
626 return TileSizeCalculator.getNumIndividualColumns(getActivity());
627 }
628
629 /**
630 * Returns whether rotation is enabled for this category.
631 */
632 private boolean isRotationEnabled() {
633 boolean isRotationSupported =
634 mRotatingWallpaperComponentChecker.getRotatingWallpaperSupport(getContext())
635 == RotatingWallpaperComponentChecker.ROTATING_WALLPAPER_SUPPORT_SUPPORTED;
636
637 return isRotationSupported && mWallpaperRotationInitializer != null;
638 }
639
640 @Override
641 public void onCurrentWallpaperRefreshed() {
642 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(true);
643 }
644
645 /**
646 * Shows a "set wallpaper" error dialog with a failure message and button to try again.
647 */
648 private void showSetWallpaperErrorDialog() {
649 SetWallpaperErrorDialogFragment dialogFragment = SetWallpaperErrorDialogFragment.newInstance(
650 R.string.set_wallpaper_error_message, WallpaperPersister.DEST_BOTH);
651 dialogFragment.setTargetFragment(this, UNUSED_REQUEST_CODE);
652
653 if (((BaseActivity) getActivity()).isSafeToCommitFragmentTransaction()) {
654 dialogFragment.show(getFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
655 } else {
656 mStagedSetWallpaperErrorDialogFragment = dialogFragment;
657 }
658 }
659
660 /**
661 * ViewHolder subclass for "daily refresh" tile in the RecyclerView, only shown if rotation is
662 * enabled for this category.
663 */
664 private class RotationHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
665
666 private FrameLayout mTileLayout;
667 private TextView mRotationMessage;
668 private TextView mRotationTitle;
669 private ImageView mRefreshIcon;
670
671 RotationHolder(View itemView) {
672 super(itemView);
673 itemView.setOnClickListener(this);
674
675 mTileLayout = (FrameLayout) itemView.findViewById(R.id.daily_refresh);
676 mRotationMessage = (TextView) itemView.findViewById(R.id.rotation_tile_message);
677 mRotationTitle = (TextView) itemView.findViewById(R.id.rotation_tile_title);
678 mRefreshIcon = (ImageView) itemView.findViewById(R.id.rotation_tile_refresh_icon);
679 mTileLayout.getLayoutParams().height = mTileSizePx.y;
680
681 // If the feature flag for "dynamic start rotation tile" is not enabled, fall back to the
682 // static UI with a blue accent color background and "Tap to turn on" text.
683 if (!Flags.dynamicStartRotationTileEnabled) {
684 mTileLayout.setBackgroundColor(
685 getResources().getColor(R.color.rotation_tile_enabled_background_color));
686 mRotationMessage.setText(R.string.daily_refresh_tile_subtitle);
687 mRotationTitle.setTextColor(
688 getResources().getColor(R.color.rotation_tile_enabled_title_text_color));
689 mRotationMessage.setTextColor(
690 getResources().getColor(R.color.rotation_tile_enabled_subtitle_text_color));
691 mRefreshIcon.setColorFilter(
692 getResources().getColor(R.color.rotation_tile_enabled_refresh_icon_color), Mode.SRC_IN);
693 return;
694 }
695
696 // Initialize the state of the "start rotation" tile (i.e., whether it is gray or blue to
697 // indicate if rotation is turned on for the current category) with last-known rotation state
698 // that could be stale. The last-known rotation state is correct in most cases and is a good
699 // starting point but may not be accurate if the user set a wallpaper through a 3rd party app
700 // while this app was paused.
701 int rotationState = mWallpaperRotationInitializer.getRotationInitializationStateDirty(
702 getContext());
703 bindRotationInitializationState(rotationState);
704 }
705
706 @Override
707 public void onClick(View v) {
708 boolean isLiveWallpaperNeeded = mWallpaperRotationInitializer
709 .isNoBackupImageWallpaperPreviewNeeded(getActivity().getApplicationContext());
710 DialogFragment startRotationDialogFragment = StartRotationDialogFragment
711 .newInstance(isLiveWallpaperNeeded);
712 startRotationDialogFragment.setTargetFragment(
713 IndividualPickerFragment.this, UNUSED_REQUEST_CODE);
714 startRotationDialogFragment.show(getFragmentManager(), TAG_START_ROTATION_DIALOG);
715 }
716
717 /**
718 * Binds the provided rotation initialization state to the RotationHolder and updates the tile's
719 * UI to be in sync with the state (i.e., message and color appropriately reflect the state to
720 * the user).
721 */
722 void bindRotationInitializationState(@RotationInitializationState int rotationState) {
723 int newBackgroundColor =
724 (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
725 ? getResources().getColor(R.color.rotation_tile_not_enabled_background_color)
726 : getResources().getColor(R.color.rotation_tile_enabled_background_color);
727 int newTitleTextColor =
728 (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
729 ? getResources().getColor(R.color.rotation_tile_not_enabled_title_text_color)
730 : getResources().getColor(R.color.rotation_tile_enabled_title_text_color);
731 int newSubtitleTextColor =
732 (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
733 ? getResources().getColor(R.color.rotation_tile_not_enabled_subtitle_text_color)
734 : getResources().getColor(R.color.rotation_tile_enabled_subtitle_text_color);
735 int newRefreshIconColor =
736 (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
737 ? getResources().getColor(R.color.rotation_tile_not_enabled_refresh_icon_color)
738 : getResources().getColor(R.color.rotation_tile_enabled_refresh_icon_color);
739
740 mTileLayout.setBackgroundColor(newBackgroundColor);
741 mRotationTitle.setTextColor(newTitleTextColor);
742 mRotationMessage.setText(getResIdForRotationState(rotationState));
743 mRotationMessage.setTextColor(newSubtitleTextColor);
744 mRefreshIcon.setColorFilter(newRefreshIconColor, Mode.SRC_IN);
745 }
746 }
747
748 /**
749 * RecyclerView Adapter subclass for the wallpaper tiles in the RecyclerView.
750 */
751 private class IndividualAdapter extends RecyclerView.Adapter<ViewHolder> {
752 private static final int ITEM_VIEW_TYPE_ROTATION = 1;
753 private static final int ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER = 2;
754 private static final int ITEM_VIEW_TYPE_MY_PHOTOS = 3;
755
756 private final List<WallpaperInfo> mWallpapers;
757
758 private int mPendingSelectedAdapterPosition;
759 private int mSelectedAdapterPosition;
760
761 IndividualAdapter(List<WallpaperInfo> wallpapers) {
762 mWallpapers = wallpapers;
763 mPendingSelectedAdapterPosition = -1;
764 mSelectedAdapterPosition = -1;
765 }
766
767 @Override
768 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
769 switch (viewType) {
770 case ITEM_VIEW_TYPE_ROTATION:
771 return createRotationHolder(parent);
772 case ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER:
773 return createIndividualHolder(parent);
774 case ITEM_VIEW_TYPE_MY_PHOTOS:
775 return createMyPhotosHolder(parent);
776 default:
777 Log.e(TAG, "Unsupported viewType " + viewType + " in IndividualAdapter");
778 return null;
779 }
780 }
781
782 @Override
783 public int getItemViewType(int position) {
784 if (isRotationEnabled() && position == SPECIAL_FIXED_TILE_ADAPTER_POSITION) {
785 return ITEM_VIEW_TYPE_ROTATION;
786 }
787
788 // A category cannot have both a "start rotation" tile and a "my photos" tile.
789 if (mCategory.supportsCustomPhotos()
790 && !isRotationEnabled()
791 && position == SPECIAL_FIXED_TILE_ADAPTER_POSITION) {
792 return ITEM_VIEW_TYPE_MY_PHOTOS;
793 }
794
795 return ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER;
796 }
797
798 @Override
799 public void onBindViewHolder(ViewHolder holder, int position) {
800 int viewType = getItemViewType(position);
801
802 switch (viewType) {
803 case ITEM_VIEW_TYPE_ROTATION:
804 onBindRotationHolder(holder, position);
805 break;
806 case ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER:
807 onBindIndividualHolder(holder, position);
808 break;
809 case ITEM_VIEW_TYPE_MY_PHOTOS:
810 ((MyPhotosViewHolder) holder).bind();
811 break;
812 default:
813 Log.e(TAG, "Unsupported viewType " + viewType + " in IndividualAdapter");
814 }
815 }
816
817 @Override
818 public int getItemCount() {
819 return (isRotationEnabled() || mCategory.supportsCustomPhotos())
820 ? mWallpapers.size() + 1
821 : mWallpapers.size();
822 }
823
824 private ViewHolder createRotationHolder(ViewGroup parent) {
825 LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
826 View view;
827
828 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
829 view = layoutInflater.inflate(R.layout.grid_item_rotation_desktop, parent, false);
830 SelectionAnimator selectionAnimator =
831 new CheckmarkSelectionAnimator(getActivity(), view);
832 return new DesktopRotationHolder(
833 getActivity(), mTileSizePx.y, view, selectionAnimator,
834 IndividualPickerFragment.this);
835 } else { // MOBILE
836 view = layoutInflater.inflate(R.layout.grid_item_rotation, parent, false);
837 return new RotationHolder(view);
838 }
839 }
840
841 private ViewHolder createIndividualHolder(ViewGroup parent) {
842 LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
843 View view = layoutInflater.inflate(R.layout.grid_item_image, parent, false);
844
845 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
846 SelectionAnimator selectionAnimator =
847 new CheckmarkSelectionAnimator(getActivity(), view);
848 return new SetIndividualHolder(
849 getActivity(), mTileSizePx.y, view,
850 selectionAnimator,
851 new OnSetListener() {
852 @Override
853 public void onPendingWallpaperSet(int adapterPosition) {
854 // Deselect and hide loading indicator for any previously pending tile.
855 if (mPendingSelectedAdapterPosition != -1) {
856 ViewHolder oldViewHolder = mImageGrid.findViewHolderForAdapterPosition(
857 mPendingSelectedAdapterPosition);
858 if (oldViewHolder instanceof SelectableHolder) {
859 ((SelectableHolder) oldViewHolder).setSelectionState(
860 SelectableHolder.SELECTION_STATE_DESELECTED);
861 }
862 }
863
864 if (mSelectedAdapterPosition != -1) {
865 ViewHolder oldViewHolder = mImageGrid.findViewHolderForAdapterPosition(
866 mSelectedAdapterPosition);
867 if (oldViewHolder instanceof SelectableHolder) {
868 ((SelectableHolder) oldViewHolder).setSelectionState(
869 SelectableHolder.SELECTION_STATE_DESELECTED);
870 }
871 }
872
873 mPendingSelectedAdapterPosition = adapterPosition;
874 }
875
876 @Override
877 public void onWallpaperSet(int adapterPosition) {
878 // No-op -- UI handles a new wallpaper being set by reacting to the
879 // WallpaperChangedNotifier.
880 }
881
882 @Override
883 public void onWallpaperSetFailed(SetIndividualHolder holder) {
884 showSetWallpaperErrorDialog();
885 mPendingSetIndividualHolder = holder;
886 }
887 });
888 } else { // MOBILE
889 return new PreviewIndividualHolder(
890 (IndividualPickerActivity) getActivity(), mTileSizePx.y, view);
891 }
892 }
893
894 private ViewHolder createMyPhotosHolder(ViewGroup parent) {
895 LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
896 View view = layoutInflater.inflate(R.layout.grid_item_my_photos, parent, false);
897
Santiago Etchebeherefab49612019-01-15 12:22:42 -0800898 return new MyPhotosViewHolder(getActivity(),
899 ((MyPhotosStarterProvider) getActivity()).getMyPhotosStarter(),
900 mTileSizePx.y, view);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800901 }
902
903 /**
904 * Marks the tile at the given position as selected with a visual indication. Also updates the
905 * "currently selected" BottomSheet to reflect the newly selected tile.
906 */
907 private void updateSelectedTile(int newlySelectedPosition) {
908 // Prevent multiple spinners from appearing with a user tapping several tiles in rapid
909 // succession.
910 if (mPendingSelectedAdapterPosition == mSelectedAdapterPosition) {
911 return;
912 }
913
914 if (mCurrentWallpaperBottomSheetPresenter != null) {
915 mCurrentWallpaperBottomSheetPresenter.refreshCurrentWallpapers(
916 IndividualPickerFragment.this);
917
918 if (mCurrentWallpaperBottomSheetExpandedRunnable != null) {
919 mHandler.removeCallbacks(mCurrentWallpaperBottomSheetExpandedRunnable);
920 }
921 mCurrentWallpaperBottomSheetExpandedRunnable = new Runnable() {
922 @Override
923 public void run() {
924 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(true);
925 }
926 };
927 mHandler.postDelayed(mCurrentWallpaperBottomSheetExpandedRunnable, 100);
928 }
929
930 // User may have switched to another category, thus detaching this fragment, so check here.
931 // NOTE: We do this check after updating the current wallpaper BottomSheet so that the update
932 // still occurs in the UI after the user selects that other category.
933 if (getActivity() == null) {
934 return;
935 }
936
937 // Update the newly selected wallpaper ViewHolder and the old one so that if
938 // selection UI state applies (desktop UI), it is updated.
939 if (mSelectedAdapterPosition >= 0) {
940 ViewHolder oldViewHolder = mImageGrid.findViewHolderForAdapterPosition(
941 mSelectedAdapterPosition);
942 if (oldViewHolder instanceof SelectableHolder) {
943 ((SelectableHolder) oldViewHolder).setSelectionState(
944 SelectableHolder.SELECTION_STATE_DESELECTED);
945 }
946 }
947
948 // Animate selection of newly selected tile.
949 ViewHolder newViewHolder = mImageGrid
950 .findViewHolderForAdapterPosition(newlySelectedPosition);
951 if (newViewHolder instanceof SelectableHolder) {
952 ((SelectableHolder) newViewHolder).setSelectionState(
953 SelectableHolder.SELECTION_STATE_SELECTED);
954 }
955
956 mSelectedAdapterPosition = newlySelectedPosition;
957
958 // If the tile was in the last row of the grid, add space below it so the user can scroll down
959 // and up to see the BottomSheet without it fully overlapping the newly selected tile.
960 int spanCount = ((GridLayoutManager) mImageGrid.getLayoutManager()).getSpanCount();
961 int numRows = (int) Math.ceil((float) getItemCount() / spanCount);
962 int rowOfNewlySelectedTile = newlySelectedPosition / spanCount;
963 boolean isInLastRow = rowOfNewlySelectedTile == numRows - 1;
964
965 updateImageGridPadding(isInLastRow /* addExtraBottomSpace */);
966 }
967
968 private void onBindRotationHolder(ViewHolder holder, int position) {
969 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
970 String collectionId = mCategory.getCollectionId();
971 ((DesktopRotationHolder) holder).bind(collectionId);
972
973 if (mWallpaperPreferences.getWallpaperPresentationMode()
974 == WallpaperPreferences.PRESENTATION_MODE_ROTATING
975 && collectionId.equals(mWallpaperPreferences.getHomeWallpaperCollectionId())) {
976 mSelectedAdapterPosition = position;
977 }
978
979 if (!mWasUpdateRunnableRun && !mWallpapers.isEmpty()) {
980 updateDesktopDailyRotationThumbnail((DesktopRotationHolder) holder);
981 mWasUpdateRunnableRun = true;
982 }
983 }
984 }
985
986 private void onBindIndividualHolder(ViewHolder holder, int position) {
987 int wallpaperIndex = (isRotationEnabled() || mCategory.supportsCustomPhotos())
988 ? position - 1 : position;
989 WallpaperInfo wallpaper = mWallpapers.get(wallpaperIndex);
990 ((IndividualHolder) holder).bindWallpaper(wallpaper);
991 WallpaperPreferences prefs = InjectorProvider.getInjector().getPreferences(getContext());
992
993 String wallpaperId = wallpaper.getWallpaperId();
994 if (wallpaperId != null && wallpaperId.equals(prefs.getHomeWallpaperRemoteId())) {
995 mSelectedAdapterPosition = position;
996 }
997 }
998 }
999}