| /* |
| * 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.annotation.MenuRes; |
| import android.app.Activity; |
| import android.app.ProgressDialog; |
| import android.app.WallpaperManager; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.Intent; |
| import android.content.res.Configuration; |
| import android.content.res.Resources.NotFoundException; |
| import android.graphics.Point; |
| import android.graphics.Rect; |
| import android.os.Build.VERSION; |
| import android.os.Build.VERSION_CODES; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.service.wallpaper.WallpaperService; |
| import android.text.TextUtils; |
| import android.util.ArraySet; |
| import android.util.Log; |
| import android.view.LayoutInflater; |
| import android.view.MenuItem; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.Toast; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.cardview.widget.CardView; |
| import androidx.core.widget.ContentLoadingProgressBar; |
| import androidx.fragment.app.DialogFragment; |
| import androidx.fragment.app.Fragment; |
| import androidx.recyclerview.widget.GridLayoutManager; |
| import androidx.recyclerview.widget.RecyclerView; |
| import androidx.recyclerview.widget.RecyclerView.OnScrollListener; |
| import androidx.recyclerview.widget.RecyclerView.ViewHolder; |
| |
| import com.android.wallpaper.R; |
| import com.android.wallpaper.asset.Asset; |
| import com.android.wallpaper.asset.Asset.DrawableLoadedListener; |
| import com.android.wallpaper.model.Category; |
| import com.android.wallpaper.model.CategoryProvider; |
| import com.android.wallpaper.model.CategoryReceiver; |
| import com.android.wallpaper.model.LiveWallpaperInfo; |
| 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.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.PackageStatusNotifier; |
| import com.android.wallpaper.module.UserEventLogger; |
| 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.module.WallpaperSetter; |
| import com.android.wallpaper.picker.AppbarFragment; |
| import com.android.wallpaper.picker.BaseActivity; |
| import com.android.wallpaper.picker.CurrentWallpaperBottomSheetPresenter; |
| import com.android.wallpaper.picker.FragmentTransactionChecker; |
| import com.android.wallpaper.picker.MyPhotosStarter.MyPhotosStarterProvider; |
| import com.android.wallpaper.picker.RotationStarter; |
| import com.android.wallpaper.picker.SetWallpaperDialogFragment; |
| import com.android.wallpaper.picker.SetWallpaperErrorDialogFragment; |
| import com.android.wallpaper.picker.StartRotationDialogFragment; |
| import com.android.wallpaper.picker.StartRotationErrorDialogFragment; |
| import com.android.wallpaper.picker.WallpaperInfoHelper; |
| 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.SizeCalculator; |
| import com.android.wallpaper.widget.WallpaperInfoView; |
| import com.android.wallpaper.widget.WallpaperPickerRecyclerViewAccessibilityDelegate; |
| import com.android.wallpaper.widget.WallpaperPickerRecyclerViewAccessibilityDelegate.BottomSheetHost; |
| |
| 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.Optional; |
| import java.util.Random; |
| import java.util.Set; |
| |
| /** |
| * Displays the Main UI for picking an individual wallpaper image. |
| */ |
| public class IndividualPickerFragment extends AppbarFragment |
| implements RotationStarter, StartRotationErrorDialogFragment.Listener, |
| CurrentWallpaperBottomSheetPresenter.RefreshListener, |
| SetWallpaperErrorDialogFragment.Listener, SetWallpaperDialogFragment.Listener, |
| StartRotationDialogFragment.Listener { |
| |
| /** |
| * Position of a special tile that doesn't belong to an individual wallpaper of the category, |
| * such as "my photos" or "daily rotation". |
| */ |
| static final int SPECIAL_FIXED_TILE_ADAPTER_POSITION = 0; |
| static final String ARG_CATEGORY_COLLECTION_ID = "category_collection_id"; |
| |
| protected static final int MAX_CAPACITY_IN_FEWER_COLUMN_LAYOUT = 8; |
| |
| private static final String TAG = "IndividualPickerFrgmnt"; |
| 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"; |
| private static final String KEY_NIGHT_MODE = "IndividualPickerFragment.NIGHT_MODE"; |
| |
| /** |
| * An interface for updating the thumbnail with the specific wallpaper. |
| */ |
| public interface ThumbnailUpdater { |
| /** |
| * Updates the thumbnail with the specific wallpaper. |
| */ |
| void updateThumbnail(WallpaperInfo wallpaperInfo); |
| |
| /** |
| * Restores to the thumbnails of the wallpapers which were applied. |
| */ |
| void restoreThumbnails(); |
| } |
| |
| /** |
| * An interface for receiving the destination of the new applied wallpaper. |
| */ |
| public interface WallpaperDestinationCallback { |
| /** |
| * Called when the destination of the wallpaper is set. |
| * |
| * @param destination the destination which a wallpaper may be set. |
| * See {@link Destination} for more details. |
| */ |
| void onDestinationSet(@Destination int destination); |
| } |
| |
| /** |
| * The listener which will be notified when the wallpaper is selected. |
| */ |
| public interface WallpaperSelectedListener { |
| /** |
| * Called when the wallpaper is selected. |
| * |
| * @param position the position of the selected wallpaper |
| */ |
| void onWallpaperSelected(int position); |
| } |
| |
| /** |
| * Interface to be implemented by a Fragment(or an Activity) hosting |
| * a {@link IndividualPickerFragment}. |
| */ |
| public interface IndividualPickerFragmentHost { |
| /** |
| * Indicates if the host has toolbar to show the title. If it does, we should set the title |
| * there. |
| */ |
| boolean isHostToolbarShown(); |
| |
| /** |
| * Sets the title in the host's toolbar. |
| */ |
| void setToolbarTitle(CharSequence title); |
| |
| /** |
| * Configures the menu in the toolbar. |
| * |
| * @param menuResId the resource id of the menu |
| */ |
| void setToolbarMenu(@MenuRes int menuResId); |
| |
| /** |
| * Removes the menu in the toolbar. |
| */ |
| void removeToolbarMenu(); |
| |
| /** |
| * Moves to the previous fragment. |
| */ |
| void moveToPreviousFragment(); |
| } |
| |
| WallpaperPersister mWallpaperPersister; |
| WallpaperPreferences mWallpaperPreferences; |
| WallpaperChangedNotifier mWallpaperChangedNotifier; |
| RecyclerView mImageGrid; |
| IndividualAdapter mAdapter; |
| WallpaperCategory mCategory; |
| WallpaperRotationInitializer mWallpaperRotationInitializer; |
| List<WallpaperInfo> mWallpapers; |
| Point mTileSizePx; |
| WallpapersUiContainer mWallpapersUiContainer; |
| @FormFactor |
| int mFormFactor; |
| PackageStatusNotifier mPackageStatusNotifier; |
| |
| Handler mHandler; |
| Random mRandom; |
| boolean mIsWallpapersReceived; |
| |
| 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); |
| } |
| } |
| }; |
| PackageStatusNotifier.Listener mAppStatusListener; |
| WallpaperInfoView mWallpaperInfoView; |
| @Nullable WallpaperInfo mSelectedWallpaperInfo; |
| |
| private UserEventLogger mUserEventLogger; |
| private ProgressDialog mProgressDialog; |
| private boolean mTestingMode; |
| private CurrentWallpaperBottomSheetPresenter mCurrentWallpaperBottomSheetPresenter; |
| private SetIndividualHolder mPendingSetIndividualHolder; |
| private ContentLoadingProgressBar mLoading; |
| private CategoryProvider mCategoryProvider; |
| |
| /** |
| * 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 Runnable mCurrentWallpaperBottomSheetExpandedRunnable; |
| |
| /** |
| * 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 WallpaperSetter mWallpaperSetter; |
| private WallpaperInfo mAppliedWallpaperInfo; |
| private WallpaperManager mWallpaperManager; |
| private int mWallpaperDestination; |
| private WallpaperSelectedListener mWallpaperSelectedListener; |
| private Set<String> mAppliedWallpaperIds; |
| |
| 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; |
| } |
| |
| /** |
| * Highlights the applied wallpaper (if it exists) according to the destination a wallpaper |
| * would be set. |
| * |
| * @param wallpaperDestination the destination a wallpaper would be set. |
| * It will be either {@link Destination#DEST_HOME_SCREEN} |
| * or {@link Destination#DEST_LOCK_SCREEN}. |
| */ |
| public void highlightAppliedWallpaper(@Destination int wallpaperDestination) { |
| mWallpaperDestination = wallpaperDestination; |
| if (mWallpapers != null) { |
| refreshAppliedWallpaper(); |
| } |
| } |
| |
| 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); |
| |
| mWallpaperManager = WallpaperManager.getInstance(appContext); |
| |
| mFormFactor = injector.getFormFactorChecker(appContext).getFormFactor(); |
| |
| mPackageStatusNotifier = injector.getPackageStatusNotifier(appContext); |
| |
| mUserEventLogger = injector.getUserEventLogger(appContext); |
| |
| mWallpaperPersister = injector.getWallpaperPersister(appContext); |
| mWallpaperSetter = new WallpaperSetter( |
| mWallpaperPersister, |
| injector.getPreferences(appContext), |
| injector.getUserEventLogger(appContext), |
| false); |
| |
| mWallpapers = new ArrayList<>(); |
| mRandom = new Random(); |
| mHandler = new Handler(); |
| |
| // Clear Glide's cache if night-mode changed to ensure thumbnails are reloaded |
| if (savedInstanceState != null && (savedInstanceState.getInt(KEY_NIGHT_MODE) |
| != (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK))) { |
| Glide.get(getContext()).clearMemory(); |
| } |
| |
| mCategoryProvider = injector.getCategoryProvider(appContext); |
| mCategoryProvider.fetchCategories(new CategoryReceiver() { |
| @Override |
| public void onCategoryReceived(Category category) { |
| // Do nothing. |
| } |
| |
| @Override |
| public void doneFetchingCategories() { |
| Category category = mCategoryProvider.getCategory( |
| getArguments().getString(ARG_CATEGORY_COLLECTION_ID)); |
| if (category != null && !(category instanceof WallpaperCategory)) { |
| return; |
| } |
| mCategory = (WallpaperCategory) category; |
| if (mCategory == null) { |
| DiskBasedLogger.e(TAG, "Failed to find the category.", getContext()); |
| |
| // The absence of this category in the CategoryProvider indicates a broken |
| // state, see b/38030129. Hence, finish the activity and return. |
| getIndividualPickerFragmentHost().moveToPreviousFragment(); |
| Toast.makeText(getContext(), R.string.collection_not_exist_msg, |
| Toast.LENGTH_SHORT).show(); |
| return; |
| } |
| onCategoryLoaded(); |
| } |
| }, false); |
| } |
| |
| |
| protected void onCategoryLoaded() { |
| if (getIndividualPickerFragmentHost() == null) { |
| return; |
| } |
| if (getIndividualPickerFragmentHost().isHostToolbarShown()) { |
| getIndividualPickerFragmentHost().setToolbarTitle(mCategory.getTitle()); |
| } else { |
| setTitle(mCategory.getTitle()); |
| } |
| mWallpaperRotationInitializer = mCategory.getWallpaperRotationInitializer(); |
| if (mToolbar != null && isRotationEnabled()) { |
| setUpToolbarMenu(R.menu.individual_picker_menu); |
| } |
| fetchWallpapers(false); |
| |
| if (mCategory.supportsThirdParty()) { |
| mAppStatusListener = (packageName, status) -> { |
| if (status != PackageStatusNotifier.PackageStatus.REMOVED || |
| mCategory.containsThirdParty(packageName)) { |
| fetchWallpapers(true); |
| } |
| }; |
| mPackageStatusNotifier.addListener(mAppStatusListener, |
| WallpaperService.SERVICE_INTERFACE); |
| } |
| } |
| |
| void fetchWallpapers(boolean forceReload) { |
| mWallpapers.clear(); |
| mIsWallpapersReceived = false; |
| updateLoading(); |
| mCategory.fetchWallpapers(getActivity().getApplicationContext(), new WallpaperReceiver() { |
| @Override |
| public void onWallpapersReceived(List<WallpaperInfo> wallpapers) { |
| mIsWallpapersReceived = true; |
| updateLoading(); |
| for (WallpaperInfo wallpaper : wallpapers) { |
| mWallpapers.add(wallpaper); |
| } |
| maybeSetUpImageGrid(); |
| |
| // 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(); |
| } else { |
| if (wallpapers.isEmpty()) { |
| // If there are no more wallpapers and we're on phone, just finish the |
| // Activity. |
| Activity activity = getActivity(); |
| if (activity != null |
| && mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) { |
| activity.finish(); |
| } |
| } |
| } |
| } |
| }, forceReload); |
| } |
| |
| void updateLoading() { |
| if (mLoading == null) { |
| return; |
| } |
| |
| if (mIsWallpapersReceived) { |
| mLoading.hide(); |
| } else { |
| mLoading.show(); |
| } |
| } |
| |
| @Override |
| public void onSaveInstanceState(@NonNull Bundle outState) { |
| super.onSaveInstanceState(outState); |
| outState.putInt(KEY_NIGHT_MODE, |
| getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK); |
| } |
| |
| @Override |
| public View onCreateView(LayoutInflater inflater, ViewGroup container, |
| Bundle savedInstanceState) { |
| View view = inflater.inflate(R.layout.fragment_individual_picker, container, false); |
| if (getIndividualPickerFragmentHost().isHostToolbarShown()) { |
| view.findViewById(R.id.header_bar).setVisibility(View.GONE); |
| setUpArrowEnabled(/* upArrow= */ true); |
| if (isRotationEnabled()) { |
| getIndividualPickerFragmentHost().setToolbarMenu(R.menu.individual_picker_menu); |
| } |
| } else { |
| setUpToolbar(view); |
| if (isRotationEnabled()) { |
| setUpToolbarMenu(R.menu.individual_picker_menu); |
| } |
| if (mCategory != null) { |
| setTitle(mCategory.getTitle()); |
| } |
| } |
| |
| mAppliedWallpaperIds = getAppliedWallpaperIds(); |
| |
| 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); |
| } |
| mLoading = view.findViewById(R.id.loading_indicator); |
| updateLoading(); |
| maybeSetUpImageGrid(); |
| setUpBottomSheet(); |
| // For nav bar edge-to-edge effect. |
| view.setOnApplyWindowInsetsListener((v, windowInsets) -> { |
| // For status bar height. |
| v.setPadding( |
| v.getPaddingLeft(), |
| windowInsets.getSystemWindowInsetTop(), |
| v.getPaddingRight(), |
| v.getPaddingBottom()); |
| |
| View gridView = v.findViewById(R.id.wallpaper_grid); |
| gridView.setPadding( |
| gridView.getPaddingLeft(), |
| gridView.getPaddingTop(), |
| gridView.getPaddingRight(), |
| windowInsets.getSystemWindowInsetBottom()); |
| return windowInsets.consumeSystemWindowInsets(); |
| }); |
| return view; |
| } |
| |
| @Override |
| public void onClickTryAgain(@Destination int unused) { |
| if (mPendingSetIndividualHolder != null) { |
| mPendingSetIndividualHolder.setWallpaper(); |
| } |
| } |
| |
| 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); |
| } |
| |
| private IndividualPickerFragmentHost getIndividualPickerFragmentHost() { |
| Fragment parentFragment = getParentFragment(); |
| if (parentFragment != null) { |
| return (IndividualPickerFragmentHost) parentFragment; |
| } else { |
| return (IndividualPickerFragmentHost) getActivity(); |
| } |
| } |
| |
| protected void maybeSetUpImageGrid() { |
| // Skip if mImageGrid been initialized yet |
| if (mImageGrid == null) { |
| return; |
| } |
| // Skip if category hasn't loaded yet |
| if (mCategory == null) { |
| return; |
| } |
| |
| // Wallpaper count could change, so we may need to change the layout(2 or 3 columns layout) |
| GridLayoutManager gridLayoutManager = (GridLayoutManager) mImageGrid.getLayoutManager(); |
| boolean needUpdateLayout = |
| gridLayoutManager != null && gridLayoutManager.getSpanCount() != getNumColumns(); |
| |
| // Skip if the adapter was already created and don't need to change the layout |
| if (mAdapter != null && !needUpdateLayout) { |
| return; |
| } |
| |
| // Clear the old decoration |
| int decorationCount = mImageGrid.getItemDecorationCount(); |
| for (int i = 0; i < decorationCount; i++) { |
| mImageGrid.removeItemDecorationAt(i); |
| } |
| |
| mImageGrid.addItemDecoration(new GridPaddingDecoration(getGridItemPaddingHorizontal(), |
| getGridItemPaddingBottom())); |
| int edgePadding = getEdgePadding(); |
| mImageGrid.setPadding(edgePadding, mImageGrid.getPaddingTop(), edgePadding, |
| mImageGrid.getPaddingBottom()); |
| mTileSizePx = isFewerColumnLayout() |
| ? SizeCalculator.getFeaturedIndividualTileSize(getActivity()) |
| : SizeCalculator.getIndividualTileSize(getActivity()); |
| setUpImageGrid(); |
| mImageGrid.setAccessibilityDelegateCompat( |
| new WallpaperPickerRecyclerViewAccessibilityDelegate( |
| mImageGrid, (BottomSheetHost) getParentFragment(), getNumColumns())); |
| } |
| |
| boolean isFewerColumnLayout() { |
| return mWallpapers != null && mWallpapers.size() <= MAX_CAPACITY_IN_FEWER_COLUMN_LAYOUT; |
| } |
| |
| private int getGridItemPaddingHorizontal() { |
| return isFewerColumnLayout() |
| ? getResources().getDimensionPixelSize( |
| R.dimen.grid_item_featured_individual_padding_horizontal) |
| : getResources().getDimensionPixelSize( |
| R.dimen.grid_item_individual_padding_horizontal); |
| } |
| |
| private int getGridItemPaddingBottom() { |
| return isFewerColumnLayout() |
| ? getResources().getDimensionPixelSize( |
| R.dimen.grid_item_featured_individual_padding_bottom) |
| : getResources().getDimensionPixelSize(R.dimen.grid_item_individual_padding_bottom); |
| } |
| |
| private int getEdgePadding() { |
| return isFewerColumnLayout() |
| ? getResources().getDimensionPixelSize(R.dimen.featured_wallpaper_grid_edge_space) |
| : getResources().getDimensionPixelSize(R.dimen.wallpaper_grid_edge_space); |
| } |
| |
| /** |
| * Create the adapter and assign it to mImageGrid. |
| * Both mImageGrid and mCategory are guaranteed to not be null when this method is called. |
| */ |
| void setUpImageGrid() { |
| mAdapter = new IndividualAdapter(mWallpapers); |
| mImageGrid.setAdapter(mAdapter); |
| mImageGrid.setLayoutManager(new GridLayoutManager(getActivity(), getNumColumns())); |
| } |
| |
| /** |
| * Enables and populates the "Currently set" wallpaper BottomSheet. |
| */ |
| 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 (shouldShowRotationTile() && 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 onDestroyView() { |
| super.onDestroyView(); |
| getIndividualPickerFragmentHost().removeToolbarMenu(); |
| } |
| |
| @Override |
| public void onDestroy() { |
| super.onDestroy(); |
| if (mProgressDialog != null) { |
| mProgressDialog.dismiss(); |
| } |
| mWallpaperChangedNotifier.unregisterListener(mWallpaperChangedListener); |
| if (mAppStatusListener != null) { |
| mPackageStatusNotifier.removeListener(mAppStatusListener); |
| } |
| mWallpaperSetter.cleanUp(); |
| } |
| |
| @Override |
| public void onStartRotationDialogDismiss(@NonNull DialogInterface dialog) { |
| // TODO(b/159310028): Refactor fragment layer to make it able to restore from config change. |
| // This is to handle config change with StartRotationDialog popup, the StartRotationDialog |
| // still holds a reference to the destroyed Fragment and is calling |
| // onStartRotationDialogDismissed on that destroyed Fragment. |
| } |
| |
| @Override |
| public void retryStartRotation(@NetworkPreference int networkPreference) { |
| startRotation(networkPreference); |
| } |
| |
| public void setCurrentWallpaperBottomSheetPresenter( |
| CurrentWallpaperBottomSheetPresenter presenter) { |
| mCurrentWallpaperBottomSheetPresenter = presenter; |
| } |
| |
| public void setWallpapersUiContainer(WallpapersUiContainer uiContainer) { |
| mWallpapersUiContainer = uiContainer; |
| } |
| |
| public void setOnWallpaperSelectedListener( |
| WallpaperSelectedListener wallpaperSelectedListener) { |
| mWallpaperSelectedListener = wallpaperSelectedListener; |
| } |
| |
| /** |
| * Resizes the layout's height. |
| */ |
| public void resizeLayout(int height) { |
| mImageGrid.getLayoutParams().height = height; |
| mImageGrid.requestLayout(); |
| } |
| |
| /** |
| * Scrolls to the specific item. |
| * |
| * @param position the position of the item |
| */ |
| public void scrollToPosition(int position) { |
| ((GridLayoutManager) mImageGrid.getLayoutManager()) |
| .scrollToPositionWithOffset(position, /* offset= */ 0); |
| } |
| |
| /** |
| * 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; |
| } |
| |
| @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 (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) { |
| FragmentTransactionChecker activity = (FragmentTransactionChecker) 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; |
| } |
| } |
| } |
| |
| int getNumColumns() { |
| Activity activity = getActivity(); |
| if (activity == null) { |
| return 1; |
| } |
| return isFewerColumnLayout() |
| ? SizeCalculator.getNumFeaturedIndividualColumns(activity) |
| : SizeCalculator.getNumIndividualColumns(activity); |
| } |
| |
| /** |
| * Returns whether rotation is enabled for this category. |
| */ |
| boolean isRotationEnabled() { |
| return mWallpaperRotationInitializer != null; |
| } |
| |
| @Override |
| public void onCurrentWallpaperRefreshed() { |
| mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(true); |
| } |
| |
| @Override |
| public void onSet(int destination) { |
| if (mSelectedWallpaperInfo == null) { |
| Log.e(TAG, "Unable to set wallpaper since the selected wallpaper info is null"); |
| return; |
| } |
| |
| mWallpaperPersister.setWallpaperInfoInPreview(mSelectedWallpaperInfo); |
| if (mSelectedWallpaperInfo instanceof LiveWallpaperInfo) { |
| mWallpaperSetter.setCurrentWallpaper(getActivity(), mSelectedWallpaperInfo, null, |
| destination, 0, null, mSetWallpaperCallback); |
| } else { |
| mWallpaperSetter.setCurrentWallpaper( |
| getActivity(), mSelectedWallpaperInfo, destination, mSetWallpaperCallback); |
| } |
| onWallpaperDestinationSet(destination); |
| } |
| |
| private WallpaperPersister.SetWallpaperCallback mSetWallpaperCallback = |
| new WallpaperPersister.SetWallpaperCallback() { |
| @Override |
| public void onSuccess(WallpaperInfo wallpaperInfo) { |
| mWallpaperPersister.onLiveWallpaperSet(); |
| Toast.makeText(getActivity(), R.string.wallpaper_set_successfully_message, |
| Toast.LENGTH_SHORT).show(); |
| getActivity().overridePendingTransition(R.anim.fade_in, R.anim.fade_out); |
| getActivity().finish(); |
| } |
| |
| @Override |
| public void onError(@Nullable Throwable throwable) { |
| Log.e(TAG, "Can't apply the wallpaper."); |
| } |
| }; |
| |
| @Override |
| public boolean onMenuItemClick(MenuItem item) { |
| if (item.getItemId() == R.id.daily_rotation) { |
| showRotationDialog(); |
| return true; |
| } |
| return super.onMenuItemClick(item); |
| } |
| |
| /** |
| * Popups a daily rotation dialog for the uses to confirm. |
| */ |
| public void showRotationDialog() { |
| DialogFragment startRotationDialogFragment = new StartRotationDialogFragment(); |
| startRotationDialogFragment.setTargetFragment( |
| IndividualPickerFragment.this, UNUSED_REQUEST_CODE); |
| startRotationDialogFragment.show(getFragmentManager(), TAG_START_ROTATION_DIALOG); |
| } |
| |
| /** |
| * 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; |
| } |
| } |
| |
| private void updateThumbnail(WallpaperInfo selectedWallpaperInfo) { |
| ThumbnailUpdater thumbnailUpdater = (ThumbnailUpdater) getParentFragment(); |
| if (thumbnailUpdater == null) { |
| return; |
| } |
| |
| if (selectedWallpaperInfo != null) { |
| thumbnailUpdater.updateThumbnail(selectedWallpaperInfo); |
| } else { |
| thumbnailUpdater.restoreThumbnails(); |
| } |
| } |
| |
| private void onWallpaperDestinationSet(int destination) { |
| WallpaperDestinationCallback wallpaperDestinationCallback = |
| (WallpaperDestinationCallback) getParentFragment(); |
| if (wallpaperDestinationCallback == null) { |
| return; |
| } |
| |
| wallpaperDestinationCallback.onDestinationSet(destination); |
| } |
| |
| void onWallpaperSelected(@Nullable WallpaperInfo newSelectedWallpaperInfo, |
| int position) { |
| if (mSelectedWallpaperInfo == newSelectedWallpaperInfo) { |
| return; |
| } |
| // Update current wallpaper. |
| updateActivatedStatus(mSelectedWallpaperInfo == null |
| ? mAppliedWallpaperInfo : mSelectedWallpaperInfo, false); |
| // Update new selected wallpaper. |
| updateActivatedStatus(newSelectedWallpaperInfo == null |
| ? mAppliedWallpaperInfo : newSelectedWallpaperInfo, true); |
| |
| mSelectedWallpaperInfo = newSelectedWallpaperInfo; |
| updateThumbnail(mSelectedWallpaperInfo); |
| // Populate wallpaper info into view. |
| if (mSelectedWallpaperInfo != null && mWallpaperInfoView != null) { |
| WallpaperInfoHelper.loadExploreIntent( |
| getContext(), |
| mSelectedWallpaperInfo, |
| (actionLabel, exploreIntent) -> |
| mWallpaperInfoView.populateWallpaperInfo( |
| mSelectedWallpaperInfo, |
| actionLabel, |
| WallpaperInfoHelper.shouldShowExploreButton( |
| getContext(), exploreIntent), |
| v -> onExploreClicked(exploreIntent)) |
| ); |
| } |
| |
| if (mWallpaperSelectedListener != null) { |
| mWallpaperSelectedListener.onWallpaperSelected(position); |
| } |
| } |
| |
| private void onExploreClicked(Intent exploreIntent) { |
| if (getContext() == null) { |
| return; |
| } |
| Context context = getContext(); |
| mUserEventLogger.logActionClicked(mSelectedWallpaperInfo.getCollectionId(context), |
| mSelectedWallpaperInfo.getActionLabelRes(context)); |
| |
| startActivity(exploreIntent); |
| } |
| |
| private void updateActivatedStatus(WallpaperInfo wallpaperInfo, boolean isActivated) { |
| if (wallpaperInfo == null) { |
| return; |
| } |
| int index = mWallpapers.indexOf(wallpaperInfo); |
| index = (shouldShowRotationTile() || mCategory.supportsCustomPhotos()) |
| ? index + 1 : index; |
| ViewHolder holder = mImageGrid.findViewHolderForAdapterPosition(index); |
| if (holder != null) { |
| CustomShapeImageView thumbnail = holder.itemView.findViewById(R.id.thumbnail); |
| thumbnail.setClipped(isActivated); |
| } else { |
| // Item is not visible, make sure the item is re-bound when it becomes visible. |
| mAdapter.notifyItemChanged(index); |
| } |
| } |
| |
| private void updateAppliedStatus(WallpaperInfo wallpaperInfo, boolean isApplied) { |
| if (wallpaperInfo == null) { |
| return; |
| } |
| int index = mWallpapers.indexOf(wallpaperInfo); |
| index = (shouldShowRotationTile() || mCategory.supportsCustomPhotos()) |
| ? index + 1 : index; |
| ViewHolder holder = mImageGrid.findViewHolderForAdapterPosition(index); |
| if (holder == null) { |
| // Item is not visible, make sure the item is re-bound when it becomes visible. |
| mAdapter.notifyItemChanged(index); |
| } |
| } |
| |
| private void refreshAppliedWallpaper() { |
| // Clear the check mark and blue border(if it shows) of the old applied wallpaper. |
| showCheckMarkAndBorderForAppliedWallpaper(false); |
| |
| // Update to the new applied wallpaper. |
| String appliedWallpaperId = getAppliedWallpaperId(); |
| Optional<WallpaperInfo> wallpaperInfoOptional = mWallpapers |
| .stream() |
| .filter(wallpaper -> wallpaper.getWallpaperId() != null) |
| .filter(wallpaper -> wallpaper.getWallpaperId().equals(appliedWallpaperId)) |
| .findFirst(); |
| mAppliedWallpaperInfo = wallpaperInfoOptional.orElse(null); |
| |
| // Set the check mark and blue border(if user doesn't select) of the new applied wallpaper. |
| showCheckMarkAndBorderForAppliedWallpaper(true); |
| } |
| |
| private String getAppliedWallpaperId() { |
| WallpaperPreferences prefs = |
| InjectorProvider.getInjector().getPreferences(getContext()); |
| android.app.WallpaperInfo wallpaperInfo = mWallpaperManager.getWallpaperInfo(); |
| boolean isDestinationBoth = |
| mWallpaperManager.getWallpaperId(WallpaperManager.FLAG_LOCK) < 0; |
| |
| if (isDestinationBoth || mWallpaperDestination == WallpaperPersister.DEST_HOME_SCREEN) { |
| return wallpaperInfo != null |
| ? wallpaperInfo.getServiceName() : prefs.getHomeWallpaperRemoteId(); |
| } else { |
| return prefs.getLockWallpaperRemoteId(); |
| } |
| } |
| |
| private Set<String> getAppliedWallpaperIds() { |
| WallpaperPreferences prefs = |
| InjectorProvider.getInjector().getPreferences(getContext()); |
| android.app.WallpaperInfo wallpaperInfo = mWallpaperManager.getWallpaperInfo(); |
| Set<String> appliedWallpaperIds = new ArraySet<>(); |
| |
| String homeWallpaperId = wallpaperInfo != null ? wallpaperInfo.getServiceName() |
| : prefs.getHomeWallpaperRemoteId(); |
| if (!TextUtils.isEmpty(homeWallpaperId)) { |
| appliedWallpaperIds.add(homeWallpaperId); |
| } |
| |
| boolean isLockWallpaperApplied = |
| mWallpaperManager.getWallpaperId(WallpaperManager.FLAG_LOCK) >= 0; |
| String lockWallpaperId = prefs.getLockWallpaperRemoteId(); |
| if (isLockWallpaperApplied && !TextUtils.isEmpty(lockWallpaperId)) { |
| appliedWallpaperIds.add(lockWallpaperId); |
| } |
| |
| return appliedWallpaperIds; |
| } |
| |
| private void showCheckMarkAndBorderForAppliedWallpaper(boolean show) { |
| updateAppliedStatus(mAppliedWallpaperInfo, show); |
| if (mSelectedWallpaperInfo == null) { |
| updateActivatedStatus(mAppliedWallpaperInfo, show); |
| } |
| } |
| |
| boolean shouldShowRotationTile() { |
| return mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP && isRotationEnabled(); |
| } |
| |
| class EmptySelectionAnimator implements SelectionAnimator{ |
| EmptySelectionAnimator() {} |
| |
| public boolean isSelected() { |
| return false; |
| } |
| |
| /** |
| * Sets the UI to selected immediately with no animation. |
| */ |
| public void selectImmediately() {} |
| |
| /** |
| * Sets the UI to deselected immediately with no animation. |
| */ |
| public void deselectImmediately() {} |
| |
| /** |
| * Sets the UI to selected with a smooth animation. |
| */ |
| public void animateSelected() {} |
| |
| /** |
| * Sets the UI to deselected with a smooth animation. |
| */ |
| public void animateDeselected() {} |
| |
| /** |
| * Sets the UI to show a loading indicator. |
| */ |
| public void showLoading() {} |
| |
| /** |
| * Sets the UI to hide the loading indicator. |
| */ |
| public void showNotLoading() {} |
| |
| } |
| |
| /** |
| * RecyclerView Adapter subclass for the wallpaper tiles in the RecyclerView. |
| */ |
| class IndividualAdapter extends RecyclerView.Adapter<ViewHolder> { |
| static final int ITEM_VIEW_TYPE_ROTATION = 1; |
| static final int ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER = 2; |
| 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 (shouldShowRotationTile() && 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 (shouldShowRotationTile() || mCategory.supportsCustomPhotos()) |
| ? mWallpapers.size() + 1 |
| : mWallpapers.size(); |
| } |
| |
| private ViewHolder createRotationHolder(ViewGroup parent) { |
| LayoutInflater layoutInflater = LayoutInflater.from(getActivity()); |
| View view = layoutInflater.inflate(R.layout.grid_item_rotation_desktop, parent, false); |
| SelectionAnimator selectionAnimator = new EmptySelectionAnimator(); |
| return new DesktopRotationHolder(getActivity(), mTileSizePx.y, view, selectionAnimator, |
| IndividualPickerFragment.this); |
| } |
| |
| 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 EmptySelectionAnimator(); |
| 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(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(), |
| ((MyPhotosStarterProvider) getActivity()).getMyPhotosStarter(), |
| 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 */); |
| } |
| |
| 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; |
| } |
| } |
| } |
| |
| void onBindIndividualHolder(ViewHolder holder, int position) { |
| int wallpaperIndex = (shouldShowRotationTile() || mCategory.supportsCustomPhotos()) |
| ? position - 1 : position; |
| WallpaperInfo wallpaper = mWallpapers.get(wallpaperIndex); |
| wallpaper.computePlaceholderColor(holder.itemView.getContext()); |
| ((IndividualHolder) holder).bindWallpaper(wallpaper); |
| boolean isWallpaperApplied = mAppliedWallpaperIds.contains(wallpaper.getWallpaperId()); |
| |
| if (isWallpaperApplied) { |
| mSelectedAdapterPosition = position; |
| mAppliedWallpaperInfo = wallpaper; |
| } |
| |
| CardView container = holder.itemView.findViewById(R.id.wallpaper_container); |
| int radiusId = isFewerColumnLayout() ? R.dimen.grid_item_all_radius |
| : R.dimen.grid_item_all_radius_small; |
| container.setRadius(getResources().getDimension(radiusId)); |
| CustomShapeImageView thumbnail = holder.itemView.findViewById(R.id.thumbnail); |
| thumbnail.setClipped(isWallpaperApplied); |
| } |
| } |
| |
| private class GridPaddingDecoration extends RecyclerView.ItemDecoration { |
| |
| private final int mPaddingHorizontal; |
| private final int mPaddingBottom; |
| |
| GridPaddingDecoration(int paddingHorizontal, int paddingBottom) { |
| mPaddingHorizontal = paddingHorizontal; |
| mPaddingBottom = paddingBottom; |
| } |
| |
| @Override |
| public void getItemOffsets(Rect outRect, View view, RecyclerView parent, |
| RecyclerView.State state) { |
| int position = parent.getChildAdapterPosition(view); |
| if (position >= 0) { |
| outRect.left = mPaddingHorizontal; |
| outRect.right = mPaddingHorizontal; |
| outRect.bottom = mPaddingBottom; |
| } |
| } |
| } |
| } |