blob: 2bab8631f87f1f9464984abd0dfb6f1bd0337ddc [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 static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.makeMeasureSpec;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Point;
import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.net.Uri;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Message;
import android.os.RemoteException;
import android.provider.Settings;
import android.service.wallpaper.WallpaperService;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
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.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.cardview.widget.CardView;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.android.wallpaper.R;
import com.android.wallpaper.config.Flags;
import com.android.wallpaper.model.Category;
import com.android.wallpaper.model.LiveWallpaperInfo;
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.InjectorProvider;
import com.android.wallpaper.module.UserEventLogger;
import com.android.wallpaper.module.WallpaperPersister;
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.CategorySelectorFragment.CategorySelectorFragmentHost;
import com.android.wallpaper.picker.MyPhotosStarter.MyPhotosStarterProvider;
import com.android.wallpaper.picker.MyPhotosStarter.PermissionChangedListener;
import com.android.wallpaper.picker.individual.IndividualPickerFragment;
import com.android.wallpaper.picker.individual.IndividualPickerFragment.ThumbnailUpdater;
import com.android.wallpaper.picker.individual.IndividualPickerFragment.WallpaperDestinationCallback;
import com.android.wallpaper.util.DisplayMetricsRetriever;
import com.android.wallpaper.util.PreviewUtils;
import com.android.wallpaper.util.ScreenSizeCalculator;
import com.android.wallpaper.util.SurfaceViewUtils;
import com.android.wallpaper.util.TileSizeCalculator;
import com.android.wallpaper.util.WallpaperConnection;
import com.android.wallpaper.util.WallpaperConnection.WallpaperConnectionListener;
import com.android.wallpaper.widget.LiveTileOverlay;
import com.android.wallpaper.widget.PreviewPager;
import com.bumptech.glide.Glide;
import com.bumptech.glide.MemoryCategory;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* Displays the Main UI for picking a category of wallpapers to choose from.
*/
public class CategoryFragment extends AppbarFragment
implements CategorySelectorFragmentHost, ThumbnailUpdater, WallpaperDestinationCallback {
private final Rect mPreviewLocalRect = new Rect();
private final Rect mPreviewGlobalRect = new Rect();
private final int[] mLivePreviewLocation = new int[2];
/**
* Interface to be implemented by an Activity hosting a {@link CategoryFragment}
*/
public interface CategoryFragmentHost extends MyPhotosStarterProvider {
void requestExternalStoragePermission(PermissionChangedListener listener);
boolean isReadExternalStoragePermissionGranted();
void showViewOnlyPreview(WallpaperInfo wallpaperInfo);
}
public static CategoryFragment newInstance(CharSequence title) {
CategoryFragment fragment = new CategoryFragment();
fragment.setArguments(AppbarFragment.createArguments(title));
return fragment;
}
private static final String TAG = "CategoryFragment";
private static final int MAX_ALPHA = 255;
// 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 = 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 ProgressDialog mRefreshWallpaperProgressDialog;
private boolean mTestingMode;
private ImageView mHomePreview;
private SurfaceView mWorkspaceSurface;
private SurfaceView mWallpaperSurface;
private ImageView mLockscreenPreview;
private PreviewPager mPreviewPager;
private List<View> mWallPaperPreviews;
private WallpaperConnection mWallpaperConnection;
private CategorySelectorFragment mCategorySelectorFragment;
private IndividualPickerFragment mIndividualPickerFragment;
private boolean mShowSelectedWallpaper;
private BottomSheetBehavior<View> mBottomSheetBehavior;
private PreviewUtils mPreviewUtils;
private int mSelectedPreviewPage;
// The wallpaper information which is currently shown on the home preview.
private WallpaperInfo mHomePreviewWallpaperInfo;
// The wallpaper information which is currently shown on the lock preview.
private WallpaperInfo mLockPreviewWallpaperInfo;
// Home workspace surface is behind the app window, and so must the home image wallpaper like
// the live wallpaper. This view is rendered on mWallpaperSurface for home image wallpaper.
private ImageView mHomeImageWallpaper;
private boolean mIsCollapsingByUserSelecting;
public CategoryFragment() {
mCategorySelectorFragment = new CategorySelectorFragment();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(
R.layout.fragment_category_picker, container, /* attachToRoot= */ false);
mWallPaperPreviews = new ArrayList<>();
CardView homePreviewCard = (CardView) inflater.inflate(
R.layout.wallpaper_preview_card, null);
mHomePreview = homePreviewCard.findViewById(R.id.wallpaper_preview_image);
mWorkspaceSurface = homePreviewCard.findViewById(R.id.workspace_surface);
mWallpaperSurface = homePreviewCard.findViewById(R.id.wallpaper_surface);
mWallPaperPreviews.add(homePreviewCard);
CardView lockscreenPreviewCard = (CardView) inflater.inflate(
R.layout.wallpaper_preview_card, null);
mLockscreenPreview = lockscreenPreviewCard.findViewById(R.id.wallpaper_preview_image);
lockscreenPreviewCard.findViewById(R.id.workspace_surface).setVisibility(View.GONE);
lockscreenPreviewCard.findViewById(R.id.wallpaper_surface).setVisibility(View.GONE);
mWallPaperPreviews.add(lockscreenPreviewCard);
mPreviewPager = view.findViewById(R.id.wallpaper_preview_pager);
mPreviewPager.setAdapter(new PreviewPagerAdapter(mWallPaperPreviews));
mPreviewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
// For live wallpaper, show its thumbnail when scrolling.
if (mHomePreviewWallpaperInfo instanceof LiveWallpaperInfo) {
if (positionOffset == 0.0f) {
// The page is not moved. Show live wallpaper.
mWorkspaceSurface.setZOrderMediaOverlay(true);
mWallpaperSurface.setZOrderMediaOverlay(false);
} else {
// The page is moving. Show live wallpaper's thumbnail.
mWorkspaceSurface.setZOrderOnTop(true);
mWallpaperSurface.setZOrderMediaOverlay(true);
}
}
if (mLockPreviewWallpaperInfo instanceof LiveWallpaperInfo) {
if (positionOffset == 0.0f) {
// The page is not moved. Show live wallpaper.
LiveTileOverlay.INSTANCE.attach(mLockscreenPreview.getOverlay());
} else {
// The page is moving. Show live wallpaper's thumbnail.
LiveTileOverlay.INSTANCE.detach(mLockscreenPreview.getOverlay());
}
} else {
LiveTileOverlay.INSTANCE.detach(mLockscreenPreview.getOverlay());
}
}
@Override
public void onPageSelected(int i) {
mSelectedPreviewPage = i;
if (mIndividualPickerFragment != null && mIndividualPickerFragment.isVisible()) {
mIndividualPickerFragment.highlightAppliedWallpaper(i);
}
}
@Override
public void onPageScrollStateChanged(int i) {
}
});
setupCurrentWallpaperPreview(view);
View fragmentContainer = view.findViewById(R.id.category_fragment_container);
mBottomSheetBehavior = BottomSheetBehavior.from(fragmentContainer);
mBottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
if (mIsCollapsingByUserSelecting) {
mIsCollapsingByUserSelecting = newState != STATE_COLLAPSED;
return;
}
if (mIndividualPickerFragment != null && mIndividualPickerFragment.isVisible()) {
mIndividualPickerFragment.resizeLayout(newState == STATE_COLLAPSED
? mBottomSheetBehavior.getPeekHeight() : MATCH_PARENT);
}
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
}
});
fragmentContainer.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View containerView, int left, int top, int right,
int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
int minimumHeight = containerView.getHeight() - mPreviewPager.getMeasuredHeight();
mBottomSheetBehavior.setPeekHeight(minimumHeight);
containerView.setMinimumHeight(minimumHeight);
((CardView) mHomePreview.getParent())
.setRadius(TileSizeCalculator.getPreviewCornerRadius(
getActivity(), homePreviewCard.getMeasuredWidth()));
if (mLockscreenPreview != null) {
((CardView) mLockscreenPreview.getParent())
.setRadius(TileSizeCalculator.getPreviewCornerRadius(
getActivity(), mLockscreenPreview.getMeasuredWidth()));
}
fragmentContainer.removeOnLayoutChangeListener(this);
}});
mPreviewUtils = new PreviewUtils(getContext(),
getString(R.string.grid_control_metadata_name));
setUpToolbar(view);
getChildFragmentManager()
.beginTransaction()
.replace(R.id.category_fragment_container, mCategorySelectorFragment)
.commitNow();
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
updateWallpaperSurface();
updateWorkspaceSurface();
}
@Override
public CharSequence getDefaultTitle() {
return getContext().getString(R.string.app_name);
}
@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);
// The wallpaper may have been set while this fragment was paused, so force refresh the current
// wallpapers and presentation mode.
if (!mShowSelectedWallpaper) {
refreshCurrentWallpapers(/* MetadataHolder= */ null, /* forceRefresh= */ true);
}
if (mWallpaperConnection != null) {
mWallpaperConnection.setVisibility(true);
}
}
@Override
public void onPause() {
super.onPause();
if (mWallpaperConnection != null) {
mWallpaperConnection.setVisibility(false);
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
LiveTileOverlay.INSTANCE.detach(mHomePreview.getOverlay());
LiveTileOverlay.INSTANCE.detach(mLockscreenPreview.getOverlay());
if (mWallpaperConnection != null) {
mWallpaperConnection.disconnect();
mWallpaperConnection = null;
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (mWallpaperConnection != null) {
mWallpaperConnection.disconnect();
mWallpaperConnection = null;
}
if (mRefreshWallpaperProgressDialog != null) {
mRefreshWallpaperProgressDialog.dismiss();
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == SETTINGS_APP_INFO_REQUEST_CODE) {
mCategorySelectorFragment.notifyDataSetChanged();
}
}
@Override
public void requestCustomPhotoPicker(PermissionChangedListener listener) {
getFragmentHost().getMyPhotosStarter().requestCustomPhotoPicker(listener);
}
@Override
public void show(String collectionId) {
mIndividualPickerFragment =
InjectorProvider.getInjector().getIndividualPickerFragment(collectionId);
mIndividualPickerFragment.highlightAppliedWallpaper(mSelectedPreviewPage);
mIndividualPickerFragment.setOnWallpaperSelectedListener(position -> {
// Scroll to the selected wallpaper and collapse the sheet if needed.
// Resize and scroll here because we want to let the RecyclerView's scrolling and
// BottomSheet's collapsing can be executed together instead of scrolling
// the RecyclerView after the BottomSheet is collapsed.
mIndividualPickerFragment.resizeLayout(mBottomSheetBehavior.getPeekHeight());
mIndividualPickerFragment.scrollToPosition(position);
if (mBottomSheetBehavior.getState() != STATE_COLLAPSED) {
mIsCollapsingByUserSelecting = true;
mBottomSheetBehavior.setState(STATE_COLLAPSED);
}
});
getChildFragmentManager()
.beginTransaction()
.replace(R.id.category_fragment_container, mIndividualPickerFragment)
.addToBackStack(null)
.commit();
getChildFragmentManager().executePendingTransactions();
}
@Override
public void updateThumbnail(WallpaperInfo wallpaperInfo) {
new android.os.Handler().post(() -> {
// A config change may have destroyed the activity since the refresh started, so check
// for that.
if (getActivity() == null) {
return;
}
mHomePreviewWallpaperInfo = wallpaperInfo;
mLockPreviewWallpaperInfo = wallpaperInfo;
updateThumbnail(mHomePreviewWallpaperInfo, mHomePreview, true);
updateThumbnail(mLockPreviewWallpaperInfo, mLockscreenPreview, false);
mShowSelectedWallpaper = true;
});
}
@Override
public void restoreThumbnails() {
refreshCurrentWallpapers(/* MetadataHolder= */ null, /* forceRefresh= */ true);
mShowSelectedWallpaper = false;
}
@Override
public void onDestinationSet(@WallpaperPersister.Destination int destination) {
if (destination == WallpaperPersister.DEST_BOTH) {
return;
}
mPreviewPager.switchPreviewPage(destination);
}
@Override
public boolean onBackPressed() {
Fragment childFragment = getChildFragmentManager().findFragmentById(
R.id.category_fragment_container);
if (childFragment instanceof BottomActionBarFragment
&& ((BottomActionBarFragment) childFragment).onBackPressed()) {
return true;
}
return false;
}
/**
* Pops the child fragment from the stack if {@link CategoryFragment} is visible to the users.
*
* @return {@code true} if the child fragment is popped, {@code false} otherwise.
*/
public boolean popChildFragment() {
return isVisible() && getChildFragmentManager().popBackStackImmediate();
}
/**
* Inserts the given category into the categories list in priority order.
*/
void addCategory(Category category, boolean loading) {
mCategorySelectorFragment.addCategory(category, loading);
}
void removeCategory(Category category) {
mCategorySelectorFragment.removeCategory(category);
}
void updateCategory(Category category) {
mCategorySelectorFragment.updateCategory(category);
}
void clearCategories() {
mCategorySelectorFragment.clearCategories();
}
/**
* Notifies the CategoryFragment that no further categories are expected so it may hide
* the loading indicator.
*/
void doneFetchingCategories() {
mCategorySelectorFragment.doneFetchingCategories();
}
/**
* 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() {
Activity activity = getActivity();
CategoryFragmentHost host = getFragmentHost();
PackageManager packageManager = activity.getPackageManager();
String packageName = activity.getPackageName();
boolean hasReadWallpaperInternal = packageManager.checkPermission(
PERMISSION_READ_WALLPAPER_INTERNAL, packageName) == PackageManager.PERMISSION_GRANTED;
return hasReadWallpaperInternal || host.isReadExternalStoragePermissionGranted();
}
private void showCurrentWallpaper(View rootView, boolean show) {
rootView.findViewById(R.id.wallpaper_preview_pager)
.setVisibility(show ? View.VISIBLE : View.GONE);
rootView.findViewById(R.id.permission_needed)
.setVisibility(show ? View.GONE : View.VISIBLE);
}
private void setupCurrentWallpaperPreview(View rootView) {
if (canShowCurrentWallpaper()) {
showCurrentWallpaper(rootView, true);
} else {
showCurrentWallpaper(rootView, false);
Button mAllowAccessButton = rootView
.findViewById(R.id.permission_needed_allow_access_button);
mAllowAccessButton.setOnClickListener(view ->
getFragmentHost().requestExternalStoragePermission(
new PermissionChangedListener() {
@Override
public void onPermissionsGranted() {
showCurrentWallpaper(rootView, true);
mCategorySelectorFragment.notifyDataSetChanged();
}
@Override
public void onPermissionsDenied(boolean dontAskAgain) {
if (!dontAskAgain) {
return;
}
showPermissionNeededDialog();
}
})
);
// 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 explanationView = rootView.findViewById(R.id.permission_needed_explanation);
explanationView.setText(explanation);
}
}
private void showPermissionNeededDialog() {
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, /* onClickListener= */ null)
.setNegativeButton(
R.string.settings_button_label,
(dialogInterface, i) -> {
Intent appInfoIntent = new Intent();
appInfoIntent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package",
getActivity().getPackageName(), /* fragment= */ null);
appInfoIntent.setData(uri);
startActivityForResult(appInfoIntent, SETTINGS_APP_INFO_REQUEST_CODE);
})
.create();
dialog.show();
}
private CategoryFragmentHost getFragmentHost() {
return (CategoryFragmentHost) getActivity();
}
private Intent getWallpaperIntent(android.app.WallpaperInfo info) {
return new Intent(WallpaperService.SERVICE_INTERFACE)
.setClassName(info.getPackageName(), info.getServiceName());
}
/**
* 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() {
final Activity activity = getActivity();
// A config change may have destroyed the activity since the refresh
// started, so check for that.
if (activity == null) {
return;
}
mHomePreviewWallpaperInfo = homeWallpaper;
mLockPreviewWallpaperInfo =
lockWallpaper == null ? homeWallpaper : lockWallpaper;
updateThumbnail(mHomePreviewWallpaperInfo, mHomePreview, true);
updateThumbnail(mLockPreviewWallpaperInfo, mLockscreenPreview, false);
// 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 void setUpLiveWallpaperPreview(WallpaperInfo homeWallpaper) {
Activity activity = getActivity();
if (activity == null) {
return;
}
if (mWallpaperConnection != null) {
mWallpaperConnection.disconnect();
}
ImageView previewView = mSelectedPreviewPage == 0 ? mHomePreview : mLockscreenPreview;
previewView.getLocationOnScreen(mLivePreviewLocation);
mPreviewGlobalRect.set(0, 0, previewView.getMeasuredWidth(),
previewView.getMeasuredHeight());
mPreviewLocalRect.set(mPreviewGlobalRect);
mPreviewGlobalRect.offset(mLivePreviewLocation[0], mLivePreviewLocation[1]);
mWallpaperConnection = new WallpaperConnection(
getWallpaperIntent(homeWallpaper.getWallpaperComponent()), activity,
new WallpaperConnectionListener() {
@Override
public void onEngineShown() {
}
}, mPreviewGlobalRect);
LiveTileOverlay.INSTANCE.update(new RectF(mPreviewLocalRect),
((CardView) previewView.getParent()).getRadius());
mWallpaperConnection.setVisibility(true);
previewView.post(() -> {
if (!mWallpaperConnection.connect()) {
mWallpaperConnection = null;
LiveTileOverlay.INSTANCE.detach(previewView.getOverlay());
}
});
}
/**
* 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();
}
}
@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 void updateThumbnail(WallpaperInfo wallpaperInfo, ImageView thumbnailView,
boolean isHomeWallpaper) {
if (wallpaperInfo == null) {
return;
}
if (thumbnailView == null) {
return;
}
Activity activity = getActivity();
if (activity == null) {
return;
}
UserEventLogger eventLogger = InjectorProvider.getInjector().getUserEventLogger(activity);
boolean renderInImageWallpaperSurface =
!(wallpaperInfo instanceof LiveWallpaperInfo) && isHomeWallpaper;
wallpaperInfo.getThumbAsset(activity.getApplicationContext())
.loadPreviewImage(activity,
renderInImageWallpaperSurface ? mHomeImageWallpaper : thumbnailView,
getResources().getColor(R.color.secondary_color));
if (isHomeWallpaper) {
LiveTileOverlay.INSTANCE.detach(thumbnailView.getOverlay());
if (wallpaperInfo instanceof LiveWallpaperInfo) {
wallpaperInfo.getThumbAsset(activity.getApplicationContext()).loadPreviewImage(
activity, mHomeImageWallpaper,
getResources().getColor(R.color.secondary_color));
setUpLiveWallpaperPreview(wallpaperInfo);
} else {
if (mWallpaperConnection != null) {
mWallpaperConnection.disconnect();
mWallpaperConnection = null;
}
}
} else {
// lock screen wallpaper
if (wallpaperInfo instanceof LiveWallpaperInfo) {
LiveTileOverlay.INSTANCE.attach(thumbnailView.getOverlay());
} else {
LiveTileOverlay.INSTANCE.detach(thumbnailView.getOverlay());
}
}
thumbnailView.setOnClickListener(view -> {
getFragmentHost().showViewOnlyPreview(wallpaperInfo);
eventLogger.logCurrentWallpaperPreviewed();
});
}
private void updateWallpaperSurface() {
mWallpaperSurface.getHolder().addCallback(mWallpaperSurfaceCallback);
}
private void updateWorkspaceSurface() {
mWorkspaceSurface.setZOrderMediaOverlay(true);
mWorkspaceSurface.getHolder().addCallback(mWorkspaceSurfaceCallback);
}
private final SurfaceHolder.Callback mWallpaperSurfaceCallback = new SurfaceHolder.Callback() {
private Surface mLastSurface;
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (mLastSurface != holder.getSurface()) {
mLastSurface = holder.getSurface();
mHomeImageWallpaper = new ImageView(getContext());
mHomeImageWallpaper.setBackgroundColor(
ContextCompat.getColor(getContext(), R.color.primary_color));
mHomeImageWallpaper.measure(makeMeasureSpec(mHomePreview.getWidth(), EXACTLY),
makeMeasureSpec(mHomePreview.getHeight(), EXACTLY));
mHomeImageWallpaper.layout(0, 0, mHomePreview.getWidth(), mHomePreview.getHeight());
SurfaceControlViewHost host = new SurfaceControlViewHost(getContext(),
getContext().getDisplay(), mWallpaperSurface.getHostToken());
host.setView(mHomeImageWallpaper, mHomeImageWallpaper.getWidth(),
mHomeImageWallpaper.getHeight());
mWallpaperSurface.setChildSurfacePackage(host.getSurfacePackage());
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { }
@Override
public void surfaceDestroyed(SurfaceHolder holder) { }
};
private final SurfaceHolder.Callback mWorkspaceSurfaceCallback = new SurfaceHolder.Callback() {
private Surface mLastSurface;
private Message mCallback;
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (mPreviewUtils.supportsPreview() && mLastSurface != holder.getSurface()) {
mLastSurface = holder.getSurface();
Bundle result = mPreviewUtils.renderPreview(
SurfaceViewUtils.createSurfaceViewRequest(mWorkspaceSurface));
if (result != null) {
mWorkspaceSurface.setChildSurfacePackage(
SurfaceViewUtils.getSurfacePackage(result));
mCallback = SurfaceViewUtils.getCallback(result);
}
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { }
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (mCallback != null) {
try {
mCallback.replyTo.send(mCallback);
} catch (RemoteException e) {
e.printStackTrace();
} finally {
mCallback = null;
}
}
}
};
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);
}
}
/**
* 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 mWallpaperPresentationModeSubtitle;
private TextView mWallpaperTitle;
private TextView mWallpaperSubtitle;
private TextView mWallpaperSubtitle2;
private ImageButton mWallpaperExploreButtonNoText;
private ImageButton mSkipWallpaperButton;
public SingleWallpaperMetadataHolder(View metadataView) {
super(metadataView);
mWallpaperImage = metadataView.findViewById(R.id.wallpaper_image);
mWallpaperImage.getLayoutParams().width = getSingleWallpaperImageWidth();
mWallpaperPresentationModeSubtitle =
metadataView.findViewById(R.id.wallpaper_presentation_mode_subtitle);
mWallpaperTitle = metadataView.findViewById(R.id.wallpaper_title);
mWallpaperSubtitle = metadataView.findViewById(R.id.wallpaper_subtitle);
mWallpaperSubtitle2 = metadataView.findViewById(R.id.wallpaper_subtitle2);
mWallpaperExploreButtonNoText =
metadataView.findViewById(R.id.wallpaper_explore_button_notext);
mSkipWallpaperButton = metadataView.findViewById(R.id.skip_wallpaper_button);
}
/**
* 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) {
getFragmentHost().showViewOnlyPreview(mWallpaperInfo);
eventLogger.logCurrentWallpaperPreviewed();
}
});
}
private void bindWallpaperText(@PresentationMode int presentationMode) {
Context appContext = getActivity().getApplicationContext();
mWallpaperPresentationModeSubtitle.setText(
AttributionFormatter.getHumanReadableWallpaperPresentationMode(
appContext, presentationMode));
List<String> attributions = mWallpaperInfo.getAttributions(appContext);
if (!attributions.isEmpty()) {
mWallpaperTitle.setText(attributions.get(0));
}
if (attributions.size() > 1) {
mWallpaperSubtitle.setText(attributions.get(1));
} else {
mWallpaperSubtitle.setVisibility(View.INVISIBLE);
}
if (attributions.size() > 2) {
mWallpaperSubtitle2.setText(attributions.get(2));
} else {
mWallpaperSubtitle2.setVisibility(View.INVISIBLE);
}
}
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) {
mWallpaperExploreButtonNoText.setImageDrawable(getContext().getDrawable(
mWallpaperInfo.getActionIconRes(appContext)));
mWallpaperExploreButtonNoText.setContentDescription(
getString(mWallpaperInfo.getActionLabelRes(appContext)));
mWallpaperExploreButtonNoText.setColorFilter(
getResources().getColor(R.color.currently_set_explore_button_color,
getContext().getTheme()),
Mode.SRC_IN);
mWallpaperExploreButtonNoText.setVisibility(View.VISIBLE);
mWallpaperExploreButtonNoText.setOnClickListener((View view) -> {
eventLogger.logActionClicked(mWallpaperInfo.getCollectionId(appContext),
mWallpaperInfo.getActionLabelRes(appContext));
startActivity(exploreIntent);
});
}
if (showSkipWallpaperButton) {
mSkipWallpaperButton.setVisibility(View.VISIBLE);
mSkipWallpaperButton.setOnClickListener((View view) -> refreshDailyWallpaper());
}
}
}
/**
* 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 ImageView mHomeWallpaperImage;
private TextView mHomeWallpaperPresentationMode;
private TextView mHomeWallpaperTitle;
private TextView mHomeWallpaperSubtitle1;
private TextView mHomeWallpaperSubtitle2;
private ImageButton mHomeWallpaperExploreButton;
private ImageButton mSkipWallpaperButton;
private ViewGroup mHomeWallpaperPresentationSection;
private WallpaperInfo mLockWallpaperInfo;
private ImageView mLockWallpaperImage;
private TextView mLockWallpaperTitle;
private TextView mLockWallpaperSubtitle1;
private TextView mLockWallpaperSubtitle2;
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();
FrameLayout homeWallpaperSection = metadataView.findViewById(
R.id.home_wallpaper_section);
homeWallpaperSection.setMinimumWidth(bothWallpaperImageWidth);
mHomeWallpaperImage = metadataView.findViewById(R.id.home_wallpaper_image);
mHomeWallpaperPresentationMode =
metadataView.findViewById(R.id.home_wallpaper_presentation_mode);
mHomeWallpaperTitle = metadataView.findViewById(R.id.home_wallpaper_title);
mHomeWallpaperSubtitle1 = metadataView.findViewById(R.id.home_wallpaper_subtitle1);
mHomeWallpaperSubtitle2 = metadataView.findViewById(R.id.home_wallpaper_subtitle2);
mHomeWallpaperPresentationSection = metadataView.findViewById(
R.id.home_wallpaper_presentation_section);
mHomeWallpaperExploreButton =
metadataView.findViewById(R.id.home_wallpaper_explore_button);
mSkipWallpaperButton = metadataView.findViewById(R.id.skip_home_wallpaper);
FrameLayout lockWallpaperSection = metadataView.findViewById(
R.id.lock_wallpaper_section);
lockWallpaperSection.setMinimumWidth(bothWallpaperImageWidth);
mLockWallpaperImage = metadataView.findViewById(R.id.lock_wallpaper_image);
mLockWallpaperTitle = metadataView.findViewById(R.id.lock_wallpaper_title);
mLockWallpaperSubtitle1 = metadataView.findViewById(R.id.lock_wallpaper_subtitle1);
mLockWallpaperSubtitle2 = metadataView.findViewById(R.id.lock_wallpaper_subtitle2);
mLockWallpaperExploreButton =
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, getContext().getTheme()));
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);
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;
}
mHomeWallpaperExploreButton.setVisibility(View.VISIBLE);
mHomeWallpaperExploreButton.setImageDrawable(getContext().getDrawable(
homeWallpaper.getActionIconRes(appContext)));
mHomeWallpaperExploreButton.setContentDescription(getString(homeWallpaper
.getActionLabelRes(appContext)));
mHomeWallpaperExploreButton.setColorFilter(
getResources().getColor(R.color.currently_set_explore_button_color,
getContext().getTheme()),
Mode.SRC_IN);
mHomeWallpaperExploreButton.setOnClickListener(v -> {
eventLogger.logActionClicked(
mHomeWallpaperInfo.getCollectionId(appContext),
mHomeWallpaperInfo.getActionLabelRes(appContext));
startActivity(exploreIntent);
});
});
} else {
mHomeWallpaperExploreButton.setVisibility(View.GONE);
}
if (presentationMode == WallpaperPreferences.PRESENTATION_MODE_ROTATING) {
mHomeWallpaperPresentationSection.setVisibility(View.VISIBLE);
if (Flags.skipDailyWallpaperButtonEnabled) {
mSkipWallpaperButton.setVisibility(View.VISIBLE);
mSkipWallpaperButton.setColorFilter(
getResources().getColor(R.color.currently_set_explore_button_color,
getContext().getTheme()), Mode.SRC_IN);
mSkipWallpaperButton.setOnClickListener(view -> refreshDailyWallpaper());
} else {
mSkipWallpaperButton.setVisibility(View.GONE);
}
} else {
mHomeWallpaperPresentationSection.setVisibility(View.GONE);
}
mHomeWallpaperImage.setOnClickListener(v -> {
eventLogger.logCurrentWallpaperPreviewed();
getFragmentHost().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));
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);
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;
}
mLockWallpaperExploreButton.setImageDrawable(getContext().getDrawable(
lockWallpaper.getActionIconRes(appContext)));
mLockWallpaperExploreButton.setContentDescription(getString(
lockWallpaper.getActionLabelRes(appContext)));
mLockWallpaperExploreButton.setVisibility(View.VISIBLE);
mLockWallpaperExploreButton.setColorFilter(
getResources().getColor(
R.color.currently_set_explore_button_color),
Mode.SRC_IN);
mLockWallpaperExploreButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
eventLogger.logActionClicked(
mLockWallpaperInfo.getCollectionId(appContext),
mLockWallpaperInfo.getActionLabelRes(appContext));
startActivity(exploreIntent);
}
});
});
} else {
mLockWallpaperExploreButton.setVisibility(View.GONE);
}
mLockWallpaperImage.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
eventLogger.logCurrentWallpaperPreviewed();
getFragmentHost().showViewOnlyPreview(mLockWallpaperInfo);
}
});
}
}
private class PreviewPagerAdapter extends PagerAdapter {
private List<View> mPages;
PreviewPagerAdapter(List<View> pages) {
mPages = pages;
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position,
@NonNull Object object) {
container.removeView((View) object);
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
View view = mPages.get(position);
container.addView(view);
return view;
}
@Override
public int getCount() {
return mPages.size();
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object o) {
return view == o;
}
}
}