blob: 4318315d2e1664a9813e510d6d3beda640248471 [file] [log] [blame]
/*
* 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;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.graphics.Point;
import android.graphics.PorterDuff.Mode;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.provider.Settings;
import android.service.wallpaper.WallpaperService;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.GridLayoutManager.SpanSizeLookup;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.android.wallpaper.R;
import com.android.wallpaper.asset.Asset;
import com.android.wallpaper.compat.ButtonDrawableSetterCompat;
import com.android.wallpaper.config.Flags;
import com.android.wallpaper.model.Category;
import com.android.wallpaper.model.ThirdPartyAppCategory;
import com.android.wallpaper.model.WallpaperCategory;
import com.android.wallpaper.model.WallpaperInfo;
import com.android.wallpaper.module.CurrentWallpaperInfoFactory;
import com.android.wallpaper.module.CurrentWallpaperInfoFactory.WallpaperInfoCallback;
import com.android.wallpaper.module.ExploreIntentChecker;
import com.android.wallpaper.module.Injector;
import com.android.wallpaper.module.InjectorProvider;
import com.android.wallpaper.module.LockWallpaperStatusChecker;
import com.android.wallpaper.module.PackageStatusNotifier;
import com.android.wallpaper.module.UserEventLogger;
import com.android.wallpaper.module.WallpaperPreferences;
import com.android.wallpaper.module.WallpaperPreferences.PresentationMode;
import com.android.wallpaper.module.WallpaperRotationRefresher;
import com.android.wallpaper.module.WallpaperRotationRefresher.Listener;
import com.android.wallpaper.picker.MyPhotosLauncher.PermissionChangedListener;
import com.android.wallpaper.util.DisplayMetricsRetriever;
import com.android.wallpaper.util.ScreenSizeCalculator;
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;
/**
* Displays the Main UI for picking an category of wallpapers to choose from.
*/
public class CategoryPickerFragment extends Fragment {
private static final String TAG = "CategoryPickerFragment";
// The number of ViewHolders that don't pertain to category tiles.
// Currently 2: one for the metadata section and one for the "Select wallpaper" header.
private static final int NUM_NON_CATEGORY_VIEW_HOLDERS = 2;
/**
* The fixed RecyclerView.Adapter position of the ViewHolder for the initial item in the grid --
* usually the wallpaper metadata, or a "permission needed" warning UI.
*/
private static final int INITIAL_HOLDER_ADAPTER_POSITION = 0;
private static final int SETTINGS_APP_INFO_REQUEST_CODE = 1;
private static final String PERMISSION_READ_WALLPAPER_INTERNAL =
"android.permission.READ_WALLPAPER_INTERNAL";
private RecyclerView mImageGrid;
private CategoryAdapter mAdapter;
private ArrayList<Category> mCategories;
private Point mTileSizePx;
private boolean mAwaitingCategories;
private ProgressDialog mRefreshWallpaperProgressDialog;
private boolean mTestingMode;
public CategoryPickerFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mCategories = new ArrayList<>();
mAdapter = new CategoryAdapter(mCategories);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(
R.layout.fragment_category_picker, container, /* attachToRoot */ false);
mImageGrid = (RecyclerView) view.findViewById(R.id.category_grid);
GridMarginDecoration.applyTo(mImageGrid);
mTileSizePx = TileSizeCalculator.getCategoryTileSize(getActivity());
if (LockWallpaperStatusChecker.isLockWallpaperSet(getContext())) {
mAdapter.setNumMetadataCards(CategoryAdapter.METADATA_VIEW_TWO_CARDS);
} else {
mAdapter.setNumMetadataCards(CategoryAdapter.METADATA_VIEW_SINGLE_CARD);
}
mImageGrid.setAdapter(mAdapter);
GridLayoutManager gridLayoutManager = new GridLayoutManager(getActivity(), getNumColumns());
gridLayoutManager.setSpanSizeLookup(new CategorySpanSizeLookup(mAdapter));
mImageGrid.setLayoutManager(gridLayoutManager);
return view;
}
@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);
// Refresh metadata since it may have changed since the activity was paused.
ViewHolder initialViewHolder =
mImageGrid.findViewHolderForAdapterPosition(INITIAL_HOLDER_ADAPTER_POSITION);
MetadataHolder metadataHolder = null;
if (initialViewHolder instanceof MetadataHolder) {
metadataHolder = (MetadataHolder) initialViewHolder;
}
// The wallpaper may have been set while this fragment was paused, so force refresh the current
// wallpapers and presentation mode.
refreshCurrentWallpapers(metadataHolder, true /* forceRefresh */);
}
@Override
public void onDestroy() {
super.onDestroy();
if (mRefreshWallpaperProgressDialog != null) {
mRefreshWallpaperProgressDialog.dismiss();
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == SETTINGS_APP_INFO_REQUEST_CODE) {
mAdapter.notifyDataSetChanged();
}
}
/**
* Inserts the given category into the categories list in priority order.
*/
public void addCategory(Category category, boolean loading) {
// If not previously waiting for categories, enter the waiting state by showing the loading
// indicator.
if (loading && !mAwaitingCategories) {
mAdapter.notifyItemChanged(getNumColumns());
mAdapter.notifyItemInserted(getNumColumns());
mAwaitingCategories = true;
}
int priority = category.getPriority();
int index = 0;
while (index < mCategories.size() && priority >= mCategories.get(index).getPriority()) {
index++;
}
mCategories.add(index, category);
if (mAdapter != null) {
// Offset the index because of the static metadata element at beginning of RecyclerView.
mAdapter.notifyItemInserted(index + NUM_NON_CATEGORY_VIEW_HOLDERS);
}
}
public void removeCategory(Category category) {
int index = mCategories.indexOf(category);
if (index >= 0) {
mCategories.remove(index);
mAdapter.notifyItemRemoved(index + NUM_NON_CATEGORY_VIEW_HOLDERS);
}
}
public void updateCategory(Category category) {
int index = mCategories.indexOf(category);
if (index >= 0) {
mCategories.remove(index);
mCategories.add(index, category);
mAdapter.notifyItemChanged(index + NUM_NON_CATEGORY_VIEW_HOLDERS);
}
}
public void clearCategories() {
mCategories.clear();
mAdapter.notifyDataSetChanged();
}
/**
* Notifies the CategoryPickerFragment that no further categories are expected so it may hide
* the loading indicator.
*/
public void doneFetchingCategories() {
if (mAwaitingCategories) {
mAdapter.notifyItemRemoved(mAdapter.getItemCount() - 1);
mAwaitingCategories = false;
}
}
/**
* 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.
*/
void setTestingMode(boolean testingMode) {
mTestingMode = testingMode;
}
private boolean canShowCurrentWallpaper() {
TopLevelPickerActivity activity = (TopLevelPickerActivity) getActivity();
PackageManager packageManager = activity.getPackageManager();
String packageName = activity.getPackageName();
boolean hasReadWallpaperInternal = packageManager.checkPermission(
PERMISSION_READ_WALLPAPER_INTERNAL, packageName) == PackageManager.PERMISSION_GRANTED;
return hasReadWallpaperInternal || activity.isReadExternalStoragePermissionGranted();
}
/**
* Obtains the {@link WallpaperInfo} object(s) representing the wallpaper(s) currently set to the
* device from the {@link CurrentWallpaperInfoFactory} and binds them to the provided
* {@link MetadataHolder}.
*/
private void refreshCurrentWallpapers(@Nullable final MetadataHolder holder,
boolean forceRefresh) {
CurrentWallpaperInfoFactory factory = InjectorProvider.getInjector()
.getCurrentWallpaperFactory(getActivity().getApplicationContext());
factory.createCurrentWallpaperInfos(new WallpaperInfoCallback() {
@Override
public void onWallpaperInfoCreated(
final WallpaperInfo homeWallpaper,
@Nullable final WallpaperInfo lockWallpaper,
@PresentationMode final int presentationMode) {
// Update the metadata 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 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.
if (getActivity() == null) {
return;
}
int numMetadataCards = (lockWallpaper == null)
? CategoryAdapter.METADATA_VIEW_SINGLE_CARD
: CategoryAdapter.METADATA_VIEW_TWO_CARDS;
mAdapter.setNumMetadataCards(numMetadataCards);
// The MetadataHolder may be null if the RecyclerView has not yet created the view
// holder.
if (holder != null) {
holder.bindWallpapers(homeWallpaper, lockWallpaper, presentationMode);
}
}
});
}
}, forceRefresh);
}
private int getNumColumns() {
return TileSizeCalculator.getNumCategoryColumns(getActivity());
}
/**
* Returns the width to use for the home screen wallpaper in the "single metadata" configuration.
*/
private int getSingleWallpaperImageWidth() {
Point screenSize = ScreenSizeCalculator.getInstance()
.getScreenSize(getActivity().getWindowManager().getDefaultDisplay());
int height = getResources().getDimensionPixelSize(R.dimen.single_metadata_card_layout_height);
return height * screenSize.x / screenSize.y;
}
/**
* Refreshes the current wallpaper in a daily wallpaper rotation.
*/
private void refreshDailyWallpaper() {
// ProgressDialog endlessly updates the UI thread, keeping it from going idle which therefore
// causes Espresso to hang once the dialog is shown.
if (!mTestingMode) {
int themeResId;
if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
themeResId = R.style.ProgressDialogThemePreL;
} else {
themeResId = R.style.LightDialogTheme;
}
mRefreshWallpaperProgressDialog = new ProgressDialog(getActivity(), themeResId);
mRefreshWallpaperProgressDialog.setTitle(null);
mRefreshWallpaperProgressDialog.setMessage(
getResources().getString(R.string.refreshing_daily_wallpaper_dialog_message));
mRefreshWallpaperProgressDialog.setIndeterminate(true);
mRefreshWallpaperProgressDialog.setCancelable(false);
mRefreshWallpaperProgressDialog.show();
}
WallpaperRotationRefresher wallpaperRotationRefresher =
InjectorProvider.getInjector().getWallpaperRotationRefresher();
wallpaperRotationRefresher.refreshWallpaper(getContext(), new Listener() {
@Override
public void onRefreshed() {
// If the fragment is detached from the activity there's nothing to do here and the UI will
// update when the fragment is resumed.
if (getActivity() == null) {
return;
}
if (mRefreshWallpaperProgressDialog != null) {
mRefreshWallpaperProgressDialog.dismiss();
}
ViewHolder initialViewHolder =
mImageGrid.findViewHolderForAdapterPosition(INITIAL_HOLDER_ADAPTER_POSITION);
if (initialViewHolder instanceof MetadataHolder) {
MetadataHolder metadataHolder = (MetadataHolder) initialViewHolder;
// Update the metadata pane since we know now the UI there is stale.
refreshCurrentWallpapers(metadataHolder, true /* forceRefresh */);
}
}
@Override
public void onError() {
if (getActivity() == null) {
return;
}
if (mRefreshWallpaperProgressDialog != null) {
mRefreshWallpaperProgressDialog.dismiss();
}
AlertDialog errorDialog = new AlertDialog.Builder(getActivity(), R.style.LightDialogTheme)
.setMessage(R.string.refresh_daily_wallpaper_failed_message)
.setPositiveButton(android.R.string.ok, null /* onClickListener */)
.create();
errorDialog.show();
}
});
}
/**
* Returns the width to use for the home and lock screen wallpapers in the "both metadata"
* configuration.
*/
private int getBothWallpaperImageWidth() {
DisplayMetrics metrics = DisplayMetricsRetriever.getInstance().getDisplayMetrics(getResources(),
getActivity().getWindowManager().getDefaultDisplay());
// In the "both metadata" configuration, wallpaper images minus the gutters account for the full
// width of the device's screen.
return metrics.widthPixels - (3 * getResources().getDimensionPixelSize(R.dimen.grid_padding));
}
private interface MetadataHolder {
/**
* Binds {@link WallpaperInfo} objects representing the currently-set wallpapers to the
* ViewHolder layout.
*/
void bindWallpapers(WallpaperInfo homeWallpaper, WallpaperInfo lockWallpaper,
@PresentationMode int presentationMode);
}
private static class SelectWallpaperHeaderHolder extends RecyclerView.ViewHolder {
public SelectWallpaperHeaderHolder(View headerView) {
super(headerView);
}
}
/**
* SpanSizeLookup subclass which provides that the item in the first position spans the number of
* columns in the RecyclerView and all other items only take up a single span.
*/
private class CategorySpanSizeLookup extends SpanSizeLookup {
CategoryAdapter mAdapter;
public CategorySpanSizeLookup(CategoryAdapter adapter) {
mAdapter = adapter;
}
@Override
public int getSpanSize(int position) {
if (position < NUM_NON_CATEGORY_VIEW_HOLDERS
|| mAdapter.getItemViewType(position)
== CategoryAdapter.ITEM_VIEW_TYPE_LOADING_INDICATOR) {
return getNumColumns();
}
return 1;
}
}
/**
* ViewHolder subclass for a metadata "card" at the beginning of the RecyclerView.
*/
private class SingleWallpaperMetadataHolder extends RecyclerView.ViewHolder
implements MetadataHolder {
private WallpaperInfo mWallpaperInfo;
private ImageView mWallpaperImage;
private TextView mWallpaperPresentationMode;
private TextView mWallpaperTitle;
private TextView mWallpaperSubtitle;
private ViewGroup mWallpaperExploreSection;
private Button mWallpaperExploreButton;
private ImageButton mWallpaperExploreButtonNoText;
private ImageButton mSkipWallpaperButton;
public SingleWallpaperMetadataHolder(View metadataView) {
super(metadataView);
mWallpaperImage = (ImageView) metadataView.findViewById(R.id.wallpaper_image);
mWallpaperImage.getLayoutParams().width = getSingleWallpaperImageWidth();
mWallpaperPresentationMode =
(TextView) metadataView.findViewById(R.id.wallpaper_presentation_mode);
mWallpaperTitle = (TextView) metadataView.findViewById(R.id.wallpaper_title);
mWallpaperSubtitle = (TextView) metadataView.findViewById(R.id.wallpaper_subtitle);
mWallpaperExploreSection =
(ViewGroup) metadataView.findViewById(R.id.wallpaper_explore_section);
mWallpaperExploreButton =
(Button) metadataView.findViewById(R.id.wallpaper_explore_button);
mWallpaperExploreButtonNoText = (ImageButton)
metadataView.findViewById(R.id.wallpaper_explore_button_notext);
mSkipWallpaperButton = (ImageButton) metadataView.findViewById(R.id.skip_wallpaper_button);
if (Flags.skipDailyWallpaperButtonEnabled) {
Drawable exploreButtonDrawable = getContext().getDrawable(
R.drawable.ic_explore_24px);
// This Drawable's state is shared across the app, so make a copy of it before applying a
// color tint as not to affect other clients elsewhere in the app.
exploreButtonDrawable = exploreButtonDrawable.getConstantState().newDrawable().mutate();
exploreButtonDrawable.setColorFilter(
getResources().getColor(R.color.currently_set_explore_button_color), Mode.SRC_IN);
ButtonDrawableSetterCompat.setDrawableToButtonStart(
mWallpaperExploreButton, exploreButtonDrawable);
}
}
/**
* Binds home screen wallpaper to the ViewHolder layout.
*/
@Override
public void bindWallpapers(WallpaperInfo homeWallpaper, WallpaperInfo lockWallpaper,
@PresentationMode int presentationMode) {
mWallpaperInfo = homeWallpaper;
bindWallpaperAsset();
bindWallpaperText(presentationMode);
bindWallpaperActionButtons(presentationMode);
}
private void bindWallpaperAsset() {
final UserEventLogger eventLogger =
InjectorProvider.getInjector().getUserEventLogger(getActivity());
mWallpaperInfo.getThumbAsset(getActivity().getApplicationContext()).loadDrawable(
getActivity(), mWallpaperImage, getResources().getColor(R.color.secondary_color));
mWallpaperImage.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
((TopLevelPickerActivity) getActivity()).showViewOnlyPreview(mWallpaperInfo);
eventLogger.logCurrentWallpaperPreviewed();
}
});
}
private void bindWallpaperText(@PresentationMode int presentationMode) {
Context appContext = getActivity().getApplicationContext();
mWallpaperPresentationMode.setText(
AttributionFormatter.getHumanReadableWallpaperPresentationMode(
appContext, presentationMode));
List<String> attributions = mWallpaperInfo.getAttributions(appContext);
if (!attributions.isEmpty()) {
mWallpaperTitle.setText(attributions.get(0));
}
String subtitleText =
AttributionFormatter.formatWallpaperSubtitle(appContext, mWallpaperInfo);
if (!TextUtils.isEmpty(subtitleText)) {
mWallpaperSubtitle.setText(subtitleText);
mWallpaperSubtitle.setVisibility(View.VISIBLE);
} else {
mWallpaperSubtitle.setVisibility(View.GONE);
}
}
private void bindWallpaperActionButtons(@PresentationMode int presentationMode) {
final Context appContext = getActivity().getApplicationContext();
final String actionUrl = mWallpaperInfo.getActionUrl(appContext);
if (actionUrl != null && !actionUrl.isEmpty()) {
Uri exploreUri = Uri.parse(actionUrl);
ExploreIntentChecker intentChecker =
InjectorProvider.getInjector().getExploreIntentChecker(appContext);
intentChecker.fetchValidActionViewIntent(exploreUri, (@Nullable Intent exploreIntent) -> {
if (getActivity() == null) {
return;
}
updateExploreSectionVisibility(presentationMode, exploreIntent);
});
} else {
updateExploreSectionVisibility(presentationMode, null /* exploreIntent */);
}
}
/**
* Shows or hides appropriate elements in the "Explore section" (containing the Explore button
* and the Next Wallpaper button) depending on the current wallpaper.
*
* @param presentationMode The presentation mode of the current wallpaper.
* @param exploreIntent An optional explore intent for the current wallpaper.
*/
private void updateExploreSectionVisibility(
@PresentationMode int presentationMode, @Nullable Intent exploreIntent) {
final Context appContext = getActivity().getApplicationContext();
final UserEventLogger eventLogger =
InjectorProvider.getInjector().getUserEventLogger(appContext);
boolean showSkipWallpaperButton = Flags.skipDailyWallpaperButtonEnabled
&& presentationMode == WallpaperPreferences.PRESENTATION_MODE_ROTATING;
if (exploreIntent != null || showSkipWallpaperButton) {
mWallpaperExploreSection.setVisibility(View.VISIBLE);
if (exploreIntent != null) {
View exploreButton;
if (showSkipWallpaperButton) {
exploreButton = mWallpaperExploreButtonNoText;
mWallpaperExploreButtonNoText.setImageDrawable(getContext().getDrawable(
mWallpaperInfo.getActionIconRes(appContext)));
mWallpaperExploreButtonNoText.setContentDescription(
getString(mWallpaperInfo.getActionLabelRes(appContext)));
mWallpaperExploreButtonNoText.setColorFilter(
getResources().getColor(R.color.currently_set_explore_button_color),
Mode.SRC_IN);
mWallpaperExploreButton.setVisibility(View.GONE);
} else {
exploreButton = mWallpaperExploreButton;
Drawable drawable = getContext().getDrawable(
mWallpaperInfo.getActionIconRes(appContext)).getConstantState()
.newDrawable().mutate();
// Color the "compass" icon with the accent color.
drawable.setColorFilter(
getResources().getColor(R.color.accent_color), Mode.SRC_IN);
ButtonDrawableSetterCompat.setDrawableToButtonStart(
mWallpaperExploreButton, drawable);
mWallpaperExploreButton.setText(
mWallpaperInfo.getActionLabelRes(appContext));
mWallpaperExploreButtonNoText.setVisibility(View.GONE);
}
exploreButton.setVisibility(View.VISIBLE);
exploreButton.setOnClickListener((View view) -> {
eventLogger.logActionClicked(mWallpaperInfo.getCollectionId(appContext),
mWallpaperInfo.getActionLabelRes(appContext));
startActivity(exploreIntent);
});
}
if (showSkipWallpaperButton) {
mSkipWallpaperButton.setVisibility(View.VISIBLE);
mSkipWallpaperButton.setColorFilter(
getResources().getColor(R.color.currently_set_explore_button_color), Mode.SRC_IN);
mSkipWallpaperButton.setOnClickListener((View view) -> refreshDailyWallpaper());
}
} else {
mWallpaperExploreSection.setVisibility(View.GONE);
}
}
}
/**
* ViewHolder subclass for a metadata "card" at the beginning of the RecyclerView that shows
* both home screen and lock screen wallpapers.
*/
private class TwoWallpapersMetadataHolder extends RecyclerView.ViewHolder
implements MetadataHolder {
private WallpaperInfo mHomeWallpaperInfo;
private FrameLayout mHomeWallpaperSection;
private ImageView mHomeWallpaperImage;
private LinearLayout mHomeWallpaperTopSection;
private TextView mHomeWallpaperPresentationMode;
private TextView mHomeWallpaperTitle;
private TextView mHomeWallpaperSubtitle1;
private TextView mHomeWallpaperSubtitle2;
private ImageButton mHomeWallpaperExploreButtonLegacy;
private ImageButton mHomeWallpaperExploreButton;
private ImageButton mSkipWallpaperButton;
private FrameLayout mLockWallpaperSection;
private WallpaperInfo mLockWallpaperInfo;
private ImageView mLockWallpaperImage;
private LinearLayout mLockWallpaperTopSection;
private TextView mLockWallpaperPresentationMode;
private TextView mLockWallpaperTitle;
private TextView mLockWallpaperSubtitle1;
private TextView mLockWallpaperSubtitle2;
private ImageButton mLockWallpaperExploreButtonLegacy;
private ImageButton mLockWallpaperExploreButton;
public TwoWallpapersMetadataHolder(View metadataView) {
super(metadataView);
// Set the min width of the metadata panel to be the screen width minus space for the
// 2 gutters on the sides. This ensures the RecyclerView's GridLayoutManager gives it
// a wide-enough initial width to fill up the width of the grid prior to the view being
// fully populated.
final Display display = getActivity().getWindowManager().getDefaultDisplay();
Point screenSize = ScreenSizeCalculator.getInstance().getScreenSize(display);
metadataView.setMinimumWidth(
screenSize.x - 2 * getResources().getDimensionPixelSize(R.dimen.grid_padding));
int bothWallpaperImageWidth = getBothWallpaperImageWidth();
mHomeWallpaperSection = (FrameLayout) metadataView.findViewById(R.id.home_wallpaper_section);
mHomeWallpaperSection.setMinimumWidth(bothWallpaperImageWidth);
mHomeWallpaperImage = (ImageView) metadataView.findViewById(R.id.home_wallpaper_image);
mHomeWallpaperTopSection =
(LinearLayout) metadataView.findViewById(R.id.home_wallpaper_top_section);
mHomeWallpaperPresentationMode =
(TextView) metadataView.findViewById(R.id.home_wallpaper_presentation_mode);
mHomeWallpaperTitle = (TextView) metadataView.findViewById(R.id.home_wallpaper_title);
mHomeWallpaperSubtitle1 = (TextView) metadataView.findViewById(R.id.home_wallpaper_subtitle1);
mHomeWallpaperSubtitle2 = (TextView) metadataView.findViewById(R.id.home_wallpaper_subtitle2);
mHomeWallpaperExploreButtonLegacy =
(ImageButton) metadataView.findViewById(R.id.home_wallpaper_explore_button_legacy);
mHomeWallpaperExploreButton =
(ImageButton) metadataView.findViewById(R.id.home_wallpaper_explore_button);
mSkipWallpaperButton = (ImageButton) metadataView.findViewById(R.id.skip_home_wallpaper);
mLockWallpaperSection = (FrameLayout) metadataView.findViewById(R.id.lock_wallpaper_section);
mLockWallpaperSection.setMinimumWidth(bothWallpaperImageWidth);
mLockWallpaperImage = (ImageView) metadataView.findViewById(R.id.lock_wallpaper_image);
mLockWallpaperTopSection =
(LinearLayout) metadataView.findViewById(R.id.lock_wallpaper_top_section);
mLockWallpaperPresentationMode =
(TextView) metadataView.findViewById(R.id.lock_wallpaper_presentation_mode);
mLockWallpaperTitle = (TextView) metadataView.findViewById(R.id.lock_wallpaper_title);
mLockWallpaperSubtitle1 = (TextView) metadataView.findViewById(R.id.lock_wallpaper_subtitle1);
mLockWallpaperSubtitle2 = (TextView) metadataView.findViewById(R.id.lock_wallpaper_subtitle2);
mLockWallpaperExploreButtonLegacy =
(ImageButton) metadataView.findViewById(R.id.lock_wallpaper_explore_button_legacy);
mLockWallpaperExploreButton =
(ImageButton) metadataView.findViewById(R.id.lock_wallpaper_explore_button);
}
@Override
public void bindWallpapers(WallpaperInfo homeWallpaper, WallpaperInfo lockWallpaper,
@PresentationMode int presentationMode) {
bindHomeWallpaper(homeWallpaper, presentationMode);
bindLockWallpaper(lockWallpaper);
}
private void bindHomeWallpaper(WallpaperInfo homeWallpaper,
@PresentationMode int presentationMode) {
final Context appContext = getActivity().getApplicationContext();
final UserEventLogger eventLogger =
InjectorProvider.getInjector().getUserEventLogger(appContext);
mHomeWallpaperInfo = homeWallpaper;
homeWallpaper.getThumbAsset(appContext).loadDrawable(
getActivity(), mHomeWallpaperImage, getResources().getColor(R.color.secondary_color));
mHomeWallpaperTopSection.setVisibility(View.VISIBLE);
mHomeWallpaperPresentationMode.setText(
AttributionFormatter.getHumanReadableWallpaperPresentationMode(
appContext, presentationMode));
List<String> attributions = homeWallpaper.getAttributions(appContext);
if (!attributions.isEmpty()) {
mHomeWallpaperTitle.setText(attributions.get(0));
}
if (attributions.size() > 1) {
mHomeWallpaperSubtitle1.setText(attributions.get(1));
}
if (attributions.size() > 2) {
mHomeWallpaperSubtitle2.setText(attributions.get(2));
}
final String homeActionUrl = homeWallpaper.getActionUrl(appContext);
ImageButton exploreButton = Flags.skipDailyWallpaperButtonEnabled
? mHomeWallpaperExploreButton
: mHomeWallpaperExploreButtonLegacy;
if (homeActionUrl != null && !homeActionUrl.isEmpty()) {
Uri homeExploreUri = Uri.parse(homeActionUrl);
ExploreIntentChecker intentChecker =
InjectorProvider.getInjector().getExploreIntentChecker(appContext);
intentChecker.fetchValidActionViewIntent(
homeExploreUri, (@Nullable Intent exploreIntent) -> {
if (exploreIntent == null || getActivity() == null) {
return;
}
exploreButton.setVisibility(View.VISIBLE);
exploreButton.setImageDrawable(getContext().getDrawable(
homeWallpaper.getActionIconRes(appContext)));
exploreButton.setContentDescription(getString(homeWallpaper
.getActionLabelRes(appContext)));
exploreButton.setColorFilter(
getResources().getColor(R.color.currently_set_explore_button_color), Mode.SRC_IN);
exploreButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
eventLogger.logActionClicked(
mHomeWallpaperInfo.getCollectionId(appContext),
mHomeWallpaperInfo.getActionLabelRes(appContext));
startActivity(exploreIntent);
}
});
});
} else {
exploreButton.setVisibility(View.GONE);
}
if (Flags.skipDailyWallpaperButtonEnabled
&& presentationMode == WallpaperPreferences.PRESENTATION_MODE_ROTATING) {
mSkipWallpaperButton.setVisibility(View.VISIBLE);
mSkipWallpaperButton.setColorFilter(
getResources().getColor(R.color.currently_set_explore_button_color), Mode.SRC_IN);
mSkipWallpaperButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
refreshDailyWallpaper();
}
});
} else {
mSkipWallpaperButton.setVisibility(View.GONE);
}
mHomeWallpaperImage.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
eventLogger.logCurrentWallpaperPreviewed();
((TopLevelPickerActivity) getActivity()).showViewOnlyPreview(mHomeWallpaperInfo);
}
});
}
private void bindLockWallpaper(WallpaperInfo lockWallpaper) {
if (lockWallpaper == null) {
Log.e(TAG, "TwoWallpapersMetadataHolder bound without a lock screen wallpaper.");
return;
}
final Context appContext = getActivity().getApplicationContext();
final UserEventLogger eventLogger =
InjectorProvider.getInjector().getUserEventLogger(getActivity());
mLockWallpaperInfo = lockWallpaper;
lockWallpaper.getThumbAsset(appContext).loadDrawable(
getActivity(), mLockWallpaperImage, getResources().getColor(R.color.secondary_color));
mLockWallpaperTopSection.setVisibility(View.VISIBLE);
// Daily wallpaper rotation can't be in effect on only the lock screen, so if there's a
// separate lock screen, it must be presented as "Currently set".
mLockWallpaperPresentationMode.setText(
AttributionFormatter.getHumanReadableWallpaperPresentationMode(
appContext, WallpaperPreferences.PRESENTATION_MODE_STATIC));
List<String> lockAttributions = lockWallpaper.getAttributions(appContext);
if (!lockAttributions.isEmpty()) {
mLockWallpaperTitle.setText(lockAttributions.get(0));
}
if (lockAttributions.size() > 1) {
mLockWallpaperSubtitle1.setText(lockAttributions.get(1));
}
if (lockAttributions.size() > 2) {
mLockWallpaperSubtitle2.setText(lockAttributions.get(2));
}
final String lockActionUrl = lockWallpaper.getActionUrl(appContext);
ImageButton exploreButton = Flags.skipDailyWallpaperButtonEnabled
? mLockWallpaperExploreButton
: mLockWallpaperExploreButtonLegacy;
if (lockActionUrl != null && !lockActionUrl.isEmpty()) {
Uri lockExploreUri = Uri.parse(lockActionUrl);
ExploreIntentChecker intentChecker =
InjectorProvider.getInjector().getExploreIntentChecker(appContext);
intentChecker.fetchValidActionViewIntent(
lockExploreUri, (@Nullable Intent exploreIntent) -> {
if (exploreIntent == null || getActivity() == null) {
return;
}
exploreButton.setImageDrawable(getContext().getDrawable(
lockWallpaper.getActionIconRes(appContext)));
exploreButton.setContentDescription(getString(
lockWallpaper.getActionLabelRes(appContext)));
exploreButton.setVisibility(View.VISIBLE);
exploreButton.setColorFilter(
getResources().getColor(
R.color.currently_set_explore_button_color),
Mode.SRC_IN);
exploreButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
eventLogger.logActionClicked(
mLockWallpaperInfo.getCollectionId(appContext),
mLockWallpaperInfo.getActionLabelRes(appContext));
startActivity(exploreIntent);
}
});
});
} else {
exploreButton.setVisibility(View.GONE);
}
mLockWallpaperImage.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
eventLogger.logCurrentWallpaperPreviewed();
((TopLevelPickerActivity) getActivity()).showViewOnlyPreview(mLockWallpaperInfo);
}
});
}
}
/**
* ViewHolder subclass for a category tile in the RecyclerView.
*/
private class CategoryHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private Category mCategory;
private RelativeLayout mTileLayout;
private ImageView mImageView;
private ImageView mOverlayIconView;
private TextView mTitleView;
public CategoryHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(this);
mTileLayout = (RelativeLayout) itemView.findViewById(R.id.tile);
mImageView = (ImageView) itemView.findViewById(R.id.image);
mOverlayIconView = (ImageView) itemView.findViewById(R.id.overlay_icon);
mTitleView = (TextView) itemView.findViewById(R.id.category_title);
mTileLayout.getLayoutParams().height = mTileSizePx.y;
}
@Override
public void onClick(View view) {
final UserEventLogger eventLogger =
InjectorProvider.getInjector().getUserEventLogger(getActivity());
eventLogger.logCategorySelected(mCategory.getCollectionId());
if (mCategory.supportsCustomPhotos()) {
((MyPhotosLauncher) getActivity()).requestCustomPhotoPicker(
new PermissionChangedListener() {
@Override
public void onPermissionsGranted() {
drawThumbnailAndOverlayIcon();
}
@Override
public void onPermissionsDenied(boolean dontAskAgain) {
// No-op
}
});
return;
}
((TopLevelPickerActivity) getActivity()).show(mCategory.getCollectionId());
}
/**
* Binds the given category to this CategoryHolder.
*/
public void bindCategory(Category category) {
mCategory = category;
mTitleView.setText(category.getTitle());
drawThumbnailAndOverlayIcon();
}
/**
* Draws the CategoryHolder's thumbnail and overlay icon.
*/
public void drawThumbnailAndOverlayIcon() {
mOverlayIconView.setImageDrawable(mCategory.getOverlayIcon(
getActivity().getApplicationContext()));
// Size the overlay icon according to the category.
int overlayIconDimenDp = mCategory.getOverlayIconSizeDp();
DisplayMetrics metrics = DisplayMetricsRetriever.getInstance().getDisplayMetrics(
getResources(), getActivity().getWindowManager().getDefaultDisplay());
int overlayIconDimenPx = (int) (overlayIconDimenDp * metrics.density);
mOverlayIconView.getLayoutParams().width = overlayIconDimenPx;
mOverlayIconView.getLayoutParams().height = overlayIconDimenPx;
Asset thumbnail = mCategory.getThumbnail(getActivity().getApplicationContext());
if (thumbnail != null) {
thumbnail.loadDrawable(getActivity(), mImageView,
getResources().getColor(R.color.secondary_color));
} else {
// TODO(orenb): Replace this workaround for b/62584914 with a proper way of unloading the
// ImageView such that no incorrect image is improperly loaded upon rapid scroll.
Object nullObj = null;
Glide.with(getActivity())
.asDrawable()
.load(nullObj)
.into(mImageView);
mTitleView.setBackgroundColor(getResources().getColor(
R.color.category_title_scrim_color_no_thumbnail));
}
}
}
/**
* ViewHolder subclass for the loading indicator ("spinner") shown when categories are being
* fetched.
*/
private class LoadingIndicatorHolder extends RecyclerView.ViewHolder {
public LoadingIndicatorHolder(View view) {
super(view);
ProgressBar progressBar = (ProgressBar) view.findViewById(R.id.loading_indicator);
progressBar.getIndeterminateDrawable().setColorFilter(
getResources().getColor(R.color.accent_color), Mode.SRC_IN);
}
}
/**
* ViewHolder subclass for a "card" at the beginning of the RecyclerView showing the app needs the
* user to grant the storage permission to show the currently set wallpaper.
*/
private class PermissionNeededHolder extends RecyclerView.ViewHolder {
private Button mAllowAccessButton;
public PermissionNeededHolder(View view) {
super(view);
mAllowAccessButton =
(Button) view.findViewById(R.id.permission_needed_allow_access_button);
mAllowAccessButton.setOnClickListener((View v) -> {
((TopLevelPickerActivity) getActivity()).requestExternalStoragePermission(mAdapter);
});
// Replace explanation text with text containing the Wallpapers app name which replaces the
// placeholder.
String appName = getString(R.string.app_name);
String explanation = getString(R.string.permission_needed_explanation, appName);
TextView explanationTextView =
(TextView) view.findViewById(R.id.permission_needed_explanation);
explanationTextView.setText(explanation);
}
}
/**
* RecyclerView Adapter subclass for the category tiles in the RecyclerView.
*/
private class CategoryAdapter extends RecyclerView.Adapter<ViewHolder>
implements PermissionChangedListener {
public static final int METADATA_VIEW_SINGLE_CARD = 1;
public static final int METADATA_VIEW_TWO_CARDS = 2;
private static final int ITEM_VIEW_TYPE_METADATA = 1;
private static final int ITEM_VIEW_TYPE_SELECT_WALLPAPER_HEADER = 2;
private static final int ITEM_VIEW_TYPE_CATEGORY = 3;
private static final int ITEM_VIEW_TYPE_LOADING_INDICATOR = 4;
private static final int ITEM_VIEW_TYPE_PERMISSION_NEEDED = 5;
private List<Category> mCategories;
private int mNumMetadataCards;
public CategoryAdapter(List<Category> categories) {
mCategories = categories;
mNumMetadataCards = METADATA_VIEW_SINGLE_CARD;
}
/**
* Sets the number of metadata cards to be shown in the metadata view holder. Updates the UI
* to reflect any changes in that number (e.g., a lock screen wallpaper has been set so we now
* need to show two cards).
*/
public void setNumMetadataCards(int numMetadataCards) {
if (numMetadataCards != mNumMetadataCards && getItemCount() > 0) {
notifyItemChanged(0);
}
mNumMetadataCards = numMetadataCards;
}
@Override
public int getItemViewType(int position) {
if (position == 0) {
if (canShowCurrentWallpaper()) {
return ITEM_VIEW_TYPE_METADATA;
} else {
return ITEM_VIEW_TYPE_PERMISSION_NEEDED;
}
} else if (position == 1) {
return ITEM_VIEW_TYPE_SELECT_WALLPAPER_HEADER;
} else if (mAwaitingCategories && position == getItemCount() - 1) {
return ITEM_VIEW_TYPE_LOADING_INDICATOR;
}
return ITEM_VIEW_TYPE_CATEGORY;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
View view;
switch (viewType) {
case ITEM_VIEW_TYPE_METADATA:
if (mNumMetadataCards == METADATA_VIEW_SINGLE_CARD) {
view = layoutInflater.inflate(
R.layout.grid_item_single_metadata, parent, /* attachToRoot */ false);
return new SingleWallpaperMetadataHolder(view);
} else { // TWO_CARDS
view = layoutInflater.inflate(
R.layout.grid_item_both_metadata, parent, /* attachToRoot */ false);
return new TwoWallpapersMetadataHolder(view);
}
case ITEM_VIEW_TYPE_SELECT_WALLPAPER_HEADER:
view = layoutInflater.inflate(
R.layout.grid_item_select_wallpaper_header, parent, /* attachToRoot */ false);
return new SelectWallpaperHeaderHolder(view);
case ITEM_VIEW_TYPE_LOADING_INDICATOR:
view = layoutInflater.inflate(
R.layout.grid_item_loading_indicator, parent, /* attachToRoot */ false);
return new LoadingIndicatorHolder(view);
case ITEM_VIEW_TYPE_CATEGORY:
view = layoutInflater.inflate(
R.layout.grid_item_category, parent, /* attachToRoot */ false);
return new CategoryHolder(view);
case ITEM_VIEW_TYPE_PERMISSION_NEEDED:
view = layoutInflater.inflate(
R.layout.grid_item_permission_needed, parent, /* attachToRoot */ false);
return new PermissionNeededHolder(view);
default:
Log.e(TAG, "Unsupported viewType " + viewType + " in CategoryAdapter");
return null;
}
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
int viewType = getItemViewType(position);
switch (viewType) {
case ITEM_VIEW_TYPE_METADATA:
refreshCurrentWallpapers((MetadataHolder) holder, false /* forceRefresh */);
break;
case ITEM_VIEW_TYPE_SELECT_WALLPAPER_HEADER:
// No op.
break;
case ITEM_VIEW_TYPE_CATEGORY:
// Offset position to get category index to account for the non-category view holders.
Category category = mCategories.get(position - NUM_NON_CATEGORY_VIEW_HOLDERS);
((CategoryHolder) holder).bindCategory(category);
break;
case ITEM_VIEW_TYPE_LOADING_INDICATOR:
case ITEM_VIEW_TYPE_PERMISSION_NEEDED:
// No op.
break;
default:
Log.e(TAG, "Unsupported viewType " + viewType + " in CategoryAdapter");
}
}
@Override
public int getItemCount() {
// Add to size of categories to account for the metadata related views.
// Add 1 more for the loading indicator if not yet done loading.
int size = mCategories.size() + NUM_NON_CATEGORY_VIEW_HOLDERS;
if (mAwaitingCategories) {
size += 1;
}
return size;
}
@Override
public void onPermissionsGranted() {
notifyDataSetChanged();
}
@Override
public void onPermissionsDenied(boolean dontAskAgain) {
if (!dontAskAgain) {
return;
}
String permissionNeededMessage =
getString(R.string.permission_needed_explanation_go_to_settings);
AlertDialog dialog = new AlertDialog.Builder(getActivity(), R.style.LightDialogTheme)
.setMessage(permissionNeededMessage)
.setPositiveButton(android.R.string.ok, null /* onClickListener */)
.setNegativeButton(
R.string.settings_button_label,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Intent appInfoIntent = new Intent();
appInfoIntent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts(
"package", getActivity().getPackageName(), null /* fragment */);
appInfoIntent.setData(uri);
startActivityForResult(appInfoIntent, SETTINGS_APP_INFO_REQUEST_CODE);
}
})
.create();
dialog.show();
}
}
}