Initial AOSP WallpaperPicker2 commit.
Change-Id: If8770266dd3ff0d7dc3ee97820e815e39312a7ff
diff --git a/src/com/android/wallpaper/picker/individual/IndividualPickerFragment.java b/src/com/android/wallpaper/picker/individual/IndividualPickerFragment.java
new file mode 100755
index 0000000..7b579ab
--- /dev/null
+++ b/src/com/android/wallpaper/picker/individual/IndividualPickerFragment.java
@@ -0,0 +1,959 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wallpaper.picker.individual;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.res.Resources.NotFoundException;
+import android.graphics.Point;
+import android.graphics.PorterDuff.Mode;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.Fragment;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.OnScrollListener;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.wallpaper.R;
+import com.android.wallpaper.asset.Asset;
+import com.android.wallpaper.asset.Asset.DrawableLoadedListener;
+import com.android.wallpaper.config.Flags;
+import com.android.wallpaper.model.WallpaperCategory;
+import com.android.wallpaper.model.WallpaperInfo;
+import com.android.wallpaper.model.WallpaperReceiver;
+import com.android.wallpaper.model.WallpaperRotationInitializer;
+import com.android.wallpaper.model.WallpaperRotationInitializer.Listener;
+import com.android.wallpaper.model.WallpaperRotationInitializer.NetworkPreference;
+import com.android.wallpaper.model.WallpaperRotationInitializer.RotationInitializationState;
+import com.android.wallpaper.model.WallpaperRotationInitializer.RotationStateListener;
+import com.android.wallpaper.module.FormFactorChecker;
+import com.android.wallpaper.module.FormFactorChecker.FormFactor;
+import com.android.wallpaper.module.Injector;
+import com.android.wallpaper.module.InjectorProvider;
+import com.android.wallpaper.module.RotatingWallpaperComponentChecker;
+import com.android.wallpaper.module.WallpaperChangedNotifier;
+import com.android.wallpaper.module.WallpaperPersister;
+import com.android.wallpaper.module.WallpaperPersister.Destination;
+import com.android.wallpaper.module.WallpaperPreferences;
+import com.android.wallpaper.picker.BaseActivity;
+import com.android.wallpaper.picker.CurrentWallpaperBottomSheetPresenter;
+import com.android.wallpaper.picker.RotationStarter;
+import com.android.wallpaper.picker.SetWallpaperErrorDialogFragment;
+import com.android.wallpaper.picker.StartRotationDialogFragment;
+import com.android.wallpaper.picker.StartRotationErrorDialogFragment;
+import com.android.wallpaper.picker.WallpapersUiContainer;
+import com.android.wallpaper.picker.individual.SetIndividualHolder.OnSetListener;
+import com.android.wallpaper.util.DiskBasedLogger;
+import com.android.wallpaper.util.TileSizeCalculator;
+import com.android.wallpaper.widget.GridMarginDecoration;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.MemoryCategory;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Displays the Main UI for picking an individual wallpaper image.
+ */
+public class IndividualPickerFragment extends Fragment
+ implements RotationStarter, StartRotationErrorDialogFragment.Listener,
+ CurrentWallpaperBottomSheetPresenter.RefreshListener,
+ SetWallpaperErrorDialogFragment.Listener {
+ private static final String TAG = "IndividualPickerFrgmnt";
+ private static final String ARG_CATEGORY_COLLECTION_ID = "category_collection_id";
+ private static final int UNUSED_REQUEST_CODE = 1;
+ private static final String TAG_START_ROTATION_DIALOG = "start_rotation_dialog";
+ private static final String TAG_START_ROTATION_ERROR_DIALOG = "start_rotation_error_dialog";
+ private static final String PROGRESS_DIALOG_NO_TITLE = null;
+ private static final boolean PROGRESS_DIALOG_INDETERMINATE = true;
+ private static final String TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT =
+ "individual_set_wallpaper_error_dialog";
+
+ /**
+ * Position of a special tile that doesn't belong to an individual wallpaper of the category,
+ * such as "my photos" or "daily rotation".
+ */
+ private static final int SPECIAL_FIXED_TILE_ADAPTER_POSITION = 0;
+
+ private WallpaperPreferences mWallpaperPreferences;
+ private WallpaperChangedNotifier mWallpaperChangedNotifier;
+ private RotatingWallpaperComponentChecker mRotatingWallpaperComponentChecker;
+ private RecyclerView mImageGrid;
+ private IndividualAdapter mAdapter;
+ private WallpaperCategory mCategory;
+ private WallpaperRotationInitializer mWallpaperRotationInitializer;
+ private List<WallpaperInfo> mWallpapers;
+ private Point mTileSizePx;
+ private ProgressDialog mProgressDialog;
+ private boolean mTestingMode;
+ private CurrentWallpaperBottomSheetPresenter mCurrentWallpaperBottomSheetPresenter;
+ private WallpapersUiContainer mWallpapersUiContainer;
+ @FormFactor
+ private int mFormFactor;
+ private SetIndividualHolder mPendingSetIndividualHolder;
+
+ /**
+ * Staged error dialog fragments that were unable to be shown when the activity didn't allow
+ * committing fragment transactions.
+ */
+ private SetWallpaperErrorDialogFragment mStagedSetWallpaperErrorDialogFragment;
+ private StartRotationErrorDialogFragment mStagedStartRotationErrorDialogFragment;
+
+ private Handler mHandler;
+ private Runnable mCurrentWallpaperBottomSheetExpandedRunnable;
+ private Random mRandom;
+
+ /**
+ * Whether {@code mUpdateDailyWallpaperThumbRunnable} has been run at least once in this
+ * invocation of the fragment.
+ */
+ private boolean mWasUpdateRunnableRun;
+
+ /**
+ * A Runnable which regularly updates the thumbnail for the "Daily wallpapers" tile in desktop
+ * mode.
+ */
+ private Runnable mUpdateDailyWallpaperThumbRunnable = new Runnable() {
+ @Override
+ public void run() {
+ ViewHolder viewHolder = mImageGrid.findViewHolderForAdapterPosition(
+ SPECIAL_FIXED_TILE_ADAPTER_POSITION);
+ if (viewHolder instanceof DesktopRotationHolder) {
+ updateDesktopDailyRotationThumbnail((DesktopRotationHolder) viewHolder);
+ } else { // viewHolder is null
+ // If the rotation tile is unavailable (because user has scrolled down, causing the
+ // ViewHolder to be recycled), schedule the update for some time later. Once user scrolls up
+ // again, the ViewHolder will be re-bound and its thumbnail will be updated.
+ mHandler.postDelayed(mUpdateDailyWallpaperThumbRunnable,
+ DesktopRotationHolder.CROSSFADE_DURATION_MILLIS
+ + DesktopRotationHolder.CROSSFADE_DURATION_PAUSE_MILLIS);
+ }
+ }
+ };
+
+ private WallpaperChangedNotifier.Listener mWallpaperChangedListener = new WallpaperChangedNotifier.Listener() {
+ @Override
+ public void onWallpaperChanged() {
+ if (mFormFactor != FormFactorChecker.FORM_FACTOR_DESKTOP) {
+ return;
+ }
+
+ ViewHolder selectedViewHolder = mImageGrid.findViewHolderForAdapterPosition(
+ mAdapter.mSelectedAdapterPosition);
+
+ // Null remote ID => My Photos wallpaper, so deselect whatever was previously selected.
+ if (mWallpaperPreferences.getHomeWallpaperRemoteId() == null) {
+ if (selectedViewHolder instanceof SelectableHolder) {
+ ((SelectableHolder) selectedViewHolder).setSelectionState(
+ SelectableHolder.SELECTION_STATE_DESELECTED);
+ }
+ } else {
+ mAdapter.updateSelectedTile(mAdapter.mPendingSelectedAdapterPosition);
+ }
+ }
+ };
+
+ public static IndividualPickerFragment newInstance(String collectionId) {
+ Bundle args = new Bundle();
+ args.putString(ARG_CATEGORY_COLLECTION_ID, collectionId);
+
+ IndividualPickerFragment fragment = new IndividualPickerFragment();
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ private static int getResIdForRotationState(@RotationInitializationState int rotationState) {
+ switch (rotationState) {
+ case WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED:
+ return R.string.daily_refresh_tile_subtitle;
+ case WallpaperRotationInitializer.ROTATION_HOME_ONLY:
+ return R.string.home_screen_message;
+ case WallpaperRotationInitializer.ROTATION_HOME_AND_LOCK:
+ return R.string.home_and_lock_short_label;
+ default:
+ Log.e(TAG, "Unknown rotation intialization state: " + rotationState);
+ return R.string.home_screen_message;
+ }
+ }
+
+ private void updateDesktopDailyRotationThumbnail(DesktopRotationHolder holder) {
+ int wallpapersIndex = mRandom.nextInt(mWallpapers.size());
+ Asset newThumbnailAsset = mWallpapers.get(wallpapersIndex).getThumbAsset(
+ getActivity());
+ holder.updateThumbnail(newThumbnailAsset, new DrawableLoadedListener() {
+ @Override
+ public void onDrawableLoaded() {
+ if (getActivity() == null) {
+ return;
+ }
+
+ // Schedule the next update of the thumbnail.
+ int delayMillis = DesktopRotationHolder.CROSSFADE_DURATION_MILLIS
+ + DesktopRotationHolder.CROSSFADE_DURATION_PAUSE_MILLIS;
+ mHandler.postDelayed(mUpdateDailyWallpaperThumbRunnable, delayMillis);
+ }
+ });
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Injector injector = InjectorProvider.getInjector();
+ Context appContext = getContext().getApplicationContext();
+ mWallpaperPreferences = injector.getPreferences(appContext);
+
+ mWallpaperChangedNotifier = WallpaperChangedNotifier.getInstance();
+ mWallpaperChangedNotifier.registerListener(mWallpaperChangedListener);
+
+ mRotatingWallpaperComponentChecker = injector.getRotatingWallpaperComponentChecker();
+
+ mFormFactor = injector.getFormFactorChecker(appContext).getFormFactor();
+
+ mWallpapers = new ArrayList<>();
+ mRandom = new Random();
+ mHandler = new Handler();
+
+ String collectionId = getArguments().getString(ARG_CATEGORY_COLLECTION_ID);
+ mCategory = (WallpaperCategory) injector.getCategoryProvider(appContext).getCategory(
+ collectionId);
+ if (mCategory == null) {
+ DiskBasedLogger.e(TAG, "Failed to find the category.", appContext);
+
+ // The absence of this category in the CategoryProvider indicates a broken state, probably due
+ // to a relaunch into this activity/fragment following a crash immediately prior; see
+ // b//38030129. Hence, finish the activity and return.
+ getActivity().finish();
+ return;
+ }
+
+ mWallpaperRotationInitializer = mCategory.getWallpaperRotationInitializer();
+
+ mCategory.fetchWallpapers(getActivity().getApplicationContext(), new WallpaperReceiver() {
+ @Override
+ public void onWallpapersReceived(List<WallpaperInfo> wallpapers) {
+ for (WallpaperInfo wallpaper : wallpapers) {
+ mWallpapers.add(wallpaper);
+ }
+
+ // Wallpapers may load after the adapter is initialized, in which case we have to explicitly
+ // notify that the data set has changed.
+ if (mAdapter != null) {
+ mAdapter.notifyDataSetChanged();
+ }
+
+ if (mWallpapersUiContainer != null) {
+ mWallpapersUiContainer.onWallpapersReady();
+ }
+ }
+ });
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_individual_picker, container, false);
+
+ mTileSizePx = TileSizeCalculator.getIndividualTileSize(getActivity());
+
+ mImageGrid = (RecyclerView) view.findViewById(R.id.wallpaper_grid);
+ if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
+ int gridPaddingPx = getResources().getDimensionPixelSize(R.dimen.grid_padding_desktop);
+ updateImageGridPadding(false /* addExtraBottomSpace */);
+ mImageGrid.setScrollBarSize(gridPaddingPx);
+ }
+ GridMarginDecoration.applyTo(mImageGrid);
+
+ mAdapter = new IndividualAdapter(mWallpapers);
+ mImageGrid.setAdapter(mAdapter);
+ mImageGrid.setLayoutManager(new GridLayoutManager(getActivity(), getNumColumns()));
+
+ setUpBottomSheet();
+
+ return view;
+ }
+
+ @Override
+ public void onClickTryAgain(@Destination int unused) {
+ if (mPendingSetIndividualHolder != null) {
+ mPendingSetIndividualHolder.setWallpaper();
+ }
+ }
+
+ private void updateImageGridPadding(boolean addExtraBottomSpace) {
+ int gridPaddingPx = getResources().getDimensionPixelSize(R.dimen.grid_padding_desktop);
+ int bottomSheetHeightPx = getResources().getDimensionPixelSize(
+ R.dimen.current_wallpaper_bottom_sheet_layout_height);
+ int paddingBottomPx = addExtraBottomSpace ? bottomSheetHeightPx : 0;
+ // Only left and top may be set in order for the GridMarginDecoration to work properly.
+ mImageGrid.setPadding(
+ gridPaddingPx, gridPaddingPx, 0, paddingBottomPx);
+ }
+
+ /**
+ * Enables and populates the "Currently set" wallpaper BottomSheet.
+ */
+ private void setUpBottomSheet() {
+ mImageGrid.addOnScrollListener(new OnScrollListener() {
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, final int dy) {
+ if (mCurrentWallpaperBottomSheetPresenter == null) {
+ return;
+ }
+
+ if (mCurrentWallpaperBottomSheetExpandedRunnable != null) {
+ mHandler.removeCallbacks(mCurrentWallpaperBottomSheetExpandedRunnable);
+ }
+ mCurrentWallpaperBottomSheetExpandedRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (dy > 0) {
+ mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(false);
+ } else {
+ mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(true);
+ }
+ }
+ };
+ mHandler.postDelayed(mCurrentWallpaperBottomSheetExpandedRunnable, 100);
+ }
+ });
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ WallpaperPreferences preferences = InjectorProvider.getInjector().getPreferences(getActivity());
+ preferences.setLastAppActiveTimestamp(new Date().getTime());
+
+ // Reset Glide memory settings to a "normal" level of usage since it may have been lowered in
+ // PreviewFragment.
+ Glide.get(getActivity()).setMemoryCategory(MemoryCategory.NORMAL);
+
+ // Show the staged 'start rotation' error dialog fragment if there is one that was unable to be
+ // shown earlier when this fragment's hosting activity didn't allow committing fragment
+ // transactions.
+ if (mStagedStartRotationErrorDialogFragment != null) {
+ mStagedStartRotationErrorDialogFragment.show(
+ getFragmentManager(), TAG_START_ROTATION_ERROR_DIALOG);
+ mStagedStartRotationErrorDialogFragment = null;
+ }
+
+ // Show the staged 'load wallpaper' or 'set wallpaper' error dialog fragments if there is one
+ // that was unable to be shown earlier when this fragment's hosting activity didn't allow
+ // committing fragment transactions.
+ if (mStagedSetWallpaperErrorDialogFragment != null) {
+ mStagedSetWallpaperErrorDialogFragment.show(
+ getFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
+ mStagedSetWallpaperErrorDialogFragment = null;
+ }
+
+ if (isRotationEnabled()) {
+ if (mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
+ // Refresh the state of the "start rotation" in case something changed the current daily
+ // rotation while this fragment was paused.
+ RotationHolder rotationHolder = (RotationHolder) mImageGrid
+ .findViewHolderForAdapterPosition(
+ SPECIAL_FIXED_TILE_ADAPTER_POSITION);
+ // The RotationHolder may be null if the RecyclerView has not created the view
+ // holder yet.
+ if (rotationHolder != null && Flags.dynamicStartRotationTileEnabled) {
+ refreshRotationHolder(rotationHolder);
+ }
+ } else if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
+ if (mWasUpdateRunnableRun && !mWallpapers.isEmpty()) {
+ // Must be resuming from a previously stopped state, so re-schedule the update of the
+ // daily wallpapers tile thumbnail.
+ mUpdateDailyWallpaperThumbRunnable.run();
+ }
+ }
+ }
+
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ mHandler.removeCallbacks(mUpdateDailyWallpaperThumbRunnable);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mProgressDialog != null) {
+ mProgressDialog.dismiss();
+ }
+ mWallpaperChangedNotifier.unregisterListener(mWallpaperChangedListener);
+ }
+
+ @Override
+ public void retryStartRotation(@NetworkPreference int networkPreference) {
+ startRotation(networkPreference);
+ }
+
+ public void setCurrentWallpaperBottomSheetPresenter(
+ CurrentWallpaperBottomSheetPresenter presenter) {
+ mCurrentWallpaperBottomSheetPresenter = presenter;
+ }
+
+ public void setWallpapersUiContainer(WallpapersUiContainer uiContainer) {
+ mWallpapersUiContainer = uiContainer;
+ }
+
+ /**
+ * Enable a test mode of operation -- in which certain UI features are disabled to allow for
+ * UI tests to run correctly. Works around issue in ProgressDialog currently where the dialog
+ * constantly keeps the UI thread alive and blocks a test forever.
+ *
+ * @param testingMode
+ */
+ void setTestingMode(boolean testingMode) {
+ mTestingMode = testingMode;
+ }
+
+ /**
+ * Asynchronously fetches the refreshed rotation initialization state that is up to date with the
+ * state of the user's device and binds the state of the current category's rotation to the "start
+ * rotation" tile.
+ */
+ private void refreshRotationHolder(final RotationHolder rotationHolder) {
+ mWallpaperRotationInitializer.fetchRotationInitializationState(getContext(),
+ new RotationStateListener() {
+ @Override
+ public void onRotationStateReceived(
+ @RotationInitializationState final int rotationInitializationState) {
+
+ // Update the UI state of the "start rotation" tile displayed on screen. Do this in a
+ // Handler so it is scheduled at the end of the message queue. This is necessary to
+ // ensure we do not remove or add data from the adapter while the layout is still being
+ // computed. RecyclerView documentation therefore recommends performing such changes in
+ // a Handler.
+ new android.os.Handler().post(new Runnable() {
+ @Override
+ public void run() {
+ // A config change may have destroyed the activity since the refresh started, so
+ // check for that to avoid an NPE.
+ if (getActivity() == null) {
+ return;
+ }
+
+ rotationHolder.bindRotationInitializationState(rotationInitializationState);
+ }
+ });
+ }
+ });
+ }
+
+ @Override
+ public void startRotation(@NetworkPreference final int networkPreference) {
+ if (!isRotationEnabled()) {
+ Log.e(TAG, "Rotation is not enabled for this category " + mCategory.getTitle());
+ return;
+ }
+
+ // ProgressDialog endlessly updates the UI thread, keeping it from going idle which therefore
+ // causes Espresso to hang once the dialog is shown.
+ if (mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE && !mTestingMode) {
+ int themeResId;
+ if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
+ themeResId = R.style.ProgressDialogThemePreL;
+ } else {
+ themeResId = R.style.LightDialogTheme;
+ }
+ mProgressDialog = new ProgressDialog(getActivity(), themeResId);
+
+ mProgressDialog.setTitle(PROGRESS_DIALOG_NO_TITLE);
+ mProgressDialog.setMessage(
+ getResources().getString(R.string.start_rotation_progress_message));
+ mProgressDialog.setIndeterminate(PROGRESS_DIALOG_INDETERMINATE);
+ mProgressDialog.show();
+ }
+
+ if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
+ mAdapter.mPendingSelectedAdapterPosition = SPECIAL_FIXED_TILE_ADAPTER_POSITION;
+ }
+
+ final Context appContext = getActivity().getApplicationContext();
+
+ mWallpaperRotationInitializer.setFirstWallpaperInRotation(
+ appContext,
+ networkPreference,
+ new Listener() {
+ @Override
+ public void onFirstWallpaperInRotationSet() {
+ if (mProgressDialog != null) {
+ mProgressDialog.dismiss();
+ }
+
+ // The fragment may be detached from its containing activity if the user exits the
+ // app before the first wallpaper image in rotation finishes downloading.
+ Activity activity = getActivity();
+
+ if (activity != null
+ && mWallpaperRotationInitializer
+ .isNoBackupImageWallpaperPreviewNeeded(appContext)) {
+ ((IndividualPickerActivity) activity).showNoBackupImageWallpaperPreview();
+ } else {
+ if (mWallpaperRotationInitializer.startRotation(appContext)) {
+ if (activity != null && mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
+ try {
+ Toast.makeText(getActivity(), R.string.wallpaper_set_successfully_message,
+ Toast.LENGTH_SHORT).show();
+ } catch (NotFoundException e) {
+ Log.e(TAG, "Could not show toast " + e);
+ }
+
+ activity.setResult(Activity.RESULT_OK);
+ activity.finish();
+ } else if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
+ mAdapter.updateSelectedTile(SPECIAL_FIXED_TILE_ADAPTER_POSITION);
+ }
+ } else { // Failed to start rotation.
+ showStartRotationErrorDialog(networkPreference);
+
+ if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
+ DesktopRotationHolder rotationViewHolder =
+ (DesktopRotationHolder) mImageGrid.findViewHolderForAdapterPosition(
+ SPECIAL_FIXED_TILE_ADAPTER_POSITION);
+ rotationViewHolder.setSelectionState(SelectableHolder.SELECTION_STATE_DESELECTED);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onError() {
+ if (mProgressDialog != null) {
+ mProgressDialog.dismiss();
+ }
+
+ showStartRotationErrorDialog(networkPreference);
+
+ if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
+ DesktopRotationHolder rotationViewHolder =
+ (DesktopRotationHolder) mImageGrid.findViewHolderForAdapterPosition(
+ SPECIAL_FIXED_TILE_ADAPTER_POSITION);
+ rotationViewHolder.setSelectionState(SelectableHolder.SELECTION_STATE_DESELECTED);
+ }
+ }
+ });
+ }
+
+ private void showStartRotationErrorDialog(@NetworkPreference int networkPreference) {
+ BaseActivity activity = (BaseActivity) getActivity();
+ if (activity != null) {
+ StartRotationErrorDialogFragment startRotationErrorDialogFragment =
+ StartRotationErrorDialogFragment.newInstance(networkPreference);
+ startRotationErrorDialogFragment.setTargetFragment(
+ IndividualPickerFragment.this, UNUSED_REQUEST_CODE);
+
+ if (activity.isSafeToCommitFragmentTransaction()) {
+ startRotationErrorDialogFragment.show(
+ getFragmentManager(), TAG_START_ROTATION_ERROR_DIALOG);
+ } else {
+ mStagedStartRotationErrorDialogFragment = startRotationErrorDialogFragment;
+ }
+ }
+ }
+
+ private int getNumColumns() {
+ return TileSizeCalculator.getNumIndividualColumns(getActivity());
+ }
+
+ /**
+ * Returns whether rotation is enabled for this category.
+ */
+ private boolean isRotationEnabled() {
+ boolean isRotationSupported =
+ mRotatingWallpaperComponentChecker.getRotatingWallpaperSupport(getContext())
+ == RotatingWallpaperComponentChecker.ROTATING_WALLPAPER_SUPPORT_SUPPORTED;
+
+ return isRotationSupported && mWallpaperRotationInitializer != null;
+ }
+
+ @Override
+ public void onCurrentWallpaperRefreshed() {
+ mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(true);
+ }
+
+ /**
+ * Shows a "set wallpaper" error dialog with a failure message and button to try again.
+ */
+ private void showSetWallpaperErrorDialog() {
+ SetWallpaperErrorDialogFragment dialogFragment = SetWallpaperErrorDialogFragment.newInstance(
+ R.string.set_wallpaper_error_message, WallpaperPersister.DEST_BOTH);
+ dialogFragment.setTargetFragment(this, UNUSED_REQUEST_CODE);
+
+ if (((BaseActivity) getActivity()).isSafeToCommitFragmentTransaction()) {
+ dialogFragment.show(getFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
+ } else {
+ mStagedSetWallpaperErrorDialogFragment = dialogFragment;
+ }
+ }
+
+ /**
+ * ViewHolder subclass for "daily refresh" tile in the RecyclerView, only shown if rotation is
+ * enabled for this category.
+ */
+ private class RotationHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
+
+ private FrameLayout mTileLayout;
+ private TextView mRotationMessage;
+ private TextView mRotationTitle;
+ private ImageView mRefreshIcon;
+
+ RotationHolder(View itemView) {
+ super(itemView);
+ itemView.setOnClickListener(this);
+
+ mTileLayout = (FrameLayout) itemView.findViewById(R.id.daily_refresh);
+ mRotationMessage = (TextView) itemView.findViewById(R.id.rotation_tile_message);
+ mRotationTitle = (TextView) itemView.findViewById(R.id.rotation_tile_title);
+ mRefreshIcon = (ImageView) itemView.findViewById(R.id.rotation_tile_refresh_icon);
+ mTileLayout.getLayoutParams().height = mTileSizePx.y;
+
+ // If the feature flag for "dynamic start rotation tile" is not enabled, fall back to the
+ // static UI with a blue accent color background and "Tap to turn on" text.
+ if (!Flags.dynamicStartRotationTileEnabled) {
+ mTileLayout.setBackgroundColor(
+ getResources().getColor(R.color.rotation_tile_enabled_background_color));
+ mRotationMessage.setText(R.string.daily_refresh_tile_subtitle);
+ mRotationTitle.setTextColor(
+ getResources().getColor(R.color.rotation_tile_enabled_title_text_color));
+ mRotationMessage.setTextColor(
+ getResources().getColor(R.color.rotation_tile_enabled_subtitle_text_color));
+ mRefreshIcon.setColorFilter(
+ getResources().getColor(R.color.rotation_tile_enabled_refresh_icon_color), Mode.SRC_IN);
+ return;
+ }
+
+ // Initialize the state of the "start rotation" tile (i.e., whether it is gray or blue to
+ // indicate if rotation is turned on for the current category) with last-known rotation state
+ // that could be stale. The last-known rotation state is correct in most cases and is a good
+ // starting point but may not be accurate if the user set a wallpaper through a 3rd party app
+ // while this app was paused.
+ int rotationState = mWallpaperRotationInitializer.getRotationInitializationStateDirty(
+ getContext());
+ bindRotationInitializationState(rotationState);
+ }
+
+ @Override
+ public void onClick(View v) {
+ boolean isLiveWallpaperNeeded = mWallpaperRotationInitializer
+ .isNoBackupImageWallpaperPreviewNeeded(getActivity().getApplicationContext());
+ DialogFragment startRotationDialogFragment = StartRotationDialogFragment
+ .newInstance(isLiveWallpaperNeeded);
+ startRotationDialogFragment.setTargetFragment(
+ IndividualPickerFragment.this, UNUSED_REQUEST_CODE);
+ startRotationDialogFragment.show(getFragmentManager(), TAG_START_ROTATION_DIALOG);
+ }
+
+ /**
+ * Binds the provided rotation initialization state to the RotationHolder and updates the tile's
+ * UI to be in sync with the state (i.e., message and color appropriately reflect the state to
+ * the user).
+ */
+ void bindRotationInitializationState(@RotationInitializationState int rotationState) {
+ int newBackgroundColor =
+ (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
+ ? getResources().getColor(R.color.rotation_tile_not_enabled_background_color)
+ : getResources().getColor(R.color.rotation_tile_enabled_background_color);
+ int newTitleTextColor =
+ (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
+ ? getResources().getColor(R.color.rotation_tile_not_enabled_title_text_color)
+ : getResources().getColor(R.color.rotation_tile_enabled_title_text_color);
+ int newSubtitleTextColor =
+ (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
+ ? getResources().getColor(R.color.rotation_tile_not_enabled_subtitle_text_color)
+ : getResources().getColor(R.color.rotation_tile_enabled_subtitle_text_color);
+ int newRefreshIconColor =
+ (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
+ ? getResources().getColor(R.color.rotation_tile_not_enabled_refresh_icon_color)
+ : getResources().getColor(R.color.rotation_tile_enabled_refresh_icon_color);
+
+ mTileLayout.setBackgroundColor(newBackgroundColor);
+ mRotationTitle.setTextColor(newTitleTextColor);
+ mRotationMessage.setText(getResIdForRotationState(rotationState));
+ mRotationMessage.setTextColor(newSubtitleTextColor);
+ mRefreshIcon.setColorFilter(newRefreshIconColor, Mode.SRC_IN);
+ }
+ }
+
+ /**
+ * RecyclerView Adapter subclass for the wallpaper tiles in the RecyclerView.
+ */
+ private class IndividualAdapter extends RecyclerView.Adapter<ViewHolder> {
+ private static final int ITEM_VIEW_TYPE_ROTATION = 1;
+ private static final int ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER = 2;
+ private static final int ITEM_VIEW_TYPE_MY_PHOTOS = 3;
+
+ private final List<WallpaperInfo> mWallpapers;
+
+ private int mPendingSelectedAdapterPosition;
+ private int mSelectedAdapterPosition;
+
+ IndividualAdapter(List<WallpaperInfo> wallpapers) {
+ mWallpapers = wallpapers;
+ mPendingSelectedAdapterPosition = -1;
+ mSelectedAdapterPosition = -1;
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ switch (viewType) {
+ case ITEM_VIEW_TYPE_ROTATION:
+ return createRotationHolder(parent);
+ case ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER:
+ return createIndividualHolder(parent);
+ case ITEM_VIEW_TYPE_MY_PHOTOS:
+ return createMyPhotosHolder(parent);
+ default:
+ Log.e(TAG, "Unsupported viewType " + viewType + " in IndividualAdapter");
+ return null;
+ }
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ if (isRotationEnabled() && position == SPECIAL_FIXED_TILE_ADAPTER_POSITION) {
+ return ITEM_VIEW_TYPE_ROTATION;
+ }
+
+ // A category cannot have both a "start rotation" tile and a "my photos" tile.
+ if (mCategory.supportsCustomPhotos()
+ && !isRotationEnabled()
+ && position == SPECIAL_FIXED_TILE_ADAPTER_POSITION) {
+ return ITEM_VIEW_TYPE_MY_PHOTOS;
+ }
+
+ return ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER;
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ int viewType = getItemViewType(position);
+
+ switch (viewType) {
+ case ITEM_VIEW_TYPE_ROTATION:
+ onBindRotationHolder(holder, position);
+ break;
+ case ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER:
+ onBindIndividualHolder(holder, position);
+ break;
+ case ITEM_VIEW_TYPE_MY_PHOTOS:
+ ((MyPhotosViewHolder) holder).bind();
+ break;
+ default:
+ Log.e(TAG, "Unsupported viewType " + viewType + " in IndividualAdapter");
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return (isRotationEnabled() || mCategory.supportsCustomPhotos())
+ ? mWallpapers.size() + 1
+ : mWallpapers.size();
+ }
+
+ private ViewHolder createRotationHolder(ViewGroup parent) {
+ LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
+ View view;
+
+ if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
+ view = layoutInflater.inflate(R.layout.grid_item_rotation_desktop, parent, false);
+ SelectionAnimator selectionAnimator =
+ new CheckmarkSelectionAnimator(getActivity(), view);
+ return new DesktopRotationHolder(
+ getActivity(), mTileSizePx.y, view, selectionAnimator,
+ IndividualPickerFragment.this);
+ } else { // MOBILE
+ view = layoutInflater.inflate(R.layout.grid_item_rotation, parent, false);
+ return new RotationHolder(view);
+ }
+ }
+
+ private ViewHolder createIndividualHolder(ViewGroup parent) {
+ LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
+ View view = layoutInflater.inflate(R.layout.grid_item_image, parent, false);
+
+ if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
+ SelectionAnimator selectionAnimator =
+ new CheckmarkSelectionAnimator(getActivity(), view);
+ return new SetIndividualHolder(
+ getActivity(), mTileSizePx.y, view,
+ selectionAnimator,
+ new OnSetListener() {
+ @Override
+ public void onPendingWallpaperSet(int adapterPosition) {
+ // Deselect and hide loading indicator for any previously pending tile.
+ if (mPendingSelectedAdapterPosition != -1) {
+ ViewHolder oldViewHolder = mImageGrid.findViewHolderForAdapterPosition(
+ mPendingSelectedAdapterPosition);
+ if (oldViewHolder instanceof SelectableHolder) {
+ ((SelectableHolder) oldViewHolder).setSelectionState(
+ SelectableHolder.SELECTION_STATE_DESELECTED);
+ }
+ }
+
+ if (mSelectedAdapterPosition != -1) {
+ ViewHolder oldViewHolder = mImageGrid.findViewHolderForAdapterPosition(
+ mSelectedAdapterPosition);
+ if (oldViewHolder instanceof SelectableHolder) {
+ ((SelectableHolder) oldViewHolder).setSelectionState(
+ SelectableHolder.SELECTION_STATE_DESELECTED);
+ }
+ }
+
+ mPendingSelectedAdapterPosition = adapterPosition;
+ }
+
+ @Override
+ public void onWallpaperSet(int adapterPosition) {
+ // No-op -- UI handles a new wallpaper being set by reacting to the
+ // WallpaperChangedNotifier.
+ }
+
+ @Override
+ public void onWallpaperSetFailed(SetIndividualHolder holder) {
+ showSetWallpaperErrorDialog();
+ mPendingSetIndividualHolder = holder;
+ }
+ });
+ } else { // MOBILE
+ return new PreviewIndividualHolder(
+ (IndividualPickerActivity) getActivity(), mTileSizePx.y, view);
+ }
+ }
+
+ private ViewHolder createMyPhotosHolder(ViewGroup parent) {
+ LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
+ View view = layoutInflater.inflate(R.layout.grid_item_my_photos, parent, false);
+
+ return new MyPhotosViewHolder(getActivity(), mTileSizePx.y, view);
+ }
+
+ /**
+ * Marks the tile at the given position as selected with a visual indication. Also updates the
+ * "currently selected" BottomSheet to reflect the newly selected tile.
+ */
+ private void updateSelectedTile(int newlySelectedPosition) {
+ // Prevent multiple spinners from appearing with a user tapping several tiles in rapid
+ // succession.
+ if (mPendingSelectedAdapterPosition == mSelectedAdapterPosition) {
+ return;
+ }
+
+ if (mCurrentWallpaperBottomSheetPresenter != null) {
+ mCurrentWallpaperBottomSheetPresenter.refreshCurrentWallpapers(
+ IndividualPickerFragment.this);
+
+ if (mCurrentWallpaperBottomSheetExpandedRunnable != null) {
+ mHandler.removeCallbacks(mCurrentWallpaperBottomSheetExpandedRunnable);
+ }
+ mCurrentWallpaperBottomSheetExpandedRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(true);
+ }
+ };
+ mHandler.postDelayed(mCurrentWallpaperBottomSheetExpandedRunnable, 100);
+ }
+
+ // User may have switched to another category, thus detaching this fragment, so check here.
+ // NOTE: We do this check after updating the current wallpaper BottomSheet so that the update
+ // still occurs in the UI after the user selects that other category.
+ if (getActivity() == null) {
+ return;
+ }
+
+ // Update the newly selected wallpaper ViewHolder and the old one so that if
+ // selection UI state applies (desktop UI), it is updated.
+ if (mSelectedAdapterPosition >= 0) {
+ ViewHolder oldViewHolder = mImageGrid.findViewHolderForAdapterPosition(
+ mSelectedAdapterPosition);
+ if (oldViewHolder instanceof SelectableHolder) {
+ ((SelectableHolder) oldViewHolder).setSelectionState(
+ SelectableHolder.SELECTION_STATE_DESELECTED);
+ }
+ }
+
+ // Animate selection of newly selected tile.
+ ViewHolder newViewHolder = mImageGrid
+ .findViewHolderForAdapterPosition(newlySelectedPosition);
+ if (newViewHolder instanceof SelectableHolder) {
+ ((SelectableHolder) newViewHolder).setSelectionState(
+ SelectableHolder.SELECTION_STATE_SELECTED);
+ }
+
+ mSelectedAdapterPosition = newlySelectedPosition;
+
+ // If the tile was in the last row of the grid, add space below it so the user can scroll down
+ // and up to see the BottomSheet without it fully overlapping the newly selected tile.
+ int spanCount = ((GridLayoutManager) mImageGrid.getLayoutManager()).getSpanCount();
+ int numRows = (int) Math.ceil((float) getItemCount() / spanCount);
+ int rowOfNewlySelectedTile = newlySelectedPosition / spanCount;
+ boolean isInLastRow = rowOfNewlySelectedTile == numRows - 1;
+
+ updateImageGridPadding(isInLastRow /* addExtraBottomSpace */);
+ }
+
+ private void onBindRotationHolder(ViewHolder holder, int position) {
+ if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
+ String collectionId = mCategory.getCollectionId();
+ ((DesktopRotationHolder) holder).bind(collectionId);
+
+ if (mWallpaperPreferences.getWallpaperPresentationMode()
+ == WallpaperPreferences.PRESENTATION_MODE_ROTATING
+ && collectionId.equals(mWallpaperPreferences.getHomeWallpaperCollectionId())) {
+ mSelectedAdapterPosition = position;
+ }
+
+ if (!mWasUpdateRunnableRun && !mWallpapers.isEmpty()) {
+ updateDesktopDailyRotationThumbnail((DesktopRotationHolder) holder);
+ mWasUpdateRunnableRun = true;
+ }
+ }
+ }
+
+ private void onBindIndividualHolder(ViewHolder holder, int position) {
+ int wallpaperIndex = (isRotationEnabled() || mCategory.supportsCustomPhotos())
+ ? position - 1 : position;
+ WallpaperInfo wallpaper = mWallpapers.get(wallpaperIndex);
+ ((IndividualHolder) holder).bindWallpaper(wallpaper);
+ WallpaperPreferences prefs = InjectorProvider.getInjector().getPreferences(getContext());
+
+ String wallpaperId = wallpaper.getWallpaperId();
+ if (wallpaperId != null && wallpaperId.equals(prefs.getHomeWallpaperRemoteId())) {
+ mSelectedAdapterPosition = position;
+ }
+ }
+ }
+}