blob: 5fd0e7ac24c4f21ea4183d20a18bec35504d22d1 [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 Liao7dc21572020-05-14 18:37:36 +080018import static com.android.wallpaper.picker.WallpaperPickerDelegate.PREVIEW_WALLPAPER_REQUEST_CODE;
Chuck Liao69630f12020-03-05 19:01:25 +080019import static com.android.wallpaper.widget.BottomActionBar.BottomAction.APPLY;
Chuck Liao7dc21572020-05-14 18:37:36 +080020import static com.android.wallpaper.widget.BottomActionBar.BottomAction.EDIT;
chihhangchuang22aa0cc2020-03-25 19:12:42 +080021import static com.android.wallpaper.widget.BottomActionBar.BottomAction.INFORMATION;
Chuck Liao8ec38e02020-02-26 20:59:32 +080022import static com.android.wallpaper.widget.BottomActionBar.BottomAction.ROTATION;
23
Jon Miranda16ea1b12017-12-12 14:52:48 -080024import android.app.Activity;
25import android.app.ProgressDialog;
“Chuck7ef99722020-03-22 04:34:03 +080026import android.app.WallpaperManager;
Jon Miranda16ea1b12017-12-12 14:52:48 -080027import android.content.Context;
Chihhang Chuang85f099a2020-06-16 18:04:39 +080028import android.content.DialogInterface;
chihhangchuangc687d912020-05-04 14:33:05 +080029import android.content.Intent;
Santiago Etchebehereb1854472019-06-06 17:44:54 -070030import android.content.res.Configuration;
Jon Miranda16ea1b12017-12-12 14:52:48 -080031import android.content.res.Resources.NotFoundException;
32import android.graphics.Point;
Chuck Liaoba401232020-03-13 20:11:04 +080033import android.graphics.Rect;
Jon Miranda16ea1b12017-12-12 14:52:48 -080034import android.os.Build.VERSION;
35import android.os.Build.VERSION_CODES;
36import android.os.Bundle;
37import android.os.Handler;
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -070038import android.service.wallpaper.WallpaperService;
Jon Miranda16ea1b12017-12-12 14:52:48 -080039import android.util.Log;
40import android.view.LayoutInflater;
41import android.view.View;
42import android.view.ViewGroup;
Jon Miranda16ea1b12017-12-12 14:52:48 -080043import 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 Liaof6b4b192020-08-07 02:31:32 +080047import androidx.core.widget.ContentLoadingProgressBar;
Santiago Etchebeherefab49612019-01-15 12:22:42 -080048import androidx.fragment.app.DialogFragment;
Santiago Etchebeherefab49612019-01-15 12:22:42 -080049import androidx.recyclerview.widget.GridLayoutManager;
50import androidx.recyclerview.widget.RecyclerView;
51import androidx.recyclerview.widget.RecyclerView.OnScrollListener;
52import androidx.recyclerview.widget.RecyclerView.ViewHolder;
53
Jon Miranda16ea1b12017-12-12 14:52:48 -080054import com.android.wallpaper.R;
55import com.android.wallpaper.asset.Asset;
56import com.android.wallpaper.asset.Asset.DrawableLoadedListener;
Santiago Etchebehere8648bb82019-08-06 17:09:02 -070057import com.android.wallpaper.model.Category;
58import com.android.wallpaper.model.CategoryProvider;
59import com.android.wallpaper.model.CategoryReceiver;
Chuck Liao69630f12020-03-05 19:01:25 +080060import com.android.wallpaper.model.LiveWallpaperInfo;
Jon Miranda16ea1b12017-12-12 14:52:48 -080061import com.android.wallpaper.model.WallpaperCategory;
62import com.android.wallpaper.model.WallpaperInfo;
63import com.android.wallpaper.model.WallpaperReceiver;
64import com.android.wallpaper.model.WallpaperRotationInitializer;
65import com.android.wallpaper.model.WallpaperRotationInitializer.Listener;
66import com.android.wallpaper.model.WallpaperRotationInitializer.NetworkPreference;
Jon Miranda16ea1b12017-12-12 14:52:48 -080067import com.android.wallpaper.module.FormFactorChecker;
68import com.android.wallpaper.module.FormFactorChecker.FormFactor;
69import com.android.wallpaper.module.Injector;
70import com.android.wallpaper.module.InjectorProvider;
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -070071import com.android.wallpaper.module.PackageStatusNotifier;
chihhangchuangc687d912020-05-04 14:33:05 +080072import com.android.wallpaper.module.UserEventLogger;
Jon Miranda16ea1b12017-12-12 14:52:48 -080073import com.android.wallpaper.module.WallpaperChangedNotifier;
74import com.android.wallpaper.module.WallpaperPersister;
75import com.android.wallpaper.module.WallpaperPersister.Destination;
76import com.android.wallpaper.module.WallpaperPreferences;
Chuck Liao69630f12020-03-05 19:01:25 +080077import com.android.wallpaper.module.WallpaperSetter;
Jon Miranda16ea1b12017-12-12 14:52:48 -080078import com.android.wallpaper.picker.BaseActivity;
chihhangchuang3efb6832020-04-17 02:06:25 +080079import com.android.wallpaper.picker.BottomActionBarFragment;
Jon Miranda16ea1b12017-12-12 14:52:48 -080080import com.android.wallpaper.picker.CurrentWallpaperBottomSheetPresenter;
Santiago Etchebeherefab49612019-01-15 12:22:42 -080081import com.android.wallpaper.picker.MyPhotosStarter.MyPhotosStarterProvider;
Chuck Liao7dc21572020-05-14 18:37:36 +080082import com.android.wallpaper.picker.PreviewActivity;
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;
chihhangchuangc687d912020-05-04 14:33:05 +080088import com.android.wallpaper.picker.WallpaperInfoHelper;
Jon Miranda16ea1b12017-12-12 14:52:48 -080089import com.android.wallpaper.picker.WallpapersUiContainer;
90import com.android.wallpaper.picker.individual.SetIndividualHolder.OnSetListener;
91import com.android.wallpaper.util.DiskBasedLogger;
Santiago Etchebehere53c63432020-05-07 18:55:35 -070092import com.android.wallpaper.util.SizeCalculator;
Chuck Liao8ec38e02020-02-26 20:59:32 +080093import com.android.wallpaper.widget.BottomActionBar;
chihhangchuang1a29e752020-04-28 18:22:53 +080094import com.android.wallpaper.widget.WallpaperInfoView;
Wesley.CW Wangdc68fde2020-06-15 19:12:33 +080095import com.android.wallpaper.widget.WallpaperPickerRecyclerViewAccessibilityDelegate;
96import com.android.wallpaper.widget.WallpaperPickerRecyclerViewAccessibilityDelegate.BottomSheetHost;
Sunny Goyal8600a3f2018-08-15 12:48:01 -070097
Jon Miranda16ea1b12017-12-12 14:52:48 -080098import com.bumptech.glide.Glide;
99import com.bumptech.glide.MemoryCategory;
100
101import java.util.ArrayList;
102import java.util.Date;
103import java.util.List;
Chuck Liao11f4a762020-04-08 13:24:43 +0800104import java.util.Optional;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800105import java.util.Random;
106
107/**
108 * Displays the Main UI for picking an individual wallpaper image.
109 */
chihhangchuang3efb6832020-04-17 02:06:25 +0800110public class IndividualPickerFragment extends BottomActionBarFragment
Jon Miranda16ea1b12017-12-12 14:52:48 -0800111 implements RotationStarter, StartRotationErrorDialogFragment.Listener,
112 CurrentWallpaperBottomSheetPresenter.RefreshListener,
Chihhang Chuang85f099a2020-06-16 18:04:39 +0800113 SetWallpaperErrorDialogFragment.Listener, SetWallpaperDialogFragment.Listener,
114 StartRotationDialogFragment.Listener {
Chuck Liao46644b92020-06-08 14:20:50 +0800115
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800116 /**
117 * Position of a special tile that doesn't belong to an individual wallpaper of the category,
118 * such as "my photos" or "daily rotation".
119 */
120 static final int SPECIAL_FIXED_TILE_ADAPTER_POSITION = 0;
121 static final String ARG_CATEGORY_COLLECTION_ID = "category_collection_id";
122
Jon Miranda16ea1b12017-12-12 14:52:48 -0800123 private static final String TAG = "IndividualPickerFrgmnt";
Jon Miranda16ea1b12017-12-12 14:52:48 -0800124 private static final int UNUSED_REQUEST_CODE = 1;
125 private static final String TAG_START_ROTATION_DIALOG = "start_rotation_dialog";
126 private static final String TAG_START_ROTATION_ERROR_DIALOG = "start_rotation_error_dialog";
127 private static final String PROGRESS_DIALOG_NO_TITLE = null;
128 private static final boolean PROGRESS_DIALOG_INDETERMINATE = true;
129 private static final String TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT =
130 "individual_set_wallpaper_error_dialog";
Santiago Etchebehereb1854472019-06-06 17:44:54 -0700131 private static final String KEY_NIGHT_MODE = "IndividualPickerFragment.NIGHT_MODE";
Jon Miranda16ea1b12017-12-12 14:52:48 -0800132
Chuck Liao8ec38e02020-02-26 20:59:32 +0800133 /**
Chuck Liaof40063f2020-03-03 18:35:24 +0800134 * 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
Chuck Liaob3829fb2020-04-01 00:47:50 +0800161 /**
162 * The listener which will be notified when the wallpaper is selected.
163 */
164 public interface WallpaperSelectedListener {
165 /**
166 * Called when the wallpaper is selected.
167 *
168 * @param position the position of the selected wallpaper
169 */
170 void onWallpaperSelected(int position);
171 }
172
Chuck Liaof6b4b192020-08-07 02:31:32 +0800173 /**
174 * Interface to be implemented by a Fragment hosting a {@link IndividualPickerFragment}
175 */
176 public interface IndividualPickerFragmentHost {
177 /**
178 * Sets the title in the toolbar.
179 */
180 void setToolbarTitle(CharSequence title);
181
182 /**
183 * Moves to the previous fragment.
184 */
185 void moveToPreviousFragment();
186 }
187
Ching-Sung Lied6560f2020-05-04 19:25:09 +0800188 WallpaperPersister mWallpaperPersister;
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800189 WallpaperPreferences mWallpaperPreferences;
190 WallpaperChangedNotifier mWallpaperChangedNotifier;
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800191 RecyclerView mImageGrid;
192 IndividualAdapter mAdapter;
193 WallpaperCategory mCategory;
194 WallpaperRotationInitializer mWallpaperRotationInitializer;
195 List<WallpaperInfo> mWallpapers;
196 Point mTileSizePx;
197 WallpapersUiContainer mWallpapersUiContainer;
198 @FormFactor
199 int mFormFactor;
200 PackageStatusNotifier mPackageStatusNotifier;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800201
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800202 Handler mHandler;
203 Random mRandom;
Chuck Liaof6b4b192020-08-07 02:31:32 +0800204 boolean mIsWallpapersReceived;
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800205
206 WallpaperChangedNotifier.Listener mWallpaperChangedListener =
207 new WallpaperChangedNotifier.Listener() {
208 @Override
209 public void onWallpaperChanged() {
210 if (mFormFactor != FormFactorChecker.FORM_FACTOR_DESKTOP) {
211 return;
212 }
213
214 ViewHolder selectedViewHolder = mImageGrid.findViewHolderForAdapterPosition(
215 mAdapter.mSelectedAdapterPosition);
216
217 // Null remote ID => My Photos wallpaper, so deselect whatever was previously selected.
218 if (mWallpaperPreferences.getHomeWallpaperRemoteId() == null) {
219 if (selectedViewHolder instanceof SelectableHolder) {
220 ((SelectableHolder) selectedViewHolder).setSelectionState(
221 SelectableHolder.SELECTION_STATE_DESELECTED);
222 }
223 } else {
224 mAdapter.updateSelectedTile(mAdapter.mPendingSelectedAdapterPosition);
225 }
226 }
227 };
228 PackageStatusNotifier.Listener mAppStatusListener;
Ching-Sung Li073812b2020-04-07 21:19:21 +0800229 BottomActionBar mBottomActionBar;
chihhangchuang1a29e752020-04-28 18:22:53 +0800230 WallpaperInfoView mWallpaperInfoView;
Ching-Sung Li073812b2020-04-07 21:19:21 +0800231 @Nullable WallpaperInfo mSelectedWallpaperInfo;
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800232
chihhangchuangc687d912020-05-04 14:33:05 +0800233 private UserEventLogger mUserEventLogger;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800234 private ProgressDialog mProgressDialog;
235 private boolean mTestingMode;
236 private CurrentWallpaperBottomSheetPresenter mCurrentWallpaperBottomSheetPresenter;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800237 private SetIndividualHolder mPendingSetIndividualHolder;
Chuck Liaof6b4b192020-08-07 02:31:32 +0800238 private ContentLoadingProgressBar mLoading;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800239
240 /**
241 * Staged error dialog fragments that were unable to be shown when the activity didn't allow
242 * committing fragment transactions.
243 */
244 private SetWallpaperErrorDialogFragment mStagedSetWallpaperErrorDialogFragment;
245 private StartRotationErrorDialogFragment mStagedStartRotationErrorDialogFragment;
246
Jon Miranda16ea1b12017-12-12 14:52:48 -0800247 private Runnable mCurrentWallpaperBottomSheetExpandedRunnable;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800248
249 /**
250 * Whether {@code mUpdateDailyWallpaperThumbRunnable} has been run at least once in this
251 * invocation of the fragment.
252 */
253 private boolean mWasUpdateRunnableRun;
254
255 /**
256 * A Runnable which regularly updates the thumbnail for the "Daily wallpapers" tile in desktop
257 * mode.
258 */
259 private Runnable mUpdateDailyWallpaperThumbRunnable = new Runnable() {
260 @Override
261 public void run() {
262 ViewHolder viewHolder = mImageGrid.findViewHolderForAdapterPosition(
263 SPECIAL_FIXED_TILE_ADAPTER_POSITION);
264 if (viewHolder instanceof DesktopRotationHolder) {
265 updateDesktopDailyRotationThumbnail((DesktopRotationHolder) viewHolder);
266 } else { // viewHolder is null
267 // If the rotation tile is unavailable (because user has scrolled down, causing the
268 // ViewHolder to be recycled), schedule the update for some time later. Once user scrolls up
269 // again, the ViewHolder will be re-bound and its thumbnail will be updated.
270 mHandler.postDelayed(mUpdateDailyWallpaperThumbRunnable,
271 DesktopRotationHolder.CROSSFADE_DURATION_MILLIS
272 + DesktopRotationHolder.CROSSFADE_DURATION_PAUSE_MILLIS);
273 }
274 }
275 };
276
Chuck Liao69630f12020-03-05 19:01:25 +0800277 private WallpaperSetter mWallpaperSetter;
“Chuckffd832c2020-03-22 02:15:58 +0800278 private WallpaperInfo mAppliedWallpaperInfo;
“Chuck7ef99722020-03-22 04:34:03 +0800279 private WallpaperManager mWallpaperManager;
280 private int mWallpaperDestination;
Chuck Liaob3829fb2020-04-01 00:47:50 +0800281 private WallpaperSelectedListener mWallpaperSelectedListener;
Chuck Liao8ec38e02020-02-26 20:59:32 +0800282
Jon Miranda16ea1b12017-12-12 14:52:48 -0800283 public static IndividualPickerFragment newInstance(String collectionId) {
284 Bundle args = new Bundle();
285 args.putString(ARG_CATEGORY_COLLECTION_ID, collectionId);
286
287 IndividualPickerFragment fragment = new IndividualPickerFragment();
288 fragment.setArguments(args);
289 return fragment;
290 }
291
“Chuck7ef99722020-03-22 04:34:03 +0800292 /**
293 * Highlights the applied wallpaper (if it exists) according to the destination a wallpaper
294 * would be set.
295 *
296 * @param wallpaperDestination the destination a wallpaper would be set.
297 * It will be either {@link Destination#DEST_HOME_SCREEN}
298 * or {@link Destination#DEST_LOCK_SCREEN}.
299 */
300 public void highlightAppliedWallpaper(@Destination int wallpaperDestination) {
301 mWallpaperDestination = wallpaperDestination;
Chuck Liao11f4a762020-04-08 13:24:43 +0800302 if (mWallpapers != null) {
303 refreshAppliedWallpaper();
“Chuck7ef99722020-03-22 04:34:03 +0800304 }
305 }
306
Jon Miranda16ea1b12017-12-12 14:52:48 -0800307 private void updateDesktopDailyRotationThumbnail(DesktopRotationHolder holder) {
308 int wallpapersIndex = mRandom.nextInt(mWallpapers.size());
309 Asset newThumbnailAsset = mWallpapers.get(wallpapersIndex).getThumbAsset(
310 getActivity());
311 holder.updateThumbnail(newThumbnailAsset, new DrawableLoadedListener() {
312 @Override
313 public void onDrawableLoaded() {
314 if (getActivity() == null) {
315 return;
316 }
317
318 // Schedule the next update of the thumbnail.
319 int delayMillis = DesktopRotationHolder.CROSSFADE_DURATION_MILLIS
320 + DesktopRotationHolder.CROSSFADE_DURATION_PAUSE_MILLIS;
321 mHandler.postDelayed(mUpdateDailyWallpaperThumbRunnable, delayMillis);
322 }
323 });
324 }
325
326 @Override
327 public void onCreate(Bundle savedInstanceState) {
328 super.onCreate(savedInstanceState);
329
330 Injector injector = InjectorProvider.getInjector();
331 Context appContext = getContext().getApplicationContext();
332 mWallpaperPreferences = injector.getPreferences(appContext);
333
334 mWallpaperChangedNotifier = WallpaperChangedNotifier.getInstance();
335 mWallpaperChangedNotifier.registerListener(mWallpaperChangedListener);
336
“Chuck7ef99722020-03-22 04:34:03 +0800337 mWallpaperManager = WallpaperManager.getInstance(appContext);
338
Jon Miranda16ea1b12017-12-12 14:52:48 -0800339 mFormFactor = injector.getFormFactorChecker(appContext).getFormFactor();
340
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700341 mPackageStatusNotifier = injector.getPackageStatusNotifier(appContext);
342
chihhangchuangc687d912020-05-04 14:33:05 +0800343 mUserEventLogger = injector.getUserEventLogger(appContext);
344
“Chuckffd832c2020-03-22 02:15:58 +0800345 mWallpaperPersister = injector.getWallpaperPersister(appContext);
Chuck Liao69630f12020-03-05 19:01:25 +0800346 mWallpaperSetter = new WallpaperSetter(
“Chuckffd832c2020-03-22 02:15:58 +0800347 mWallpaperPersister,
Chuck Liao69630f12020-03-05 19:01:25 +0800348 injector.getPreferences(appContext),
349 injector.getUserEventLogger(appContext),
350 false);
351
Jon Miranda16ea1b12017-12-12 14:52:48 -0800352 mWallpapers = new ArrayList<>();
353 mRandom = new Random();
354 mHandler = new Handler();
355
Santiago Etchebehereb1854472019-06-06 17:44:54 -0700356 // Clear Glide's cache if night-mode changed to ensure thumbnails are reloaded
357 if (savedInstanceState != null && (savedInstanceState.getInt(KEY_NIGHT_MODE)
358 != (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK))) {
359 Glide.get(getContext()).clearMemory();
360 }
361
Santiago Etchebehere8648bb82019-08-06 17:09:02 -0700362 CategoryProvider categoryProvider = injector.getCategoryProvider(appContext);
363 categoryProvider.fetchCategories(new CategoryReceiver() {
364 @Override
365 public void onCategoryReceived(Category category) {
366 // Do nothing.
367 }
368
369 @Override
370 public void doneFetchingCategories() {
371 mCategory = (WallpaperCategory) categoryProvider.getCategory(
372 getArguments().getString(ARG_CATEGORY_COLLECTION_ID));
373 if (mCategory == null) {
374 DiskBasedLogger.e(TAG, "Failed to find the category.", getContext());
375
376 // The absence of this category in the CategoryProvider indicates a broken
377 // state, see b/38030129. Hence, finish the activity and return.
Chuck Liaof6b4b192020-08-07 02:31:32 +0800378 getIndividualPickerFragmentHost().moveToPreviousFragment();
379 Toast.makeText(getContext(), R.string.collection_not_exist_msg,
380 Toast.LENGTH_SHORT).show();
Santiago Etchebehere8648bb82019-08-06 17:09:02 -0700381 return;
382 }
383 onCategoryLoaded();
384 }
385 }, false);
386 }
387
Chuck Liaof6b4b192020-08-07 02:31:32 +0800388
Santiago Etchebehere8648bb82019-08-06 17:09:02 -0700389 protected void onCategoryLoaded() {
Chuck Liaof6b4b192020-08-07 02:31:32 +0800390 getIndividualPickerFragmentHost().setToolbarTitle(mCategory.getTitle());
Santiago Etchebehere8648bb82019-08-06 17:09:02 -0700391 mWallpaperRotationInitializer = mCategory.getWallpaperRotationInitializer();
Chuck Liao58e4a1c2020-05-22 11:35:35 +0800392 // Avoids the "rotation" action is not shown correctly
393 // in a rare case : onCategoryLoaded() is called after onBottomActionBarReady().
394 if (isRotationEnabled() && mBottomActionBar != null
395 && !mBottomActionBar.areActionsShown(ROTATION)) {
396 mBottomActionBar.showActions(ROTATION);
397 }
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700398 fetchWallpapers(false);
399
400 if (mCategory.supportsThirdParty()) {
401 mAppStatusListener = (packageName, status) -> {
402 if (status != PackageStatusNotifier.PackageStatus.REMOVED ||
403 mCategory.containsThirdParty(packageName)) {
404 fetchWallpapers(true);
405 }
406 };
407 mPackageStatusNotifier.addListener(mAppStatusListener,
408 WallpaperService.SERVICE_INTERFACE);
409 }
Santiago Etchebehere8648bb82019-08-06 17:09:02 -0700410
Santiago Etchebehere460fbcb2019-08-20 11:47:30 -0700411 maybeSetUpImageGrid();
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700412 }
413
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800414 void fetchWallpapers(boolean forceReload) {
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700415 mWallpapers.clear();
Chuck Liaof6b4b192020-08-07 02:31:32 +0800416 mIsWallpapersReceived = false;
417 updateLoading();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800418 mCategory.fetchWallpapers(getActivity().getApplicationContext(), new WallpaperReceiver() {
419 @Override
420 public void onWallpapersReceived(List<WallpaperInfo> wallpapers) {
Chuck Liaof6b4b192020-08-07 02:31:32 +0800421 mIsWallpapersReceived = true;
422 updateLoading();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800423 for (WallpaperInfo wallpaper : wallpapers) {
424 mWallpapers.add(wallpaper);
425 }
426
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700427 // Wallpapers may load after the adapter is initialized, in which case we have
428 // to explicitly notify that the data set has changed.
Jon Miranda16ea1b12017-12-12 14:52:48 -0800429 if (mAdapter != null) {
430 mAdapter.notifyDataSetChanged();
431 }
432
433 if (mWallpapersUiContainer != null) {
434 mWallpapersUiContainer.onWallpapersReady();
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700435 } else {
436 if (wallpapers.isEmpty()) {
437 // If there are no more wallpapers and we're on phone, just finish the
438 // Activity.
439 Activity activity = getActivity();
440 if (activity != null
441 && mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
442 activity.finish();
443 }
444 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800445 }
446 }
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700447 }, forceReload);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800448 }
449
Chuck Liaof6b4b192020-08-07 02:31:32 +0800450 void updateLoading() {
451 if (mLoading == null) {
452 return;
453 }
454
455 if (mIsWallpapersReceived) {
456 mLoading.hide();
457 } else {
458 mLoading.show();
459 }
460 }
461
Jon Miranda16ea1b12017-12-12 14:52:48 -0800462 @Override
Santiago Etchebehereb1854472019-06-06 17:44:54 -0700463 public void onSaveInstanceState(@NonNull Bundle outState) {
464 super.onSaveInstanceState(outState);
Santiago Etchebehere0ec065c2019-06-13 11:30:21 -0700465 outState.putInt(KEY_NIGHT_MODE,
Santiago Etchebehereb1854472019-06-06 17:44:54 -0700466 getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK);
467 }
468
469 @Override
Jon Miranda16ea1b12017-12-12 14:52:48 -0800470 public View onCreateView(LayoutInflater inflater, ViewGroup container,
471 Bundle savedInstanceState) {
472 View view = inflater.inflate(R.layout.fragment_individual_picker, container, false);
473
Santiago Etchebehere53c63432020-05-07 18:55:35 -0700474 mTileSizePx = SizeCalculator.getIndividualTileSize(getActivity());
Jon Miranda16ea1b12017-12-12 14:52:48 -0800475
476 mImageGrid = (RecyclerView) view.findViewById(R.id.wallpaper_grid);
477 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
478 int gridPaddingPx = getResources().getDimensionPixelSize(R.dimen.grid_padding_desktop);
479 updateImageGridPadding(false /* addExtraBottomSpace */);
480 mImageGrid.setScrollBarSize(gridPaddingPx);
481 }
Chuck Liaoba401232020-03-13 20:11:04 +0800482 mImageGrid.addItemDecoration(new GridPaddingDecoration(
483 getResources().getDimensionPixelSize(R.dimen.grid_padding)));
Wesley.CW Wangdc68fde2020-06-15 19:12:33 +0800484 mImageGrid.setAccessibilityDelegateCompat(
485 new WallpaperPickerRecyclerViewAccessibilityDelegate(
486 mImageGrid, (BottomSheetHost) getParentFragment(), getNumColumns()));
Chuck Liaof6b4b192020-08-07 02:31:32 +0800487 mLoading = view.findViewById(R.id.loading_indicator);
488 updateLoading();
Santiago Etchebehere460fbcb2019-08-20 11:47:30 -0700489 maybeSetUpImageGrid();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800490 setUpBottomSheet();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800491 return view;
492 }
493
494 @Override
495 public void onClickTryAgain(@Destination int unused) {
496 if (mPendingSetIndividualHolder != null) {
497 mPendingSetIndividualHolder.setWallpaper();
498 }
499 }
500
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800501 void updateImageGridPadding(boolean addExtraBottomSpace) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800502 int gridPaddingPx = getResources().getDimensionPixelSize(R.dimen.grid_padding_desktop);
503 int bottomSheetHeightPx = getResources().getDimensionPixelSize(
504 R.dimen.current_wallpaper_bottom_sheet_layout_height);
505 int paddingBottomPx = addExtraBottomSpace ? bottomSheetHeightPx : 0;
506 // Only left and top may be set in order for the GridMarginDecoration to work properly.
507 mImageGrid.setPadding(
508 gridPaddingPx, gridPaddingPx, 0, paddingBottomPx);
509 }
510
Chuck Liaof6b4b192020-08-07 02:31:32 +0800511 private IndividualPickerFragmentHost getIndividualPickerFragmentHost() {
512 return (IndividualPickerFragmentHost) getParentFragment();
513 }
514
Santiago Etchebehere460fbcb2019-08-20 11:47:30 -0700515 private void maybeSetUpImageGrid() {
Santiago Etchebehere8648bb82019-08-06 17:09:02 -0700516 // Skip if mImageGrid been initialized yet
517 if (mImageGrid == null) {
518 return;
519 }
520 // Skip if category hasn't loaded yet
521 if (mCategory == null) {
522 return;
523 }
524 // Skip if the adapter was already created
525 if (mAdapter != null) {
526 return;
527 }
Santiago Etchebehere460fbcb2019-08-20 11:47:30 -0700528 setUpImageGrid();
529 }
530
531 /**
532 * Create the adapter and assign it to mImageGrid.
533 * Both mImageGrid and mCategory are guaranteed to not be null when this method is called.
534 */
535 void setUpImageGrid() {
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800536 mAdapter = new IndividualAdapter(mWallpapers);
537 mImageGrid.setAdapter(mAdapter);
538 mImageGrid.setLayoutManager(new GridLayoutManager(getActivity(), getNumColumns()));
539 }
540
Jon Miranda16ea1b12017-12-12 14:52:48 -0800541 /**
542 * Enables and populates the "Currently set" wallpaper BottomSheet.
543 */
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800544 void setUpBottomSheet() {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800545 mImageGrid.addOnScrollListener(new OnScrollListener() {
546 @Override
547 public void onScrolled(RecyclerView recyclerView, int dx, final int dy) {
548 if (mCurrentWallpaperBottomSheetPresenter == null) {
549 return;
550 }
551
552 if (mCurrentWallpaperBottomSheetExpandedRunnable != null) {
553 mHandler.removeCallbacks(mCurrentWallpaperBottomSheetExpandedRunnable);
554 }
555 mCurrentWallpaperBottomSheetExpandedRunnable = new Runnable() {
556 @Override
557 public void run() {
558 if (dy > 0) {
559 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(false);
560 } else {
561 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(true);
562 }
563 }
564 };
565 mHandler.postDelayed(mCurrentWallpaperBottomSheetExpandedRunnable, 100);
566 }
567 });
568 }
569
chihhangchuang3efb6832020-04-17 02:06:25 +0800570 @Override
571 protected void onBottomActionBarReady(BottomActionBar bottomActionBar) {
Chuck Liao342f4ab2020-05-26 18:28:09 +0800572 mBottomActionBar = bottomActionBar;
573 if (isRotationEnabled()) {
574 mBottomActionBar.showActionsOnly(ROTATION);
Ching-Sung Li073812b2020-04-07 21:19:21 +0800575 }
Chuck Liao342f4ab2020-05-26 18:28:09 +0800576 mBottomActionBar.setActionClickListener(ROTATION, unused -> {
577 DialogFragment startRotationDialogFragment = new StartRotationDialogFragment();
578 startRotationDialogFragment.setTargetFragment(
579 IndividualPickerFragment.this, UNUSED_REQUEST_CODE);
580 startRotationDialogFragment.show(getFragmentManager(), TAG_START_ROTATION_DIALOG);
581 });
582 mBottomActionBar.setActionClickListener(APPLY, unused -> {
583 mBottomActionBar.disableActions();
584 mWallpaperSetter.requestDestination(getActivity(), getFragmentManager(), this,
585 mSelectedWallpaperInfo instanceof LiveWallpaperInfo);
586 });
587
588 mWallpaperInfoView = (WallpaperInfoView) LayoutInflater.from(getContext())
589 .inflate(R.layout.wallpaper_info_view, /* root= */ null);
590 mBottomActionBar.attachViewToBottomSheetAndBindAction(mWallpaperInfoView, INFORMATION);
591 mBottomActionBar.setActionClickListener(EDIT, unused -> {
592 mWallpaperPersister.setWallpaperInfoInPreview(mSelectedWallpaperInfo);
593 mSelectedWallpaperInfo.showPreview(getActivity(),
594 new PreviewActivity.PreviewActivityIntentFactory(),
595 PREVIEW_WALLPAPER_REQUEST_CODE);
596 });
597 mBottomActionBar.show();
Ching-Sung Li073812b2020-04-07 21:19:21 +0800598 }
599
Jon Miranda16ea1b12017-12-12 14:52:48 -0800600 @Override
601 public void onResume() {
602 super.onResume();
603
Santiago Etchebehere8cad0dd2019-10-17 10:52:39 -0700604 WallpaperPreferences preferences = InjectorProvider.getInjector()
605 .getPreferences(getActivity());
Jon Miranda16ea1b12017-12-12 14:52:48 -0800606 preferences.setLastAppActiveTimestamp(new Date().getTime());
607
608 // Reset Glide memory settings to a "normal" level of usage since it may have been lowered in
609 // PreviewFragment.
610 Glide.get(getActivity()).setMemoryCategory(MemoryCategory.NORMAL);
611
612 // Show the staged 'start rotation' error dialog fragment if there is one that was unable to be
613 // shown earlier when this fragment's hosting activity didn't allow committing fragment
614 // transactions.
615 if (mStagedStartRotationErrorDialogFragment != null) {
616 mStagedStartRotationErrorDialogFragment.show(
617 getFragmentManager(), TAG_START_ROTATION_ERROR_DIALOG);
618 mStagedStartRotationErrorDialogFragment = null;
619 }
620
621 // Show the staged 'load wallpaper' or 'set wallpaper' error dialog fragments if there is one
622 // that was unable to be shown earlier when this fragment's hosting activity didn't allow
623 // committing fragment transactions.
624 if (mStagedSetWallpaperErrorDialogFragment != null) {
625 mStagedSetWallpaperErrorDialogFragment.show(
626 getFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
627 mStagedSetWallpaperErrorDialogFragment = null;
628 }
629
Chuck Liao5a4243b2020-05-20 23:56:39 +0800630 if (shouldShowRotationTile() && mWasUpdateRunnableRun && !mWallpapers.isEmpty()) {
631 // Must be resuming from a previously stopped state, so re-schedule the update of the
632 // daily wallpapers tile thumbnail.
633 mUpdateDailyWallpaperThumbRunnable.run();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800634 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800635 }
636
637 @Override
638 public void onStop() {
639 super.onStop();
640 mHandler.removeCallbacks(mUpdateDailyWallpaperThumbRunnable);
641 }
642
643 @Override
644 public void onDestroy() {
645 super.onDestroy();
646 if (mProgressDialog != null) {
647 mProgressDialog.dismiss();
648 }
649 mWallpaperChangedNotifier.unregisterListener(mWallpaperChangedListener);
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700650 if (mAppStatusListener != null) {
651 mPackageStatusNotifier.removeListener(mAppStatusListener);
652 }
Chuck Liao69630f12020-03-05 19:01:25 +0800653 mWallpaperSetter.cleanUp();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800654 }
655
656 @Override
Chihhang Chuang85f099a2020-06-16 18:04:39 +0800657 public void onStartRotationDialogDismiss(@NonNull DialogInterface dialog) {
chihhangchuang8f9da5f2020-06-17 00:32:18 +0800658 // TODO(b/159310028): Refactor fragment layer to make it able to restore from config change.
659 // This is to handle config change with StartRotationDialog popup, the StartRotationDialog
660 // still holds a reference to the destroyed Fragment and is calling
661 // onStartRotationDialogDismissed on that destroyed Fragment.
662 if (mBottomActionBar != null) {
663 mBottomActionBar.deselectAction(ROTATION);
664 }
Chihhang Chuang85f099a2020-06-16 18:04:39 +0800665 }
666
667 @Override
Jon Miranda16ea1b12017-12-12 14:52:48 -0800668 public void retryStartRotation(@NetworkPreference int networkPreference) {
669 startRotation(networkPreference);
670 }
671
chihhangchuang7feb3752020-04-24 02:48:56 +0800672 @Override
673 public boolean onBackPressed() {
674 if (mSelectedWallpaperInfo != null) {
675 onWallpaperSelected(null, 0);
676 return true;
677 }
678 return false;
679 }
680
Jon Miranda16ea1b12017-12-12 14:52:48 -0800681 public void setCurrentWallpaperBottomSheetPresenter(
682 CurrentWallpaperBottomSheetPresenter presenter) {
683 mCurrentWallpaperBottomSheetPresenter = presenter;
684 }
685
686 public void setWallpapersUiContainer(WallpapersUiContainer uiContainer) {
687 mWallpapersUiContainer = uiContainer;
688 }
689
Chuck Liaob3829fb2020-04-01 00:47:50 +0800690 public void setOnWallpaperSelectedListener(
691 WallpaperSelectedListener wallpaperSelectedListener) {
692 mWallpaperSelectedListener = wallpaperSelectedListener;
693 }
694
695 /**
696 * Resizes the layout's height.
697 */
698 public void resizeLayout(int height) {
699 mImageGrid.getLayoutParams().height = height;
700 mImageGrid.requestLayout();
701 }
702
703 /**
704 * Scrolls to the specific item.
705 *
706 * @param position the position of the item
707 */
708 public void scrollToPosition(int position) {
709 ((GridLayoutManager) mImageGrid.getLayoutManager())
710 .scrollToPositionWithOffset(position, /* offset= */ 0);
711 }
712
Jon Miranda16ea1b12017-12-12 14:52:48 -0800713 /**
714 * Enable a test mode of operation -- in which certain UI features are disabled to allow for
715 * UI tests to run correctly. Works around issue in ProgressDialog currently where the dialog
716 * constantly keeps the UI thread alive and blocks a test forever.
717 *
718 * @param testingMode
719 */
720 void setTestingMode(boolean testingMode) {
721 mTestingMode = testingMode;
722 }
723
Jon Miranda16ea1b12017-12-12 14:52:48 -0800724 @Override
725 public void startRotation(@NetworkPreference final int networkPreference) {
726 if (!isRotationEnabled()) {
727 Log.e(TAG, "Rotation is not enabled for this category " + mCategory.getTitle());
728 return;
729 }
730
731 // ProgressDialog endlessly updates the UI thread, keeping it from going idle which therefore
732 // causes Espresso to hang once the dialog is shown.
733 if (mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE && !mTestingMode) {
734 int themeResId;
735 if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
736 themeResId = R.style.ProgressDialogThemePreL;
737 } else {
738 themeResId = R.style.LightDialogTheme;
739 }
740 mProgressDialog = new ProgressDialog(getActivity(), themeResId);
741
742 mProgressDialog.setTitle(PROGRESS_DIALOG_NO_TITLE);
743 mProgressDialog.setMessage(
744 getResources().getString(R.string.start_rotation_progress_message));
745 mProgressDialog.setIndeterminate(PROGRESS_DIALOG_INDETERMINATE);
746 mProgressDialog.show();
747 }
748
749 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
750 mAdapter.mPendingSelectedAdapterPosition = SPECIAL_FIXED_TILE_ADAPTER_POSITION;
751 }
752
753 final Context appContext = getActivity().getApplicationContext();
754
755 mWallpaperRotationInitializer.setFirstWallpaperInRotation(
756 appContext,
757 networkPreference,
758 new Listener() {
759 @Override
760 public void onFirstWallpaperInRotationSet() {
761 if (mProgressDialog != null) {
762 mProgressDialog.dismiss();
763 }
764
765 // The fragment may be detached from its containing activity if the user exits the
766 // app before the first wallpaper image in rotation finishes downloading.
767 Activity activity = getActivity();
768
Jon Miranda16ea1b12017-12-12 14:52:48 -0800769
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700770 if (mWallpaperRotationInitializer.startRotation(appContext)) {
771 if (activity != null
772 && mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
773 try {
774 Toast.makeText(getActivity(),
775 R.string.wallpaper_set_successfully_message,
776 Toast.LENGTH_SHORT).show();
777 } catch (NotFoundException e) {
778 Log.e(TAG, "Could not show toast " + e);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800779 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800780
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700781 activity.setResult(Activity.RESULT_OK);
782 activity.finish();
783 } else if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
784 mAdapter.updateSelectedTile(SPECIAL_FIXED_TILE_ADAPTER_POSITION);
785 }
786 } else { // Failed to start rotation.
787 showStartRotationErrorDialog(networkPreference);
788
789 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
790 DesktopRotationHolder rotationViewHolder =
791 (DesktopRotationHolder)
792 mImageGrid.findViewHolderForAdapterPosition(
793 SPECIAL_FIXED_TILE_ADAPTER_POSITION);
794 rotationViewHolder.setSelectionState(
795 SelectableHolder.SELECTION_STATE_DESELECTED);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800796 }
797 }
798 }
799
800 @Override
801 public void onError() {
802 if (mProgressDialog != null) {
803 mProgressDialog.dismiss();
804 }
805
806 showStartRotationErrorDialog(networkPreference);
807
808 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
809 DesktopRotationHolder rotationViewHolder =
810 (DesktopRotationHolder) mImageGrid.findViewHolderForAdapterPosition(
811 SPECIAL_FIXED_TILE_ADAPTER_POSITION);
812 rotationViewHolder.setSelectionState(SelectableHolder.SELECTION_STATE_DESELECTED);
813 }
814 }
815 });
816 }
817
818 private void showStartRotationErrorDialog(@NetworkPreference int networkPreference) {
819 BaseActivity activity = (BaseActivity) getActivity();
820 if (activity != null) {
821 StartRotationErrorDialogFragment startRotationErrorDialogFragment =
822 StartRotationErrorDialogFragment.newInstance(networkPreference);
823 startRotationErrorDialogFragment.setTargetFragment(
824 IndividualPickerFragment.this, UNUSED_REQUEST_CODE);
825
826 if (activity.isSafeToCommitFragmentTransaction()) {
827 startRotationErrorDialogFragment.show(
828 getFragmentManager(), TAG_START_ROTATION_ERROR_DIALOG);
829 } else {
830 mStagedStartRotationErrorDialogFragment = startRotationErrorDialogFragment;
831 }
832 }
833 }
834
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800835 int getNumColumns() {
Ching-Sung Li9df77a32019-07-10 11:45:30 +0800836 Activity activity = getActivity();
Chuck Liaoe2fe0302020-06-29 21:15:35 +0800837 return activity == null ? 1 : SizeCalculator.getNumIndividualColumns(activity);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800838 }
839
840 /**
841 * Returns whether rotation is enabled for this category.
842 */
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +0800843 boolean isRotationEnabled() {
Samuel Fufa03bc6ac2019-09-19 12:01:50 -0700844 return mWallpaperRotationInitializer != null;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800845 }
846
847 @Override
848 public void onCurrentWallpaperRefreshed() {
849 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(true);
850 }
851
Chuck Liao69630f12020-03-05 19:01:25 +0800852 @Override
853 public void onSet(int destination) {
854 if (mSelectedWallpaperInfo == null) {
855 Log.e(TAG, "Unable to set wallpaper since the selected wallpaper info is null");
856 return;
857 }
858
“Chuckffd832c2020-03-22 02:15:58 +0800859 mWallpaperPersister.setWallpaperInfoInPreview(mSelectedWallpaperInfo);
Chuck Liao69630f12020-03-05 19:01:25 +0800860 if (mSelectedWallpaperInfo instanceof LiveWallpaperInfo) {
861 mWallpaperSetter.setCurrentWallpaper(getActivity(), mSelectedWallpaperInfo, null,
862 destination, 0, null, mSetWallpaperCallback);
863 } else {
864 mWallpaperSetter.setCurrentWallpaper(
865 getActivity(), mSelectedWallpaperInfo, destination, mSetWallpaperCallback);
866 }
“Chuck7ef99722020-03-22 04:34:03 +0800867 onWallpaperDestinationSet(destination);
Chuck Liao69630f12020-03-05 19:01:25 +0800868 }
869
870 private WallpaperPersister.SetWallpaperCallback mSetWallpaperCallback =
871 new WallpaperPersister.SetWallpaperCallback() {
872 @Override
“Chuckffd832c2020-03-22 02:15:58 +0800873 public void onSuccess(WallpaperInfo wallpaperInfo) {
chihhangchuang210c9602020-06-02 12:38:14 +0800874 mWallpaperPersister.onLiveWallpaperSet();
Chuck Liao6e52fff2020-04-23 16:11:23 +0800875 Toast.makeText(getActivity(), R.string.wallpaper_set_successfully_message,
876 Toast.LENGTH_SHORT).show();
chihhangchuang210c9602020-06-02 12:38:14 +0800877 getActivity().overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
878 getActivity().finish();
Chuck Liao69630f12020-03-05 19:01:25 +0800879 }
880
881 @Override
882 public void onError(@Nullable Throwable throwable) {
883 Log.e(TAG, "Can't apply the wallpaper.");
884 mBottomActionBar.enableActions();
885 }
886 };
887
888 @Override
889 public void onDialogDismissed(boolean withItemSelected) {
890 if (!withItemSelected) {
891 mBottomActionBar.enableActions();
892 }
893 }
894
Jon Miranda16ea1b12017-12-12 14:52:48 -0800895 /**
896 * Shows a "set wallpaper" error dialog with a failure message and button to try again.
897 */
898 private void showSetWallpaperErrorDialog() {
899 SetWallpaperErrorDialogFragment dialogFragment = SetWallpaperErrorDialogFragment.newInstance(
900 R.string.set_wallpaper_error_message, WallpaperPersister.DEST_BOTH);
901 dialogFragment.setTargetFragment(this, UNUSED_REQUEST_CODE);
902
903 if (((BaseActivity) getActivity()).isSafeToCommitFragmentTransaction()) {
904 dialogFragment.show(getFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
905 } else {
906 mStagedSetWallpaperErrorDialogFragment = dialogFragment;
907 }
908 }
909
Ching-Sung Li073812b2020-04-07 21:19:21 +0800910 void updateBottomActions(boolean hasWallpaperSelected) {
chihhangchuang803ea9a2020-04-21 13:03:10 +0800911 if (hasWallpaperSelected) {
Chuck Liao7dc21572020-05-14 18:37:36 +0800912 mBottomActionBar.showActionsOnly(INFORMATION, EDIT, APPLY);
chihhangchuang803ea9a2020-04-21 13:03:10 +0800913 } else {
chihhangchuang08abb582020-04-27 17:20:31 +0800914 mBottomActionBar.showActionsOnly(ROTATION);
chihhangchuang803ea9a2020-04-21 13:03:10 +0800915 }
Chuck Liao69630f12020-03-05 19:01:25 +0800916 }
917
918 private void updateThumbnail(WallpaperInfo selectedWallpaperInfo) {
Chuck Liaof40063f2020-03-03 18:35:24 +0800919 ThumbnailUpdater thumbnailUpdater = (ThumbnailUpdater) getParentFragment();
Chuck Liao69630f12020-03-05 19:01:25 +0800920 if (thumbnailUpdater == null) {
921 return;
922 }
923
924 if (selectedWallpaperInfo != null) {
925 thumbnailUpdater.updateThumbnail(selectedWallpaperInfo);
926 } else {
927 thumbnailUpdater.restoreThumbnails();
Chuck Liaof40063f2020-03-03 18:35:24 +0800928 }
929 }
930
“Chuck7ef99722020-03-22 04:34:03 +0800931 private void onWallpaperDestinationSet(int destination) {
932 WallpaperDestinationCallback wallpaperDestinationCallback =
933 (WallpaperDestinationCallback) getParentFragment();
934 if (wallpaperDestinationCallback == null) {
935 return;
936 }
937
938 wallpaperDestinationCallback.onDestinationSet(destination);
939 }
940
Ching-Sung Li073812b2020-04-07 21:19:21 +0800941 void onWallpaperSelected(@Nullable WallpaperInfo newSelectedWallpaperInfo,
Chuck Liaob3829fb2020-04-01 00:47:50 +0800942 int position) {
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800943 if (mSelectedWallpaperInfo == newSelectedWallpaperInfo) {
“Chuckffd832c2020-03-22 02:15:58 +0800944 return;
945 }
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800946 // Update current wallpaper.
“Chuckffd832c2020-03-22 02:15:58 +0800947 updateActivatedStatus(mSelectedWallpaperInfo == null
948 ? mAppliedWallpaperInfo : mSelectedWallpaperInfo, false);
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800949 // Update new selected wallpaper.
950 updateActivatedStatus(newSelectedWallpaperInfo == null
951 ? mAppliedWallpaperInfo : newSelectedWallpaperInfo, true);
952
953 mSelectedWallpaperInfo = newSelectedWallpaperInfo;
954 updateBottomActions(mSelectedWallpaperInfo != null);
955 updateThumbnail(mSelectedWallpaperInfo);
chihhangchuangde9e8722020-05-02 17:22:13 +0800956 // Populate wallpaper info into view.
chihhangchuang1a29e752020-04-28 18:22:53 +0800957 if (mSelectedWallpaperInfo != null && mWallpaperInfoView != null) {
chihhangchuangc687d912020-05-04 14:33:05 +0800958 WallpaperInfoHelper.loadExploreIntent(
959 getContext(),
960 mSelectedWallpaperInfo,
961 (actionLabel, exploreIntent) ->
962 mWallpaperInfoView.populateWallpaperInfo(
963 mSelectedWallpaperInfo, actionLabel, exploreIntent,
964 v -> onExploreClicked(exploreIntent)));
Chuck Liaob3829fb2020-04-01 00:47:50 +0800965 }
966
967 if (mWallpaperSelectedListener != null) {
968 mWallpaperSelectedListener.onWallpaperSelected(position);
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800969 }
Chuck Liaof40063f2020-03-03 18:35:24 +0800970 }
971
chihhangchuangc687d912020-05-04 14:33:05 +0800972 private void onExploreClicked(Intent exploreIntent) {
973 if (getContext() == null) {
974 return;
975 }
976 Context context = getContext();
977 mUserEventLogger.logActionClicked(mSelectedWallpaperInfo.getCollectionId(context),
978 mSelectedWallpaperInfo.getActionLabelRes(context));
979
980 startActivity(exploreIntent);
981 }
982
Chuck Liao6a37a1c2020-03-07 03:39:43 +0800983 private void updateActivatedStatus(WallpaperInfo wallpaperInfo, boolean isActivated) {
“Chuckffd832c2020-03-22 02:15:58 +0800984 if (wallpaperInfo == null) {
985 return;
986 }
Chuck Liao6a37a1c2020-03-07 03:39:43 +0800987 int index = mWallpapers.indexOf(wallpaperInfo);
Chuck Liao5a4243b2020-05-20 23:56:39 +0800988 index = (shouldShowRotationTile() || mCategory.supportsCustomPhotos())
Chuck Liao6a37a1c2020-03-07 03:39:43 +0800989 ? index + 1 : index;
990 ViewHolder holder = mImageGrid.findViewHolderForAdapterPosition(index);
991 if (holder != null) {
992 holder.itemView.setActivated(isActivated);
993 } else {
994 // Item is not visible, make sure the item is re-bound when it becomes visible.
995 mAdapter.notifyItemChanged(index);
996 }
997 }
998
“Chuckffd832c2020-03-22 02:15:58 +0800999 private void updateAppliedStatus(WallpaperInfo wallpaperInfo, boolean isApplied) {
1000 if (wallpaperInfo == null) {
1001 return;
1002 }
1003 int index = mWallpapers.indexOf(wallpaperInfo);
Chuck Liao5a4243b2020-05-20 23:56:39 +08001004 index = (shouldShowRotationTile() || mCategory.supportsCustomPhotos())
“Chuckffd832c2020-03-22 02:15:58 +08001005 ? index + 1 : index;
1006 ViewHolder holder = mImageGrid.findViewHolderForAdapterPosition(index);
1007 if (holder != null) {
1008 holder.itemView.findViewById(R.id.check_circle)
1009 .setVisibility(isApplied ? View.VISIBLE : View.GONE);
1010 } else {
1011 // Item is not visible, make sure the item is re-bound when it becomes visible.
1012 mAdapter.notifyItemChanged(index);
1013 }
1014 }
1015
Chuck Liao11f4a762020-04-08 13:24:43 +08001016 private void refreshAppliedWallpaper() {
1017 // Clear the check mark and blue border(if it shows) of the old applied wallpaper.
1018 showCheckMarkAndBorderForAppliedWallpaper(false);
1019
1020 // Update to the new applied wallpaper.
1021 String appliedWallpaperId = getAppliedWallpaperId();
1022 Optional<WallpaperInfo> wallpaperInfoOptional = mWallpapers
1023 .stream()
1024 .filter(wallpaper -> wallpaper.getWallpaperId() != null)
1025 .filter(wallpaper -> wallpaper.getWallpaperId().equals(appliedWallpaperId))
1026 .findFirst();
1027 mAppliedWallpaperInfo = wallpaperInfoOptional.orElse(null);
1028
1029 // Set the check mark and blue border(if user doesn't select) of the new applied wallpaper.
1030 showCheckMarkAndBorderForAppliedWallpaper(true);
1031 }
1032
1033 private String getAppliedWallpaperId() {
1034 WallpaperPreferences prefs =
1035 InjectorProvider.getInjector().getPreferences(getContext());
1036 android.app.WallpaperInfo wallpaperInfo = mWallpaperManager.getWallpaperInfo();
1037 boolean isDestinationBoth =
1038 mWallpaperManager.getWallpaperId(WallpaperManager.FLAG_LOCK) < 0;
1039
1040 if (isDestinationBoth || mWallpaperDestination == WallpaperPersister.DEST_HOME_SCREEN) {
1041 return wallpaperInfo != null
1042 ? wallpaperInfo.getServiceName() : prefs.getHomeWallpaperRemoteId();
1043 } else {
1044 return prefs.getLockWallpaperRemoteId();
1045 }
1046 }
1047
1048 private void showCheckMarkAndBorderForAppliedWallpaper(boolean show) {
1049 updateAppliedStatus(mAppliedWallpaperInfo, show);
1050 if (mSelectedWallpaperInfo == null) {
1051 updateActivatedStatus(mAppliedWallpaperInfo, show);
1052 }
1053 }
1054
Chuck Liao5a4243b2020-05-20 23:56:39 +08001055 private boolean shouldShowRotationTile() {
1056 return mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP && isRotationEnabled();
Jon Miranda16ea1b12017-12-12 14:52:48 -08001057 }
1058
1059 /**
1060 * RecyclerView Adapter subclass for the wallpaper tiles in the RecyclerView.
1061 */
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +08001062 class IndividualAdapter extends RecyclerView.Adapter<ViewHolder> {
1063 static final int ITEM_VIEW_TYPE_ROTATION = 1;
1064 static final int ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER = 2;
1065 static final int ITEM_VIEW_TYPE_MY_PHOTOS = 3;
Jon Miranda16ea1b12017-12-12 14:52:48 -08001066
1067 private final List<WallpaperInfo> mWallpapers;
1068
1069 private int mPendingSelectedAdapterPosition;
1070 private int mSelectedAdapterPosition;
1071
1072 IndividualAdapter(List<WallpaperInfo> wallpapers) {
1073 mWallpapers = wallpapers;
1074 mPendingSelectedAdapterPosition = -1;
1075 mSelectedAdapterPosition = -1;
1076 }
1077
1078 @Override
1079 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
1080 switch (viewType) {
1081 case ITEM_VIEW_TYPE_ROTATION:
1082 return createRotationHolder(parent);
1083 case ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER:
1084 return createIndividualHolder(parent);
1085 case ITEM_VIEW_TYPE_MY_PHOTOS:
1086 return createMyPhotosHolder(parent);
1087 default:
1088 Log.e(TAG, "Unsupported viewType " + viewType + " in IndividualAdapter");
1089 return null;
1090 }
1091 }
1092
1093 @Override
1094 public int getItemViewType(int position) {
Chuck Liao5a4243b2020-05-20 23:56:39 +08001095 if (shouldShowRotationTile() && position == SPECIAL_FIXED_TILE_ADAPTER_POSITION) {
Jon Miranda16ea1b12017-12-12 14:52:48 -08001096 return ITEM_VIEW_TYPE_ROTATION;
1097 }
1098
1099 // A category cannot have both a "start rotation" tile and a "my photos" tile.
1100 if (mCategory.supportsCustomPhotos()
1101 && !isRotationEnabled()
1102 && position == SPECIAL_FIXED_TILE_ADAPTER_POSITION) {
1103 return ITEM_VIEW_TYPE_MY_PHOTOS;
1104 }
1105
1106 return ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER;
1107 }
1108
1109 @Override
1110 public void onBindViewHolder(ViewHolder holder, int position) {
1111 int viewType = getItemViewType(position);
1112
1113 switch (viewType) {
1114 case ITEM_VIEW_TYPE_ROTATION:
1115 onBindRotationHolder(holder, position);
1116 break;
1117 case ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER:
1118 onBindIndividualHolder(holder, position);
1119 break;
1120 case ITEM_VIEW_TYPE_MY_PHOTOS:
1121 ((MyPhotosViewHolder) holder).bind();
1122 break;
1123 default:
1124 Log.e(TAG, "Unsupported viewType " + viewType + " in IndividualAdapter");
1125 }
1126 }
1127
1128 @Override
1129 public int getItemCount() {
Chuck Liao5a4243b2020-05-20 23:56:39 +08001130 return (shouldShowRotationTile() || mCategory.supportsCustomPhotos())
Jon Miranda16ea1b12017-12-12 14:52:48 -08001131 ? mWallpapers.size() + 1
1132 : mWallpapers.size();
1133 }
1134
1135 private ViewHolder createRotationHolder(ViewGroup parent) {
1136 LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
Chuck Liao5a4243b2020-05-20 23:56:39 +08001137 View view = layoutInflater.inflate(R.layout.grid_item_rotation_desktop, parent, false);
1138 SelectionAnimator selectionAnimator =
1139 new CheckmarkSelectionAnimator(getActivity(), view);
1140 return new DesktopRotationHolder(getActivity(), mTileSizePx.y, view, selectionAnimator,
1141 IndividualPickerFragment.this);
Jon Miranda16ea1b12017-12-12 14:52:48 -08001142 }
1143
1144 private ViewHolder createIndividualHolder(ViewGroup parent) {
1145 LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
1146 View view = layoutInflater.inflate(R.layout.grid_item_image, parent, false);
1147
1148 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
1149 SelectionAnimator selectionAnimator =
1150 new CheckmarkSelectionAnimator(getActivity(), view);
1151 return new SetIndividualHolder(
1152 getActivity(), mTileSizePx.y, view,
1153 selectionAnimator,
1154 new OnSetListener() {
1155 @Override
1156 public void onPendingWallpaperSet(int adapterPosition) {
1157 // Deselect and hide loading indicator for any previously pending tile.
1158 if (mPendingSelectedAdapterPosition != -1) {
1159 ViewHolder oldViewHolder = mImageGrid.findViewHolderForAdapterPosition(
1160 mPendingSelectedAdapterPosition);
1161 if (oldViewHolder instanceof SelectableHolder) {
1162 ((SelectableHolder) oldViewHolder).setSelectionState(
1163 SelectableHolder.SELECTION_STATE_DESELECTED);
1164 }
1165 }
1166
1167 if (mSelectedAdapterPosition != -1) {
1168 ViewHolder oldViewHolder = mImageGrid.findViewHolderForAdapterPosition(
1169 mSelectedAdapterPosition);
1170 if (oldViewHolder instanceof SelectableHolder) {
1171 ((SelectableHolder) oldViewHolder).setSelectionState(
1172 SelectableHolder.SELECTION_STATE_DESELECTED);
1173 }
1174 }
1175
1176 mPendingSelectedAdapterPosition = adapterPosition;
1177 }
1178
1179 @Override
1180 public void onWallpaperSet(int adapterPosition) {
1181 // No-op -- UI handles a new wallpaper being set by reacting to the
1182 // WallpaperChangedNotifier.
1183 }
1184
1185 @Override
1186 public void onWallpaperSetFailed(SetIndividualHolder holder) {
1187 showSetWallpaperErrorDialog();
1188 mPendingSetIndividualHolder = holder;
1189 }
1190 });
1191 } else { // MOBILE
Chuck Liao3d1a51c2020-02-17 18:29:34 +08001192 return new PreviewIndividualHolder(getActivity(), mTileSizePx.y, view);
Jon Miranda16ea1b12017-12-12 14:52:48 -08001193 }
1194 }
1195
1196 private ViewHolder createMyPhotosHolder(ViewGroup parent) {
1197 LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
1198 View view = layoutInflater.inflate(R.layout.grid_item_my_photos, parent, false);
1199
Santiago Etchebeherefab49612019-01-15 12:22:42 -08001200 return new MyPhotosViewHolder(getActivity(),
1201 ((MyPhotosStarterProvider) getActivity()).getMyPhotosStarter(),
1202 mTileSizePx.y, view);
Jon Miranda16ea1b12017-12-12 14:52:48 -08001203 }
1204
1205 /**
1206 * Marks the tile at the given position as selected with a visual indication. Also updates the
1207 * "currently selected" BottomSheet to reflect the newly selected tile.
1208 */
1209 private void updateSelectedTile(int newlySelectedPosition) {
1210 // Prevent multiple spinners from appearing with a user tapping several tiles in rapid
1211 // succession.
1212 if (mPendingSelectedAdapterPosition == mSelectedAdapterPosition) {
1213 return;
1214 }
1215
1216 if (mCurrentWallpaperBottomSheetPresenter != null) {
1217 mCurrentWallpaperBottomSheetPresenter.refreshCurrentWallpapers(
1218 IndividualPickerFragment.this);
1219
1220 if (mCurrentWallpaperBottomSheetExpandedRunnable != null) {
1221 mHandler.removeCallbacks(mCurrentWallpaperBottomSheetExpandedRunnable);
1222 }
1223 mCurrentWallpaperBottomSheetExpandedRunnable = new Runnable() {
1224 @Override
1225 public void run() {
1226 mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(true);
1227 }
1228 };
1229 mHandler.postDelayed(mCurrentWallpaperBottomSheetExpandedRunnable, 100);
1230 }
1231
1232 // User may have switched to another category, thus detaching this fragment, so check here.
1233 // NOTE: We do this check after updating the current wallpaper BottomSheet so that the update
1234 // still occurs in the UI after the user selects that other category.
1235 if (getActivity() == null) {
1236 return;
1237 }
1238
1239 // Update the newly selected wallpaper ViewHolder and the old one so that if
1240 // selection UI state applies (desktop UI), it is updated.
1241 if (mSelectedAdapterPosition >= 0) {
1242 ViewHolder oldViewHolder = mImageGrid.findViewHolderForAdapterPosition(
1243 mSelectedAdapterPosition);
1244 if (oldViewHolder instanceof SelectableHolder) {
1245 ((SelectableHolder) oldViewHolder).setSelectionState(
1246 SelectableHolder.SELECTION_STATE_DESELECTED);
1247 }
1248 }
1249
1250 // Animate selection of newly selected tile.
1251 ViewHolder newViewHolder = mImageGrid
1252 .findViewHolderForAdapterPosition(newlySelectedPosition);
1253 if (newViewHolder instanceof SelectableHolder) {
1254 ((SelectableHolder) newViewHolder).setSelectionState(
1255 SelectableHolder.SELECTION_STATE_SELECTED);
1256 }
1257
1258 mSelectedAdapterPosition = newlySelectedPosition;
1259
1260 // If the tile was in the last row of the grid, add space below it so the user can scroll down
1261 // and up to see the BottomSheet without it fully overlapping the newly selected tile.
1262 int spanCount = ((GridLayoutManager) mImageGrid.getLayoutManager()).getSpanCount();
1263 int numRows = (int) Math.ceil((float) getItemCount() / spanCount);
1264 int rowOfNewlySelectedTile = newlySelectedPosition / spanCount;
1265 boolean isInLastRow = rowOfNewlySelectedTile == numRows - 1;
1266
1267 updateImageGridPadding(isInLastRow /* addExtraBottomSpace */);
1268 }
1269
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +08001270 void onBindRotationHolder(ViewHolder holder, int position) {
Jon Miranda16ea1b12017-12-12 14:52:48 -08001271 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
1272 String collectionId = mCategory.getCollectionId();
1273 ((DesktopRotationHolder) holder).bind(collectionId);
1274
1275 if (mWallpaperPreferences.getWallpaperPresentationMode()
1276 == WallpaperPreferences.PRESENTATION_MODE_ROTATING
1277 && collectionId.equals(mWallpaperPreferences.getHomeWallpaperCollectionId())) {
1278 mSelectedAdapterPosition = position;
1279 }
1280
1281 if (!mWasUpdateRunnableRun && !mWallpapers.isEmpty()) {
1282 updateDesktopDailyRotationThumbnail((DesktopRotationHolder) holder);
1283 mWasUpdateRunnableRun = true;
1284 }
1285 }
1286 }
1287
Ching-Sung Li31fbe5e2019-01-23 19:36:24 +08001288 void onBindIndividualHolder(ViewHolder holder, int position) {
Chuck Liao5a4243b2020-05-20 23:56:39 +08001289 int wallpaperIndex = (shouldShowRotationTile() || mCategory.supportsCustomPhotos())
Jon Miranda16ea1b12017-12-12 14:52:48 -08001290 ? position - 1 : position;
1291 WallpaperInfo wallpaper = mWallpapers.get(wallpaperIndex);
1292 ((IndividualHolder) holder).bindWallpaper(wallpaper);
“Chuck7ef99722020-03-22 04:34:03 +08001293 String appliedWallpaperId = getAppliedWallpaperId();
“Chuckffd832c2020-03-22 02:15:58 +08001294 boolean isWallpaperApplied = wallpaper.getWallpaperId().equals(appliedWallpaperId);
1295 boolean isWallpaperSelected = wallpaper.equals(mSelectedWallpaperInfo);
1296 boolean hasUserSelectedWallpaper = mSelectedWallpaperInfo != null;
Jon Miranda16ea1b12017-12-12 14:52:48 -08001297
“Chuckffd832c2020-03-22 02:15:58 +08001298 if (isWallpaperApplied) {
Jon Miranda16ea1b12017-12-12 14:52:48 -08001299 mSelectedAdapterPosition = position;
“Chuckffd832c2020-03-22 02:15:58 +08001300 mAppliedWallpaperInfo = wallpaper;
Jon Miranda16ea1b12017-12-12 14:52:48 -08001301 }
Chuck Liaof40063f2020-03-03 18:35:24 +08001302
Chuck Liao342f4ab2020-05-26 18:28:09 +08001303 holder.itemView.setActivated(
1304 (isWallpaperApplied && !hasUserSelectedWallpaper) || isWallpaperSelected);
1305 holder.itemView.findViewById(R.id.check_circle).setVisibility(
1306 isWallpaperApplied ? View.VISIBLE : View.GONE);
Jon Miranda16ea1b12017-12-12 14:52:48 -08001307 }
1308 }
Chuck Liaoba401232020-03-13 20:11:04 +08001309
1310 private class GridPaddingDecoration extends RecyclerView.ItemDecoration {
1311
1312 private int mPadding;
1313
1314 GridPaddingDecoration(int padding) {
1315 mPadding = padding;
1316 }
1317
1318 @Override
1319 public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
1320 RecyclerView.State state) {
1321 int position = parent.getChildAdapterPosition(view);
1322 if (position >= 0) {
1323 outRect.left = mPadding;
1324 outRect.right = mPadding;
1325 }
1326 }
1327 }
Jon Miranda16ea1b12017-12-12 14:52:48 -08001328}