diff options
Diffstat (limited to 'src/com/android/wallpaper/picker')
6 files changed, 1311 insertions, 677 deletions
diff --git a/src/com/android/wallpaper/picker/ImagePreviewFragment.java b/src/com/android/wallpaper/picker/ImagePreviewFragment.java new file mode 100755 index 0000000..87e29c5 --- /dev/null +++ b/src/com/android/wallpaper/picker/ImagePreviewFragment.java @@ -0,0 +1,464 @@ +/* + * Copyright (C) 2019 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 com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.app.Activity; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Color; +import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.Rect; +import android.os.Bundle; +import android.view.ContextThemeWrapper; +import android.view.Display; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; + +import com.android.wallpaper.R; +import com.android.wallpaper.asset.Asset; +import com.android.wallpaper.module.WallpaperPersister.Destination; +import com.android.wallpaper.module.WallpaperPersister.SetWallpaperCallback; +import com.android.wallpaper.util.ScreenSizeCalculator; +import com.android.wallpaper.util.WallpaperCropUtils; +import com.android.wallpaper.widget.MaterialProgressDrawable; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.MemoryCategory; +import com.davemorrissey.labs.subscaleview.ImageSource; +import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView; +import com.google.android.material.bottomsheet.BottomSheetBehavior; + +import java.util.List; + +/** + * Fragment which displays the UI for previewing an individual static wallpaper and its attribution + * information. + */ +public class ImagePreviewFragment extends PreviewFragment { + + private static final float DEFAULT_WALLPAPER_MAX_ZOOM = 8f; + + private SubsamplingScaleImageView mFullResImageView; + private Asset mWallpaperAsset; + private TextView mAttributionTitle; + private TextView mAttributionSubtitle1; + private TextView mAttributionSubtitle2; + private Button mExploreButton; + private Button mSetWallpaperButton; + + private Point mDefaultCropSurfaceSize; + private Point mScreenSize; + private Point mRawWallpaperSize; // Native size of wallpaper image. + private ImageView mLoadingIndicator; + private MaterialProgressDrawable mProgressDrawable; + private ImageView mLowResImageView; + private View mSpacer; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mWallpaperAsset = mWallpaper.getAsset(requireContext().getApplicationContext()); + } + + @Override + protected int getLayoutResId() { + return R.layout.fragment_image_preview; + } + + @Override + protected int getBottomSheetResId() { + return R.id.bottom_sheet; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View view = super.onCreateView(inflater, container, savedInstanceState); + + Activity activity = requireActivity(); + // Set toolbar as the action bar. + + mFullResImageView = view.findViewById(R.id.full_res_image); + mLoadingIndicator = view.findViewById(R.id.loading_indicator); + + mAttributionTitle = view.findViewById(R.id.preview_attribution_pane_title); + mAttributionSubtitle1 = view.findViewById(R.id.preview_attribution_pane_subtitle1); + mAttributionSubtitle2 = view.findViewById(R.id.preview_attribution_pane_subtitle2); + mExploreButton = view.findViewById(R.id.preview_attribution_pane_explore_button); + mSetWallpaperButton = view.findViewById(R.id.preview_attribution_pane_set_wallpaper_button); + + mLowResImageView = view.findViewById(R.id.low_res_image); + mSpacer = view.findViewById(R.id.spacer); + + // Trim some memory from Glide to make room for the full-size image in this fragment. + Glide.get(activity).setMemoryCategory(MemoryCategory.LOW); + + mDefaultCropSurfaceSize = WallpaperCropUtils.getDefaultCropSurfaceSize( + getResources(), activity.getWindowManager().getDefaultDisplay()); + mScreenSize = ScreenSizeCalculator.getInstance().getScreenSize( + activity.getWindowManager().getDefaultDisplay()); + + // Load a low-res placeholder image if there's a thumbnail available from the asset that can + // be shown to the user more quickly than the full-sized image. + if (mWallpaperAsset.hasLowResDataSource()) { + mWallpaperAsset.loadLowResDrawable(activity, mLowResImageView, Color.BLACK, + new WallpaperPreviewBitmapTransformation(activity.getApplicationContext(), + isRtl())); + } + + mWallpaperAsset.decodeRawDimensions(getActivity(), dimensions -> { + // Don't continue loading the wallpaper if the Fragment is detached. + if (getActivity() == null) { + return; + } + + // Return early and show a dialog if dimensions are null (signaling a decoding error). + if (dimensions == null) { + showLoadWallpaperErrorDialog(); + return; + } + + mRawWallpaperSize = dimensions; + setUpExploreIntent(ImagePreviewFragment.this::initFullResView); + }); + + // Configure loading indicator with a MaterialProgressDrawable. + setUpLoadingIndicator(); + + return view; + } + + @Override + protected void setUpBottomSheetView(ViewGroup bottomSheet) { + // Nothing needed here. + } + + private void setUpLoadingIndicator() { + Context context = requireContext(); + mProgressDrawable = new MaterialProgressDrawable(context.getApplicationContext(), + mLoadingIndicator); + mProgressDrawable.setAlpha(255); + mProgressDrawable.setBackgroundColor(getResources().getColor(R.color.material_white_100, + context.getTheme())); + mProgressDrawable.setColorSchemeColors(getAttrColor( + new ContextThemeWrapper(context, getDeviceDefaultTheme()), + android.R.attr.colorAccent)); + mProgressDrawable.updateSizes(MaterialProgressDrawable.LARGE); + mLoadingIndicator.setImageDrawable(mProgressDrawable); + + // We don't want to show the spinner every time we load an image if it loads quickly; + // instead, only start showing the spinner if loading the image has taken longer than half + // of a second. + mLoadingIndicator.postDelayed(() -> { + if (mFullResImageView != null && !mFullResImageView.hasImage() + && !mTestingModeEnabled) { + mLoadingIndicator.setVisibility(View.VISIBLE); + mLoadingIndicator.setAlpha(1f); + if (mProgressDrawable != null) { + mProgressDrawable.start(); + } + } + }, 500); + } + + @Override + public void onClickOk() { + FragmentActivity activity = getActivity(); + if (activity != null) { + activity.finish(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (mProgressDrawable != null) { + mProgressDrawable.stop(); + } + mFullResImageView.recycle(); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + final BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(mBottomSheet); + outState.putInt(KEY_BOTTOM_SHEET_STATE, bottomSheetBehavior.getState()); + } + + @Override + protected void setBottomSheetContentAlpha(float alpha) { + mExploreButton.setAlpha(alpha); + mAttributionTitle.setAlpha(alpha); + mAttributionSubtitle1.setAlpha(alpha); + mAttributionSubtitle2.setAlpha(alpha); + } + + private void populateAttributionPane() { + final Context context = getContext(); + + final BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(mBottomSheet); + + List<String> attributions = mWallpaper.getAttributions(context); + if (attributions.size() > 0 && attributions.get(0) != null) { + mAttributionTitle.setText(attributions.get(0)); + } + + if (attributions.size() > 1 && attributions.get(1) != null) { + mAttributionSubtitle1.setVisibility(View.VISIBLE); + mAttributionSubtitle1.setText(attributions.get(1)); + } + + if (attributions.size() > 2 && attributions.get(2) != null) { + mAttributionSubtitle2.setVisibility(View.VISIBLE); + mAttributionSubtitle2.setText(attributions.get(2)); + } + + setUpSetWallpaperButton(mSetWallpaperButton); + + setUpExploreButton(mExploreButton); + + if (mExploreButton.getVisibility() == View.VISIBLE + && mSetWallpaperButton.getVisibility() == View.VISIBLE) { + mSpacer.setVisibility(View.VISIBLE); + } else { + mSpacer.setVisibility(View.GONE); + } + + mBottomSheet.setVisibility(View.VISIBLE); + + // Initialize the state of the BottomSheet based on the current state because if the initial + // and current state are the same, the state change listener won't fire and set the correct + // arrow asset and text alpha. + if (bottomSheetBehavior.getState() == STATE_EXPANDED) { + setPreviewChecked(false); + mAttributionTitle.setAlpha(1f); + mAttributionSubtitle1.setAlpha(1f); + mAttributionSubtitle2.setAlpha(1f); + } else { + setPreviewChecked(true); + mAttributionTitle.setAlpha(0f); + mAttributionSubtitle1.setAlpha(0f); + mAttributionSubtitle2.setAlpha(0f); + } + + // Let the state change listener take care of animating a state change to the initial state + // if there's a state change. + bottomSheetBehavior.setState(mBottomSheetInitialState); + } + + /** + * Initializes MosaicView by initializing tiling, setting a fallback page bitmap, and + * initializing a zoom-scroll observer and click listener. + */ + private void initFullResView() { + mFullResImageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_CROP); + + // Set a solid black "page bitmap" so MosaicView draws a black background while waiting + // for the image to load or a transparent one if a thumbnail already loaded. + Bitmap blackBitmap = Bitmap.createBitmap(1, 1, Config.ARGB_8888); + int color = (mLowResImageView.getDrawable() == null) ? Color.BLACK : Color.TRANSPARENT; + blackBitmap.setPixel(0, 0, color); + mFullResImageView.setImage(ImageSource.bitmap(blackBitmap)); + + // Then set a fallback "page bitmap" to cover the whole MosaicView, which is an actual + // (lower res) version of the image to be displayed. + Point targetPageBitmapSize = new Point(mRawWallpaperSize); + mWallpaperAsset.decodeBitmap(targetPageBitmapSize.x, targetPageBitmapSize.y, + pageBitmap -> { + // Check that the activity is still around since the decoding task started. + if (getActivity() == null) { + return; + } + + // Some of these may be null depending on if the Fragment is paused, stopped, + // or destroyed. + if (mLoadingIndicator != null) { + mLoadingIndicator.setVisibility(View.GONE); + } + // The page bitmap may be null if there was a decoding error, so show an + // error dialog. + if (pageBitmap == null) { + showLoadWallpaperErrorDialog(); + return; + } + if (mFullResImageView != null) { + // Set page bitmap. + mFullResImageView.setImage(ImageSource.bitmap(pageBitmap)); + + setDefaultWallpaperZoomAndScroll(); + crossFadeInMosaicView(); + } + if (mProgressDrawable != null) { + mProgressDrawable.stop(); + } + getActivity().invalidateOptionsMenu(); + + populateAttributionPane(); + }); + } + + /** + * Makes the MosaicView visible with an alpha fade-in animation while fading out the loading + * indicator. + */ + private void crossFadeInMosaicView() { + long shortAnimationDuration = getResources().getInteger( + android.R.integer.config_shortAnimTime); + + mFullResImageView.setAlpha(0f); + mFullResImageView.animate() + .alpha(1f) + .setDuration(shortAnimationDuration) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + // Clear the thumbnail bitmap reference to save memory since it's no longer + // visible. + if (mLowResImageView != null) { + mLowResImageView.setImageBitmap(null); + } + } + }); + + mLoadingIndicator.animate() + .alpha(0f) + .setDuration(shortAnimationDuration) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (mLoadingIndicator != null) { + mLoadingIndicator.setVisibility(View.GONE); + } + } + }); + } + + /** + * Sets the default wallpaper zoom and scroll position based on a "crop surface" + * (with extra width to account for parallax) superimposed on the screen. Shows as much of the + * wallpaper as possible on the crop surface and align screen to crop surface such that the + * default preview matches what would be seen by the user in the left-most home screen. + * + * <p>This method is called once in the Fragment lifecycle after the wallpaper asset has loaded + * and rendered to the layout. + */ + private void setDefaultWallpaperZoomAndScroll() { + // Determine minimum zoom to fit maximum visible area of wallpaper on crop surface. + float defaultWallpaperZoom = + WallpaperCropUtils.calculateMinZoom(mRawWallpaperSize, mDefaultCropSurfaceSize); + float minWallpaperZoom = + WallpaperCropUtils.calculateMinZoom(mRawWallpaperSize, mScreenSize); + + Point screenToCropSurfacePosition = WallpaperCropUtils.calculateCenterPosition( + mDefaultCropSurfaceSize, mScreenSize, true /* alignStart */, isRtl()); + Point zoomedWallpaperSize = new Point( + Math.round(mRawWallpaperSize.x * defaultWallpaperZoom), + Math.round(mRawWallpaperSize.y * defaultWallpaperZoom)); + Point cropSurfaceToWallpaperPosition = WallpaperCropUtils.calculateCenterPosition( + zoomedWallpaperSize, mDefaultCropSurfaceSize, false /* alignStart */, isRtl()); + + // Set min wallpaper zoom and max zoom on MosaicView widget. + mFullResImageView.setMaxScale(Math.max(DEFAULT_WALLPAPER_MAX_ZOOM, defaultWallpaperZoom)); + mFullResImageView.setMinScale(minWallpaperZoom); + + // Set center to composite positioning between scaled wallpaper and screen. + PointF centerPosition = new PointF( + mRawWallpaperSize.x / 2f, + mRawWallpaperSize.y / 2f); + centerPosition.offset(-(screenToCropSurfacePosition.x + cropSurfaceToWallpaperPosition.x), + -(screenToCropSurfacePosition.y + cropSurfaceToWallpaperPosition.y)); + + mFullResImageView.setScaleAndCenter(minWallpaperZoom, centerPosition); + } + + private Rect calculateCropRect() { + // Calculate Rect of wallpaper in physical pixel terms (i.e., scaled to current zoom). + float wallpaperZoom = mFullResImageView.getScale(); + int scaledWallpaperWidth = (int) (mRawWallpaperSize.x * wallpaperZoom); + int scaledWallpaperHeight = (int) (mRawWallpaperSize.y * wallpaperZoom); + Rect rect = new Rect(); + mFullResImageView.visibleFileRect(rect); + int scrollX = (int) (rect.left * wallpaperZoom); + int scrollY = (int) (rect.top * wallpaperZoom); + + rect.set(0, 0, scaledWallpaperWidth, scaledWallpaperHeight); + + Display defaultDisplay = requireActivity().getWindowManager().getDefaultDisplay(); + Point screenSize = ScreenSizeCalculator.getInstance().getScreenSize(defaultDisplay); + // Crop rect should start off as the visible screen and then include extra width and height + // if available within wallpaper at the current zoom. + Rect cropRect = new Rect(scrollX, scrollY, scrollX + screenSize.x, scrollY + screenSize.y); + + Point defaultCropSurfaceSize = WallpaperCropUtils.getDefaultCropSurfaceSize( + getResources(), defaultDisplay); + int extraWidth = defaultCropSurfaceSize.x - screenSize.x; + int extraHeightTopAndBottom = (int) ((defaultCropSurfaceSize.y - screenSize.y) / 2f); + + // Try to increase size of screenRect to include extra width depending on the layout + // direction. + if (isRtl()) { + cropRect.left = Math.max(cropRect.left - extraWidth, rect.left); + } else { + cropRect.right = Math.min(cropRect.right + extraWidth, rect.right); + } + + // Try to increase the size of the cropRect to to include extra height. + int availableExtraHeightTop = cropRect.top - Math.max( + rect.top, + cropRect.top - extraHeightTopAndBottom); + int availableExtraHeightBottom = Math.min( + rect.bottom, + cropRect.bottom + extraHeightTopAndBottom) - cropRect.bottom; + + int availableExtraHeightTopAndBottom = + Math.min(availableExtraHeightTop, availableExtraHeightBottom); + cropRect.top -= availableExtraHeightTopAndBottom; + cropRect.bottom += availableExtraHeightTopAndBottom; + + return cropRect; + } + + @Override + protected void setCurrentWallpaper(@Destination int destination) { + mWallpaperSetter.setCurrentWallpaper(getActivity(), mWallpaper, mWallpaperAsset, + destination, mFullResImageView.getScale(), calculateCropRect(), + new SetWallpaperCallback() { + @Override + public void onSuccess() { + finishActivityWithResultOk(); + } + + @Override + public void onError(@Nullable Throwable throwable) { + showSetWallpaperErrorDialog(destination); + } + }); + } +} diff --git a/src/com/android/wallpaper/picker/LivePreviewFragment.java b/src/com/android/wallpaper/picker/LivePreviewFragment.java new file mode 100644 index 0000000..fee60a8 --- /dev/null +++ b/src/com/android/wallpaper/picker/LivePreviewFragment.java @@ -0,0 +1,655 @@ +/* + * Copyright (C) 2019 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 com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.WallpaperColors; +import android.app.WallpaperManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; +import android.graphics.Rect; +import android.net.Uri; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.service.wallpaper.IWallpaperConnection; +import android.service.wallpaper.IWallpaperEngine; +import android.service.wallpaper.IWallpaperService; +import android.service.wallpaper.WallpaperService; +import android.service.wallpaper.WallpaperSettingsActivity; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager.LayoutParams; +import android.view.animation.AnimationUtils; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.LiveData; +import androidx.slice.Slice; +import androidx.slice.widget.SliceLiveData; +import androidx.slice.widget.SliceView; +import androidx.viewpager.widget.PagerAdapter; +import androidx.viewpager.widget.ViewPager; + +import com.android.wallpaper.R; +import com.android.wallpaper.compat.BuildCompat; +import com.android.wallpaper.model.LiveWallpaperInfo; +import com.android.wallpaper.module.WallpaperPersister.SetWallpaperCallback; +import com.android.wallpaper.widget.MaterialProgressDrawable; + +import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.google.android.material.tabs.TabLayout; + +import java.util.ArrayList; +import java.util.List; + +/** + * Fragment which displays the UI for previewing an individual live wallpaper, its attribution + * information and settings slices if available. + */ +public class LivePreviewFragment extends PreviewFragment { + + private static final String TAG = "LivePreviewFragment"; + + private static final String KEY_ACTION_DELETE_LIVE_WALLPAPER = "action_delete_live_wallpaper"; + private static final String EXTRA_LIVE_WALLPAPER_INFO = "android.live_wallpaper.info"; + + /** + * Instance of {@link WallpaperConnection} used to bind to the live wallpaper service to show + * it in this preview fragment. + * @see IWallpaperConnection + */ + private WallpaperConnection mWallpaperConnection; + + private Intent mWallpaperIntent; + private Intent mDeleteIntent; + private Intent mSettingsIntent; + + private List<Pair<String, View>> mPages; + private ImageView mLoadingIndicator; + private TextView mAttributionTitle; + private TextView mAttributionSubtitle1; + private TextView mAttributionSubtitle2; + private Button mExploreButton; + private Button mSetWallpaperButton; + private ViewPager mViewPager; + private TabLayout mTabLayout; + private SliceView mSettingsSliceView; + private LiveData<Slice> mSettingsLiveData; + private View mSpacer; + private View mLoadingScrim; + private MaterialProgressDrawable mProgressDrawable; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + android.app.WallpaperInfo info = mWallpaper.getWallpaperComponent(); + mWallpaperIntent = new Intent(WallpaperService.SERVICE_INTERFACE) + .setClassName(info.getPackageName(), info.getServiceName()); + setUpExploreIntent(null); + + android.app.WallpaperInfo currentWallpaper = + WallpaperManager.getInstance(requireContext()).getWallpaperInfo(); + String deleteAction = getDeleteAction(info.getServiceInfo(), + (currentWallpaper == null) ? null : currentWallpaper.getServiceInfo()); + + if (!TextUtils.isEmpty(deleteAction)) { + mDeleteIntent = new Intent(deleteAction); + mDeleteIntent.setPackage(info.getPackageName()); + mDeleteIntent.putExtra(EXTRA_LIVE_WALLPAPER_INFO, info); + } + + String settingsActivity = info.getSettingsActivity(); + if (settingsActivity != null) { + mSettingsIntent = new Intent(); + mSettingsIntent.setComponent(new ComponentName(info.getPackageName(), + settingsActivity)); + mSettingsIntent.putExtra(WallpaperSettingsActivity.EXTRA_PREVIEW_MODE, true); + PackageManager pm = requireContext().getPackageManager(); + ActivityInfo activityInfo = mSettingsIntent.resolveActivityInfo(pm, 0); + if (activityInfo == null) { + Log.i(TAG, "Couldn't find wallpaper settings activity: " + settingsActivity); + mSettingsIntent = null; + } + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + mPages = new ArrayList<>(); + View view = super.onCreateView(inflater, container, savedInstanceState); + if (view == null) { + return null; + } + + Activity activity = requireActivity(); + + mLoadingScrim = view.findViewById(R.id.loading); + mLoadingIndicator = view.findViewById(R.id.loading_indicator); + setUpLoadingIndicator(); + + mWallpaperConnection = new WallpaperConnection(mWallpaperIntent, activity); + container.post(() -> { + if (!mWallpaperConnection.connect()) { + mWallpaperConnection = null; + } + }); + + return view; + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (mSettingsLiveData != null && mSettingsLiveData.hasObservers()) { + mSettingsLiveData.removeObserver(mSettingsSliceView); + mSettingsLiveData = null; + } + if (mWallpaperConnection != null) { + mWallpaperConnection.disconnect(); + } + mWallpaperConnection = null; + super.onDestroy(); + } + + @Override + protected void setUpBottomSheetView(ViewGroup bottomSheet) { + + initInfoPage(); + initSettingsPage(); + + mViewPager = bottomSheet.findViewById(R.id.viewpager); + mTabLayout = bottomSheet.findViewById(R.id.tablayout); + + // Create PagerAdapter + final PagerAdapter pagerAdapter = new PagerAdapter() { + @Override + public Object instantiateItem(ViewGroup container, int position) { + final View page = mPages.get(position).second; + container.addView(page); + return page; + } + + @Override + public void destroyItem(@NonNull ViewGroup container, int position, + @NonNull Object object) { + if (object instanceof View) { + container.removeView((View) object); + } + } + + @Override + public int getCount() { + return mPages.size(); + } + + @Override + public CharSequence getPageTitle(int position) { + try { + return mPages.get(position).first; + } catch (IndexOutOfBoundsException e) { + return null; + } + } + + @Override + public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { + return (view == object); + } + }; + + // Add OnPageChangeListener to re-measure ViewPager's height + mViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { + @Override + public void onPageSelected(int position) { + mViewPager.requestLayout(); + } + }); + + // Set PagerAdapter + mViewPager.setAdapter(pagerAdapter); + + // Make TabLayout visible if there are more than one page + if (mPages.size() > 1) { + mTabLayout.setVisibility(View.VISIBLE); + mTabLayout.setupWithViewPager(mViewPager); + } + mViewPager.setCurrentItem(0); + } + + private void setUpLoadingIndicator() { + Context context = requireContext(); + mProgressDrawable = new MaterialProgressDrawable(context.getApplicationContext(), + mLoadingIndicator); + mProgressDrawable.setAlpha(255); + mProgressDrawable.setBackgroundColor(getResources().getColor(R.color.material_white_100, + context.getTheme())); + mProgressDrawable.setColorSchemeColors(getAttrColor( + new ContextThemeWrapper(context, getDeviceDefaultTheme()), + android.R.attr.colorAccent)); + mProgressDrawable.updateSizes(MaterialProgressDrawable.LARGE); + mLoadingIndicator.setImageDrawable(mProgressDrawable); + + // We don't want to show the spinner every time we load a wallpaper if it loads quickly; + // instead, only start showing the spinner after 100 ms + mLoadingIndicator.postDelayed(() -> { + if ((mWallpaperConnection == null || !mWallpaperConnection.isEngineReady()) + && !mTestingModeEnabled) { + mLoadingIndicator.setVisibility(View.VISIBLE); + mLoadingIndicator.setAlpha(1f); + if (mProgressDrawable != null) { + mProgressDrawable.start(); + } + } + }, 100); + } + + private void initInfoPage() { + View pageInfo = getLayoutInflater().inflate(R.layout.preview_page_info, null /* root */); + + mAttributionTitle = pageInfo.findViewById(R.id.preview_attribution_pane_title); + mAttributionSubtitle1 = pageInfo.findViewById(R.id.preview_attribution_pane_subtitle1); + mAttributionSubtitle2 = pageInfo.findViewById(R.id.preview_attribution_pane_subtitle2); + mSpacer = pageInfo.findViewById(R.id.spacer); + + mExploreButton = pageInfo.findViewById(R.id.preview_attribution_pane_explore_button); + mSetWallpaperButton = pageInfo.findViewById( + R.id.preview_attribution_pane_set_wallpaper_button); + + mPages.add(Pair.create(getString(R.string.tab_info), pageInfo)); + } + + private void initSettingsPage() { + final Uri uriSettingsSlice = getSettingsSliceUri(mWallpaper.getWallpaperComponent()); + if (uriSettingsSlice == null) { + return; + } + + final View pageSettings = getLayoutInflater().inflate(R.layout.preview_page_settings, + null /* root */); + + mSettingsSliceView = pageSettings.findViewById(R.id.settings_slice); + mSettingsSliceView.setMode(SliceView.MODE_LARGE); + mSettingsSliceView.setScrollable(false); + + // Set LiveData for SliceView + mSettingsLiveData = SliceLiveData.fromUri(requireContext() /* context */, uriSettingsSlice); + mSettingsLiveData.observeForever(mSettingsSliceView); + + mPages.add(Pair.create(getResources().getString(R.string.tab_customize), pageSettings)); + } + + private void populateAttributionPane() { + final Context context = getContext(); + + final BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(mBottomSheet); + + List<String> attributions = mWallpaper.getAttributions(context); + if (attributions.size() > 0 && attributions.get(0) != null) { + mAttributionTitle.setText(attributions.get(0)); + } + + if (mWallpaper.getWallpaperComponent().getShowMetadataInPreview()) { + + if (attributions.size() > 1 && attributions.get(1) != null) { + mAttributionSubtitle1.setVisibility(View.VISIBLE); + mAttributionSubtitle1.setText(attributions.get(1)); + } + + if (attributions.size() > 2 && attributions.get(2) != null) { + mAttributionSubtitle2.setVisibility(View.VISIBLE); + mAttributionSubtitle2.setText(attributions.get(2)); + } + + } else { + mExploreIntent = null; + } + + setUpSetWallpaperButton(mSetWallpaperButton); + + setUpExploreButton(mExploreButton); + + if (mExploreButton.getVisibility() == View.VISIBLE + && mSetWallpaperButton.getVisibility() == View.VISIBLE) { + mSpacer.setVisibility(View.VISIBLE); + } else { + mSpacer.setVisibility(View.GONE); + } + + mBottomSheet.setVisibility(View.VISIBLE); + + // Initialize the state of the BottomSheet based on the current state because if the initial + // and current state are the same, the state change listener won't fire and set the correct + // arrow asset and text alpha. + if (mBottomSheetInitialState == STATE_EXPANDED) { + setPreviewChecked(false); + mAttributionTitle.setAlpha(1f); + mAttributionSubtitle1.setAlpha(1f); + mAttributionSubtitle2.setAlpha(1f); + } else { + setPreviewChecked(true); + mAttributionTitle.setAlpha(0f); + mAttributionSubtitle1.setAlpha(0f); + mAttributionSubtitle2.setAlpha(0f); + } + + bottomSheetBehavior.setState(mBottomSheetInitialState); + } + + @SuppressLint("NewApi") //Already checking with isAtLeastQ + private Uri getSettingsSliceUri(android.app.WallpaperInfo info) { + if (BuildCompat.isAtLeastQ()) { + return info.getSettingsSliceUri(); + } + return null; + } + + @Override + protected int getLayoutResId() { + return R.layout.fragment_live_preview; + } + + @Override + protected int getBottomSheetResId() { + return R.id.bottom_sheet; + } + + @Override + protected void setCurrentWallpaper(int destination) { + mWallpaperSetter.setCurrentWallpaper(getActivity(), mWallpaper, null, + destination, 0, null, new SetWallpaperCallback() { + @Override + public void onSuccess() { + finishActivityWithResultOk(); + } + + @Override + public void onError(@Nullable Throwable throwable) { + showSetWallpaperErrorDialog(destination); + } + }); + } + + @Override + protected void setBottomSheetContentAlpha(float alpha) { + mExploreButton.setAlpha(alpha); + mAttributionTitle.setAlpha(alpha); + mAttributionSubtitle1.setAlpha(alpha); + mAttributionSubtitle2.setAlpha(alpha); + } + + @Override + protected void setUpExploreButton(Button exploreButton) { + super.setUpExploreButton(exploreButton); + if (exploreButton.getVisibility() != View.VISIBLE) { + return; + } + Context context = requireContext(); + CharSequence exploreLabel = ((LiveWallpaperInfo) mWallpaper).getActionDescription(context); + if (!TextUtils.isEmpty(exploreLabel)) { + exploreButton.setText(exploreLabel); + } + } + + @Nullable + private String getDeleteAction(ServiceInfo serviceInfo, + @Nullable ServiceInfo currentService) { + if (!isPackagePreInstalled(serviceInfo.applicationInfo)) { + Log.d(TAG, "This wallpaper is not pre-installed: " + serviceInfo.name); + return null; + } + + // A currently set Live wallpaper should not be deleted. + if (currentService != null && TextUtils.equals(serviceInfo.name, currentService.name)) { + return null; + } + + final Bundle metaData = serviceInfo.metaData; + if (metaData != null) { + return metaData.getString(KEY_ACTION_DELETE_LIVE_WALLPAPER); + } + return null; + } + + @Override + public void onResume() { + super.onResume(); + if (mWallpaperConnection != null) { + mWallpaperConnection.setVisibility(true); + } + } + + @Override + public void onPause() { + super.onPause(); + if (mWallpaperConnection != null) { + mWallpaperConnection.setVisibility(false); + } + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + menu.findItem(R.id.configure).setVisible(mSettingsIntent != null); + menu.findItem(R.id.delete_wallpaper).setVisible(mDeleteIntent != null); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == R.id.configure) { + if (getActivity() != null) { + startActivity(mSettingsIntent); + return true; + } + } else if (id == R.id.delete_wallpaper) { + showDeleteConfirmDialog(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void showDeleteConfirmDialog() { + final AlertDialog alertDialog = new AlertDialog.Builder( + new ContextThemeWrapper(getContext(), getDeviceDefaultTheme())) + .setMessage(R.string.delete_wallpaper_confirmation) + .setPositiveButton(R.string.delete_live_wallpaper, + (dialog, which) -> deleteLiveWallpaper()) + .setNegativeButton(android.R.string.cancel, null /* listener */) + .create(); + alertDialog.show(); + } + + private void deleteLiveWallpaper() { + if (mDeleteIntent != null) { + requireContext().startService(mDeleteIntent); + finishActivityWithResultOk(); + } + } + + private boolean isPackagePreInstalled(ApplicationInfo info) { + if (info != null && (info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + return true; + } + return false; + } + + private class WallpaperConnection extends IWallpaperConnection.Stub + implements ServiceConnection { + private final Activity mActivity; + private final Intent mIntent; + private IWallpaperService mService; + private IWallpaperEngine mEngine; + private boolean mConnected; + private boolean mIsVisible; + private boolean mIsEngineVisible; + private boolean mEngineReady; + + WallpaperConnection(Intent intent, Activity activity) { + mActivity = activity; + mIntent = intent; + } + + public boolean connect() { + synchronized (this) { + if (!mActivity.bindService(mIntent, this, Context.BIND_AUTO_CREATE)) { + return false; + } + + mConnected = true; + return true; + } + } + + public void disconnect() { + synchronized (this) { + mConnected = false; + if (mEngine != null) { + try { + mEngine.destroy(); + } catch (RemoteException e) { + // Ignore + } + mEngine = null; + } + try { + mActivity.unbindService(this); + } catch (IllegalArgumentException e) { + Log.w(TAG, "Can't unbind wallpaper service. " + + "It might have crashed, just ignoring.", e); + } + mService = null; + } + } + + public void onServiceConnected(ComponentName name, IBinder service) { + if (mWallpaperConnection == this) { + mService = IWallpaperService.Stub.asInterface(service); + try { + View root = mActivity.getWindow().getDecorView(); + int displayId = root.getDisplay().getDisplayId(); + mService.attach(this, root.getWindowToken(), + LayoutParams.TYPE_APPLICATION_MEDIA, + true, root.getWidth(), root.getHeight(), + new Rect(0, 0, 0, 0), displayId); + } catch (RemoteException e) { + Log.w(TAG, "Failed attaching wallpaper; clearing", e); + } + } + } + + public void onServiceDisconnected(ComponentName name) { + mService = null; + mEngine = null; + if (mWallpaperConnection == this) { + Log.w(TAG, "Wallpaper service gone: " + name); + } + } + + public void attachEngine(IWallpaperEngine engine, int displayId) { + synchronized (this) { + if (mConnected) { + mEngine = engine; + if (mIsVisible) { + setEngineVisibility(true); + } + } else { + try { + engine.destroy(); + } catch (RemoteException e) { + // Ignore + } + } + } + } + + public ParcelFileDescriptor setWallpaper(String name) { + return null; + } + + @Override + public void onWallpaperColorsChanged(WallpaperColors colors, int displayId) + throws RemoteException { + + } + + @Override + public void engineShown(IWallpaperEngine engine) { + mLoadingScrim.post(() -> { + mLoadingScrim.animate() + .alpha(0f) + .setDuration(220) + .setStartDelay(300) + .setInterpolator(AnimationUtils.loadInterpolator(mActivity, + android.R.interpolator.fast_out_linear_in)) + .withEndAction(() -> { + if (mLoadingIndicator != null) { + mLoadingIndicator.setVisibility(View.GONE); + } + if (mProgressDrawable != null) { + mProgressDrawable.stop(); + } + mLoadingScrim.setVisibility(View.INVISIBLE); + populateAttributionPane(); + }); + }); + mEngineReady = true; + } + + public boolean isEngineReady() { + return mEngineReady; + } + + public void setVisibility(boolean visible) { + mIsVisible = visible; + setEngineVisibility(visible); + } + + private void setEngineVisibility(boolean visible) { + if (mEngine != null && visible != mIsEngineVisible) { + try { + mEngine.setVisibility(visible); + mIsEngineVisible = visible; + } catch (RemoteException e) { + Log.w(TAG, "Failure setting wallpaper visibility ", e); + } + } + } + } +} diff --git a/src/com/android/wallpaper/picker/PreviewFragment.java b/src/com/android/wallpaper/picker/PreviewFragment.java index c698c8c..f1a1625 100755 --- a/src/com/android/wallpaper/picker/PreviewFragment.java +++ b/src/com/android/wallpaper/picker/PreviewFragment.java @@ -18,46 +18,33 @@ package com.android.wallpaper.picker; import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED; import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; -import android.content.res.Configuration; import android.content.res.Resources.NotFoundException; import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.Color; -import android.graphics.Point; -import android.graphics.PointF; import android.graphics.PorterDuff.Mode; -import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.net.Uri; import android.os.Bundle; import android.util.Log; -import android.view.ContextThemeWrapper; -import android.view.Display; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; -import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.widget.Button; import android.widget.CheckBox; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.CallSuper; +import androidx.annotation.IdRes; import androidx.annotation.IntDef; +import androidx.annotation.LayoutRes; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; @@ -66,9 +53,6 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import com.android.wallpaper.R; -import com.android.wallpaper.asset.Asset; -import com.android.wallpaper.asset.Asset.BitmapReceiver; -import com.android.wallpaper.asset.Asset.DimensionsReceiver; import com.android.wallpaper.compat.BuildCompat; import com.android.wallpaper.model.LiveWallpaperInfo; import com.android.wallpaper.model.WallpaperInfo; @@ -76,19 +60,10 @@ import com.android.wallpaper.module.ExploreIntentChecker; import com.android.wallpaper.module.Injector; import com.android.wallpaper.module.InjectorProvider; import com.android.wallpaper.module.UserEventLogger; -import com.android.wallpaper.module.WallpaperPersister; import com.android.wallpaper.module.WallpaperPersister.Destination; -import com.android.wallpaper.module.WallpaperPersister.SetWallpaperCallback; import com.android.wallpaper.module.WallpaperPreferences; import com.android.wallpaper.module.WallpaperSetter; -import com.android.wallpaper.util.ScreenSizeCalculator; -import com.android.wallpaper.util.WallpaperCropUtils; -import com.android.wallpaper.widget.MaterialProgressDrawable; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.MemoryCategory; -import com.davemorrissey.labs.subscaleview.ImageSource; -import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView; + import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.bottomsheet.BottomSheetBehavior.State; @@ -96,23 +71,22 @@ import java.util.Date; import java.util.List; /** - * Fragment which displays the UI for previewing an individual wallpaper and its attribution - * information. + * Base Fragment to display the UI for previewing an individual wallpaper */ -public class PreviewFragment extends Fragment implements +public abstract class PreviewFragment extends Fragment implements SetWallpaperDialogFragment.Listener, SetWallpaperErrorDialogFragment.Listener, LoadWallpaperErrorDialogFragment.Listener { /** * User can view wallpaper and attributions in full screen, but "Set wallpaper" button is hidden. */ - public static final int MODE_VIEW_ONLY = 0; + static final int MODE_VIEW_ONLY = 0; /** * User can view wallpaper and attributions in full screen and click "Set wallpaper" to set the * wallpaper with pan and crop position to the device. */ - public static final int MODE_CROP_AND_SET_WALLPAPER = 1; + static final int MODE_CROP_AND_SET_WALLPAPER = 1; /** * Possible preview modes for the fragment. @@ -126,52 +100,56 @@ public class PreviewFragment extends Fragment implements public static final String ARG_WALLPAPER = "wallpaper"; public static final String ARG_PREVIEW_MODE = "preview_mode"; public static final String ARG_TESTING_MODE_ENABLED = "testing_mode_enabled"; + + /** + * Creates and returns new instance of {@link ImagePreviewFragment} with the provided wallpaper + * set as an argument. + */ + public static PreviewFragment newInstance( + WallpaperInfo wallpaperInfo, @PreviewMode int mode, boolean testingModeEnabled) { + + boolean isLive = wallpaperInfo instanceof LiveWallpaperInfo; + + Bundle args = new Bundle(); + args.putParcelable(ARG_WALLPAPER, wallpaperInfo); + args.putInt(ARG_PREVIEW_MODE, mode); + args.putBoolean(ARG_TESTING_MODE_ENABLED, testingModeEnabled); + + PreviewFragment fragment = isLive ? new LivePreviewFragment() : new ImagePreviewFragment(); + fragment.setArguments(args); + return fragment; + } + private static final String TAG_LOAD_WALLPAPER_ERROR_DIALOG_FRAGMENT = "load_wallpaper_error_dialog"; private static final String TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT = "set_wallpaper_error_dialog"; private static final int UNUSED_REQUEST_CODE = 1; - private static final float DEFAULT_WALLPAPER_MAX_ZOOM = 8f; private static final String TAG = "PreviewFragment"; - private static final float PAGE_BITMAP_MAX_HEAP_RATIO = 0.25f; - private static final String KEY_BOTTOM_SHEET_STATE = "key_bottom_sheet_state"; + static final String KEY_BOTTOM_SHEET_STATE = "key_bottom_sheet_state"; @PreviewMode - private int mPreviewMode; + protected int mPreviewMode; /** * When true, enables 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. */ - private boolean mTestingModeEnabled; + protected boolean mTestingModeEnabled; - protected SubsamplingScaleImageView mFullResImageView; protected WallpaperInfo mWallpaper; - private Asset mWallpaperAsset; - private WallpaperSetter mWallpaperSetter; - private UserEventLogger mUserEventLogger; - private LinearLayout mBottomSheet; - private TextView mAttributionTitle; - private TextView mAttributionSubtitle1; - private TextView mAttributionSubtitle2; - private Button mAttributionExploreButton; - private int mCurrentScreenOrientation; - private Point mDefaultCropSurfaceSize; - private Point mScreenSize; - private Point mRawWallpaperSize; // Native size of wallpaper image. - private ImageView mLoadingIndicator; - private MaterialProgressDrawable mProgressDrawable; - private ImageView mLowResImageView; - private Button mSetWallpaperButton; - private View mSpacer; - private CheckBox mPreview; + protected WallpaperSetter mWallpaperSetter; + protected UserEventLogger mUserEventLogger; + protected ViewGroup mBottomSheet; + + protected CheckBox mPreview; @SuppressWarnings("RestrictTo") @State - private int mBottomSheetInitialState; + protected int mBottomSheetInitialState; - private Intent mExploreIntent; + protected Intent mExploreIntent; /** * Staged error dialog fragments that were unable to be shown when the hosting activity didn't @@ -180,23 +158,7 @@ public class PreviewFragment extends Fragment implements private SetWallpaperErrorDialogFragment mStagedSetWallpaperErrorDialogFragment; private LoadWallpaperErrorDialogFragment mStagedLoadWallpaperErrorDialogFragment; - /** - * Creates and returns new instance of {@link PreviewFragment} with the provided wallpaper set as - * an argument. - */ - public static PreviewFragment newInstance( - WallpaperInfo wallpaperInfo, @PreviewMode int mode, boolean testingModeEnabled) { - Bundle args = new Bundle(); - args.putParcelable(ARG_WALLPAPER, wallpaperInfo); - args.putInt(ARG_PREVIEW_MODE, mode); - args.putBoolean(ARG_TESTING_MODE_ENABLED, testingModeEnabled); - - PreviewFragment fragment = new PreviewFragment(); - fragment.setArguments(args); - return fragment; - } - - private static int getAttrColor(Context context, int attr) { + protected static int getAttrColor(Context context, int attr) { TypedArray ta = context.obtainStyledAttributes(new int[]{attr}); int colorAccent = ta.getColor(0, 0); ta.recycle(); @@ -208,12 +170,12 @@ public class PreviewFragment extends Fragment implements super.onCreate(savedInstanceState); Activity activity = getActivity(); - Context appContext = activity.getApplicationContext(); + Context appContext = getContext().getApplicationContext(); Injector injector = InjectorProvider.getInjector(); mUserEventLogger = injector.getUserEventLogger(appContext); mWallpaper = getArguments().getParcelable(ARG_WALLPAPER); - mWallpaperAsset = mWallpaper.getAsset(appContext); + //noinspection ResourceType mPreviewMode = getArguments().getInt(ARG_PREVIEW_MODE); mTestingModeEnabled = getArguments().getBoolean(ARG_TESTING_MODE_ENABLED); @@ -236,14 +198,18 @@ public class PreviewFragment extends Fragment implements } } + @LayoutRes + protected abstract int getLayoutResId(); + @Override + @CallSuper public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_preview, container, false); + View view = inflater.inflate(getLayoutResId(), container, false); // Set toolbar as the action bar. Toolbar toolbar = view.findViewById(R.id.toolbar); - AppCompatActivity activity = (AppCompatActivity) getActivity(); + AppCompatActivity activity = (AppCompatActivity) requireActivity(); activity.setSupportActionBar(toolbar); activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true); activity.getSupportActionBar().setDisplayShowTitleEnabled(false); @@ -270,18 +236,8 @@ public class PreviewFragment extends Fragment implements R.dimen.preview_toolbar_set_wallpaper_button_end_padding), /* bottom */ 0); - mFullResImageView = view.findViewById(R.id.full_res_image); - mLoadingIndicator = view.findViewById(R.id.loading_indicator); - - mBottomSheet = view.findViewById(R.id.bottom_sheet); - mAttributionTitle = view.findViewById(R.id.preview_attribution_pane_title); - mAttributionSubtitle1 = view.findViewById(R.id.preview_attribution_pane_subtitle1); - mAttributionSubtitle2 = view.findViewById(R.id.preview_attribution_pane_subtitle2); - mAttributionExploreButton = view.findViewById( - R.id.preview_attribution_pane_explore_button); - mLowResImageView = view.findViewById(R.id.low_res_image); - mSetWallpaperButton = view.findViewById(R.id.preview_attribution_pane_set_wallpaper_button); - mSpacer = view.findViewById(R.id.spacer); + mBottomSheet = view.findViewById(getBottomSheetResId()); + setUpBottomSheetView(mBottomSheet); // Workaround as we don't have access to bottomDialogCornerRadius, mBottomSheet radii are // set to dialogCornerRadius by default. @@ -294,93 +250,18 @@ public class PreviewFragment extends Fragment implements bottomSheetBackground.setCornerRadii(radii); mBottomSheet.setBackground(bottomSheetBackground); - // Trim some memory from Glide to make room for the full-size image in this fragment. - Glide.get(getActivity()).setMemoryCategory(MemoryCategory.LOW); - - mDefaultCropSurfaceSize = WallpaperCropUtils.getDefaultCropSurfaceSize( - getResources(), getActivity().getWindowManager().getDefaultDisplay()); - mScreenSize = ScreenSizeCalculator.getInstance().getScreenSize( - getActivity().getWindowManager().getDefaultDisplay()); - - // Load a low-res placeholder image if there's a thumbnail available from the asset that can be - // shown to the user more quickly than the full-sized image. - if (mWallpaperAsset.hasLowResDataSource()) { - mWallpaperAsset.loadLowResDrawable(getActivity(), mLowResImageView, Color.BLACK, - new WallpaperPreviewBitmapTransformation(getActivity().getApplicationContext(), - isRtl())); - } - - mWallpaperAsset.decodeRawDimensions(getActivity(), new DimensionsReceiver() { - @Override - public void onDimensionsDecoded(Point dimensions) { - // Don't continue loading the wallpaper if the Fragment is detached. - Activity activity = getActivity(); - if (activity == null) { - return; - } - - // Return early and show a dialog if dimensions are null (signaling a decoding error). - if (dimensions == null) { - showLoadWallpaperErrorDialog(); - return; - } - - mRawWallpaperSize = dimensions; - ExploreIntentChecker intentChecker = - InjectorProvider.getInjector().getExploreIntentChecker(activity); - String actionUrl = mWallpaper.getActionUrl(activity); - if (actionUrl != null && !actionUrl.isEmpty()) { - Uri exploreUri = Uri.parse(mWallpaper.getActionUrl(activity)); - - intentChecker.fetchValidActionViewIntent(exploreUri, exploreIntent -> { - if (getActivity() == null) { - return; - } - - mExploreIntent = exploreIntent; - initFullResView(); - }); - } else { - initFullResView(); - } - } - }); - - // Configure loading indicator with a MaterialProgressDrawable. - mProgressDrawable = new MaterialProgressDrawable(getActivity().getApplicationContext(), - mLoadingIndicator); - mProgressDrawable.setAlpha(255); - mProgressDrawable.setBackgroundColor(getResources().getColor(R.color.material_white_100, - getContext().getTheme())); - mProgressDrawable.setColorSchemeColors(getAttrColor( - new ContextThemeWrapper(getContext(), getDeviceDefaultTheme()), - android.R.attr.colorAccent)); - mProgressDrawable.updateSizes(MaterialProgressDrawable.LARGE); - mLoadingIndicator.setImageDrawable(mProgressDrawable); - - // We don't want to show the spinner every time we load an image if it loads quickly; instead, - // only start showing the spinner if loading the image has taken longer than half of a second. - mLoadingIndicator.postDelayed(() -> { - if (mFullResImageView != null && !mFullResImageView.hasImage() - && !mTestingModeEnabled) { - mLoadingIndicator.setVisibility(View.VISIBLE); - mLoadingIndicator.setAlpha(1f); - if (mProgressDrawable != null) { - mProgressDrawable.start(); - } - } - }, 500); - - - mBottomSheetInitialState = (savedInstanceState == null) - ? STATE_EXPANDED - : savedInstanceState.getInt(KEY_BOTTOM_SHEET_STATE, - STATE_EXPANDED); + mBottomSheetInitialState = (savedInstanceState == null) ? STATE_EXPANDED + : savedInstanceState.getInt(KEY_BOTTOM_SHEET_STATE, STATE_EXPANDED); setUpBottomSheetListeners(); return view; } + protected abstract void setUpBottomSheetView(ViewGroup bottomSheet); + + @IdRes + protected abstract int getBottomSheetResId(); + protected int getDeviceDefaultTheme() { return android.R.style.Theme_DeviceDefault; } @@ -398,12 +279,12 @@ public class PreviewFragment extends Fragment implements // allow committing fragment transactions. if (mStagedLoadWallpaperErrorDialogFragment != null) { mStagedLoadWallpaperErrorDialogFragment.show( - getFragmentManager(), TAG_LOAD_WALLPAPER_ERROR_DIALOG_FRAGMENT); + requireFragmentManager(), TAG_LOAD_WALLPAPER_ERROR_DIALOG_FRAGMENT); mStagedLoadWallpaperErrorDialogFragment = null; } if (mStagedSetWallpaperErrorDialogFragment != null) { mStagedSetWallpaperErrorDialogFragment.show( - getFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT); + requireFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT); mStagedSetWallpaperErrorDialogFragment = null; } } @@ -428,20 +309,20 @@ public class PreviewFragment extends Fragment implements // the IndividualPreviewActivity, the "My photos" selection (by way of // TopLevelPickerActivity), or from a system "crop and set wallpaper" intent. // Therefore, handle the Up button as a global Back. - getActivity().onBackPressed(); + requireActivity().onBackPressed(); return true; } return false; } - protected void setupPreviewMenu(Menu menu) { + private void setupPreviewMenu(Menu menu) { mPreview = (CheckBox) menu.findItem(R.id.preview).getActionView(); mPreview.setChecked(mBottomSheetInitialState == STATE_COLLAPSED); mPreview.setOnClickListener(this::setPreviewBehavior); } - private void setPreviewChecked(boolean checked) { + protected void setPreviewChecked(boolean checked) { if (mPreview != null) { mPreview.setChecked(checked); int resId = checked ? R.string.expand_attribution_panel @@ -450,7 +331,7 @@ public class PreviewFragment extends Fragment implements } } - private void setPreviewBehavior(final View v) { + private void setPreviewBehavior(View v) { CheckBox checkbox = (CheckBox) v; BottomSheetBehavior<?> behavior = BottomSheetBehavior.from(mBottomSheet); @@ -461,6 +342,61 @@ public class PreviewFragment extends Fragment implements } } + protected void setUpSetWallpaperButton(Button setWallpaperButton) { + if (mPreviewMode == MODE_VIEW_ONLY) { + setWallpaperButton.setVisibility(View.GONE); + } else { + setWallpaperButton.setVisibility(View.VISIBLE); + setWallpaperButton.setOnClickListener(this::onSetWallpaperClicked); + } + } + + protected void setUpExploreButton(Button exploreButton) { + exploreButton.setVisibility(View.GONE); + if (mExploreIntent == null) { + return; + } + Context context = requireContext(); + exploreButton.setVisibility(View.VISIBLE); + exploreButton.setText(context.getString( + mWallpaper.getActionLabelRes(context))); + + exploreButton.setOnClickListener(view -> { + mUserEventLogger.logActionClicked(mWallpaper.getCollectionId(context), + mWallpaper.getActionLabelRes(context)); + + startActivity(mExploreIntent); + }); + } + + protected void setUpExploreIntent(@Nullable Runnable callback) { + Context context = getContext(); + if (context == null) { + return; + } + String actionUrl = mWallpaper.getActionUrl(context); + if (actionUrl != null && !actionUrl.isEmpty()) { + Uri exploreUri = Uri.parse(mWallpaper.getActionUrl(context)); + ExploreIntentChecker intentChecker = + InjectorProvider.getInjector().getExploreIntentChecker(context); + + intentChecker.fetchValidActionViewIntent(exploreUri, exploreIntent -> { + if (getActivity() == null) { + return; + } + + mExploreIntent = exploreIntent; + if (callback != null) { + callback.run(); + } + }); + } else { + if (callback != null) { + callback.run(); + } + } + } + @Override public void onSet(int destination) { setCurrentWallpaper(destination); @@ -483,10 +419,6 @@ public class PreviewFragment extends Fragment implements public void onDestroy() { super.onDestroy(); mWallpaperSetter.cleanUp(); - if (mProgressDrawable != null) { - mProgressDrawable.stop(); - } - mFullResImageView.recycle(); } @Override @@ -498,41 +430,8 @@ public class PreviewFragment extends Fragment implements } private void onSetWallpaperClicked(View button) { - if (BuildCompat.isAtLeastN()) { - mWallpaperSetter.requestDestination(getContext(), getFragmentManager(), this, - mWallpaper instanceof LiveWallpaperInfo); - } else { - setCurrentWallpaper(WallpaperPersister.DEST_HOME_SCREEN); - } - } - - /** - * Returns a zoom level that is similar to the actual zoom, but that is exactly 0.5 ** n for some - * integer n. This is useful for downsampling a bitmap--we want to see the bitmap at full detail, - * or downsampled to 1 in every 2 pixels, or 1 in 4, and so on, depending on the zoom. - */ - private static float getDownsampleZoom(float actualZoom) { - if (actualZoom > 1) { - // Very zoomed in, but we can't sample more than 1 pixel per pixel. - return 1.0f; - } - float lower = 1.0f / roundUpToPower2((int) Math.ceil(1 / actualZoom)); - float upper = lower * 2; - return nearestValue(actualZoom, lower, upper); - } - - /** - * Returns the integer rounded up to the next power of 2. - */ - private static int roundUpToPower2(int value) { - return 1 << (32 - Integer.numberOfLeadingZeros(value - 1)); - } - - /** - * Returns the closer of two values a and b to the given value. - */ - private static float nearestValue(float value, float a, float b) { - return Math.abs(a - value) < Math.abs(b - value) ? a : b; + mWallpaperSetter.requestDestination(getContext(), getFragmentManager(), this, + mWallpaper instanceof LiveWallpaperInfo); } private void setUpBottomSheetListeners() { @@ -554,6 +453,8 @@ public class PreviewFragment extends Fragment implements case STATE_EXPANDED: setPreviewChecked(false /* checked */); break; + default: + Log.v(TAG, "Ignoring BottomSheet state: " + newState); } } @@ -565,264 +466,13 @@ public class PreviewFragment extends Fragment implements } else { alpha = 1f - slideOffset; } - mAttributionTitle.setAlpha(alpha); - mAttributionSubtitle1.setAlpha(alpha); - mAttributionSubtitle2.setAlpha(alpha); - mAttributionExploreButton.setAlpha(alpha); + setBottomSheetContentAlpha(alpha); } }); } - private boolean isWallpaperLoaded() { - return mFullResImageView.hasImage(); - } - - private void populateAttributionPane() { - final Context context = getContext(); - - final BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(mBottomSheet); - - List<String> attributions = mWallpaper.getAttributions(context); - if (attributions.size() > 0 && attributions.get(0) != null) { - mAttributionTitle.setText(attributions.get(0)); - } - - if (attributions.size() > 1 && attributions.get(1) != null) { - mAttributionSubtitle1.setVisibility(View.VISIBLE); - mAttributionSubtitle1.setText(attributions.get(1)); - } - - if (attributions.size() > 2 && attributions.get(2) != null) { - mAttributionSubtitle2.setVisibility(View.VISIBLE); - mAttributionSubtitle2.setText(attributions.get(2)); - } - - if (mPreviewMode == MODE_CROP_AND_SET_WALLPAPER) { - mSetWallpaperButton.setVisibility(View.VISIBLE); - mSetWallpaperButton.setOnClickListener(this::onSetWallpaperClicked); - } else { - mSetWallpaperButton.setVisibility(View.GONE); - } - - String actionUrl = mWallpaper.getActionUrl(context); - - mAttributionExploreButton.setVisibility(View.GONE); - if (actionUrl != null && !actionUrl.isEmpty()) { - if (mExploreIntent != null) { - mAttributionExploreButton.setVisibility(View.VISIBLE); - mAttributionExploreButton.setText(context.getString( - mWallpaper.getActionLabelRes(context))); - - mAttributionExploreButton.setOnClickListener(view -> { - mUserEventLogger.logActionClicked(mWallpaper.getCollectionId(context), - mWallpaper.getActionLabelRes(context)); - - startActivity(mExploreIntent); - }); - } - } - - if (mAttributionExploreButton.getVisibility() == View.VISIBLE - && mSetWallpaperButton.getVisibility() == View.VISIBLE) { - mSpacer.setVisibility(View.VISIBLE); - } else { - mSpacer.setVisibility(View.GONE); - } - - mBottomSheet.setVisibility(View.VISIBLE); - - // Initialize the state of the BottomSheet based on the current state because if the initial - // and current state are the same, the state change listener won't fire and set the correct - // arrow asset and text alpha. - if (bottomSheetBehavior.getState() == STATE_EXPANDED) { - setPreviewChecked(false); - mAttributionTitle.setAlpha(1f); - mAttributionSubtitle1.setAlpha(1f); - mAttributionSubtitle2.setAlpha(1f); - } else { - setPreviewChecked(true); - mAttributionTitle.setAlpha(0f); - mAttributionSubtitle1.setAlpha(0f); - mAttributionSubtitle2.setAlpha(0f); - } - - // Let the state change listener take care of animating a state change to the initial state - // if there's a state change. - bottomSheetBehavior.setState(mBottomSheetInitialState); - } - - /** - * Initializes MosaicView by initializing tiling, setting a fallback page bitmap, and - * initializing a zoom-scroll observer and click listener. - */ - private void initFullResView() { - mFullResImageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_CROP); - - // Set a solid black "page bitmap" so MosaicView draws a black background while waiting - // for the image to load or a transparent one if a thumbnail already loaded. - Bitmap blackBitmap = Bitmap.createBitmap(1, 1, Config.ARGB_8888); - int color = (mLowResImageView.getDrawable() == null) ? Color.BLACK : Color.TRANSPARENT; - blackBitmap.setPixel(0, 0, color); - mFullResImageView.setImage(ImageSource.bitmap(blackBitmap)); - - // Then set a fallback "page bitmap" to cover the whole MosaicView, which is an actual - // (lower res) version of the image to be displayed. - Point targetPageBitmapSize = new Point(mRawWallpaperSize); - mWallpaperAsset.decodeBitmap(targetPageBitmapSize.x, targetPageBitmapSize.y, - new BitmapReceiver() { - @Override - public void onBitmapDecoded(Bitmap pageBitmap) { - // Check that the activity is still around since the decoding task started. - if (getActivity() == null) { - return; - } - - // Some of these may be null depending on if the Fragment is paused, stopped, - // or destroyed. - if (mLoadingIndicator != null) { - mLoadingIndicator.setVisibility(View.GONE); - } - // The page bitmap may be null if there was a decoding error, so show an - // error dialog. - if (pageBitmap == null) { - showLoadWallpaperErrorDialog(); - return; - } - if (mFullResImageView != null) { - // Set page bitmap. - mFullResImageView.setImage(ImageSource.bitmap(pageBitmap)); - - setDefaultWallpaperZoomAndScroll(); - crossFadeInMosaicView(); - } - if (mProgressDrawable != null) { - mProgressDrawable.stop(); - } - getActivity().invalidateOptionsMenu(); - - populateAttributionPane(); - } - }); - } + protected void setBottomSheetContentAlpha(float alpha) { - /** - * Makes the MosaicView visible with an alpha fade-in animation while fading out the loading - * indicator. - */ - private void crossFadeInMosaicView() { - long shortAnimationDuration = getResources().getInteger( - android.R.integer.config_shortAnimTime); - - mFullResImageView.setAlpha(0f); - mFullResImageView.animate() - .alpha(1f) - .setDuration(shortAnimationDuration) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - // Clear the thumbnail bitmap reference to save memory since it's no longer - // visible. - if (mLowResImageView != null) { - mLowResImageView.setImageBitmap(null); - } - } - }); - - mLoadingIndicator.animate() - .alpha(0f) - .setDuration(shortAnimationDuration) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (mLoadingIndicator != null) { - mLoadingIndicator.setVisibility(View.GONE); - } - } - }); - } - - /** - * Sets the default wallpaper zoom and scroll position based on a "crop surface" - * (with extra width to account for parallax) superimposed on the screen. Shows as much of the - * wallpaper as possible on the crop surface and align screen to crop surface such that the - * default preview matches what would be seen by the user in the left-most home screen. - * - * <p>This method is called once in the Fragment lifecycle after the wallpaper asset has loaded - * and rendered to the layout. - */ - private void setDefaultWallpaperZoomAndScroll() { - // Determine minimum zoom to fit maximum visible area of wallpaper on crop surface. - float defaultWallpaperZoom = - WallpaperCropUtils.calculateMinZoom(mRawWallpaperSize, mDefaultCropSurfaceSize); - float minWallpaperZoom = - WallpaperCropUtils.calculateMinZoom(mRawWallpaperSize, mScreenSize); - - Point screenToCropSurfacePosition = WallpaperCropUtils.calculateCenterPosition( - mDefaultCropSurfaceSize, mScreenSize, true /* alignStart */, isRtl()); - Point zoomedWallpaperSize = new Point( - Math.round(mRawWallpaperSize.x * defaultWallpaperZoom), - Math.round(mRawWallpaperSize.y * defaultWallpaperZoom)); - Point cropSurfaceToWallpaperPosition = WallpaperCropUtils.calculateCenterPosition( - zoomedWallpaperSize, mDefaultCropSurfaceSize, false /* alignStart */, isRtl()); - - // Set min wallpaper zoom and max zoom on MosaicView widget. - mFullResImageView.setMaxScale(Math.max(DEFAULT_WALLPAPER_MAX_ZOOM, defaultWallpaperZoom)); - mFullResImageView.setMinScale(minWallpaperZoom); - - // Set center to composite positioning between scaled wallpaper and screen. - PointF centerPosition = new PointF( - mRawWallpaperSize.x / 2f, - mRawWallpaperSize.y / 2f); - centerPosition.offset( - (screenToCropSurfacePosition.x + cropSurfaceToWallpaperPosition.x), - - (screenToCropSurfacePosition.y + cropSurfaceToWallpaperPosition.y)); - - mFullResImageView.setScaleAndCenter(minWallpaperZoom, centerPosition); - } - - protected Rect calculateCropRect() { - // Calculate Rect of wallpaper in physical pixel terms (i.e., scaled to current zoom). - float wallpaperZoom = mFullResImageView.getScale(); - int scaledWallpaperWidth = (int) (mRawWallpaperSize.x * wallpaperZoom); - int scaledWallpaperHeight = (int) (mRawWallpaperSize.y * wallpaperZoom); - Rect rect = new Rect(); - mFullResImageView.visibleFileRect(rect); - int scrollX = (int) (rect.left * wallpaperZoom); - int scrollY = (int) (rect.top * wallpaperZoom); - - rect.set(0, 0, scaledWallpaperWidth, scaledWallpaperHeight); - Point screenSize = ScreenSizeCalculator.getInstance().getScreenSize( - getActivity().getWindowManager().getDefaultDisplay()); - // Crop rect should start off as the visible screen and then include extra width and height - // if available within wallpaper at the current zoom. - Rect cropRect = new Rect(scrollX, scrollY, scrollX + screenSize.x, scrollY + screenSize.y); - - Point defaultCropSurfaceSize = WallpaperCropUtils.getDefaultCropSurfaceSize( - getResources(), getActivity().getWindowManager().getDefaultDisplay()); - int extraWidth = defaultCropSurfaceSize.x - screenSize.x; - int extraHeightTopAndBottom = (int) ((defaultCropSurfaceSize.y - screenSize.y) / 2f); - - // Try to increase size of screenRect to include extra width depending on the layout - // direction. - if (isRtl()) { - cropRect.left = Math.max(cropRect.left - extraWidth, rect.left); - } else { - cropRect.right = Math.min(cropRect.right + extraWidth, rect.right); - } - - // Try to increase the size of the cropRect to to include extra height. - int availableExtraHeightTop = cropRect.top - Math.max( - rect.top, - cropRect.top - extraHeightTopAndBottom); - int availableExtraHeightBottom = Math.min( - rect.bottom, - cropRect.bottom + extraHeightTopAndBottom) - cropRect.bottom; - - int availableExtraHeightTopAndBottom = - Math.min(availableExtraHeightTop, availableExtraHeightBottom); - cropRect.top -= availableExtraHeightTopAndBottom; - cropRect.bottom += availableExtraHeightTopAndBottom; - - return cropRect; } /** @@ -830,35 +480,22 @@ public class PreviewFragment extends Fragment implements * * @param destination The wallpaper destination i.e. home vs. lockscreen vs. both. */ - private void setCurrentWallpaper(@Destination final int destination) { - mWallpaperSetter.setCurrentWallpaper(getActivity(), mWallpaper, mWallpaperAsset, - destination, mFullResImageView.getScale(), calculateCropRect(), - new SetWallpaperCallback() { - @Override - public void onSuccess() { - finishActivityWithResultOk(); - } - - @Override - public void onError(@Nullable Throwable throwable) { - showSetWallpaperErrorDialog(destination); - } - }); - } + protected abstract void setCurrentWallpaper(@Destination int destination); - private void finishActivityWithResultOk() { + protected void finishActivityWithResultOk() { + Activity activity = requireActivity(); try { - Toast.makeText( - getActivity(), R.string.wallpaper_set_successfully_message, Toast.LENGTH_SHORT).show(); + Toast.makeText(activity, + R.string.wallpaper_set_successfully_message, Toast.LENGTH_SHORT).show(); } catch (NotFoundException e) { Log.e(TAG, "Could not show toast " + e); } - getActivity().overridePendingTransition(R.anim.fade_in, R.anim.fade_out); - getActivity().setResult(Activity.RESULT_OK); - getActivity().finish(); + activity.overridePendingTransition(R.anim.fade_in, R.anim.fade_out); + activity.setResult(Activity.RESULT_OK); + activity.finish(); } - private void showSetWallpaperErrorDialog(@Destination int wallpaperDestination) { + protected void showSetWallpaperErrorDialog(@Destination int wallpaperDestination) { SetWallpaperErrorDialogFragment newFragment = SetWallpaperErrorDialogFragment.newInstance( R.string.set_wallpaper_error_message, wallpaperDestination); newFragment.setTargetFragment(this, UNUSED_REQUEST_CODE); @@ -866,9 +503,9 @@ public class PreviewFragment extends Fragment implements // Show 'set wallpaper' error dialog now if it's safe to commit fragment transactions, // otherwise stage it for later when the hosting activity is in a state to commit fragment // transactions. - BasePreviewActivity activity = (BasePreviewActivity) getActivity(); + BasePreviewActivity activity = (BasePreviewActivity) requireActivity(); if (activity.isSafeToCommitFragmentTransaction()) { - newFragment.show(getFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT); + newFragment.show(requireFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT); } else { mStagedSetWallpaperErrorDialogFragment = newFragment; } @@ -878,17 +515,16 @@ public class PreviewFragment extends Fragment implements * Shows 'load wallpaper' error dialog now or stage it to be shown when the hosting activity is * in a state that allows committing fragment transactions. */ - private void showLoadWallpaperErrorDialog() { + protected void showLoadWallpaperErrorDialog() { LoadWallpaperErrorDialogFragment dialogFragment = LoadWallpaperErrorDialogFragment.newInstance(); - dialogFragment.setTargetFragment(PreviewFragment.this, UNUSED_REQUEST_CODE); + dialogFragment.setTargetFragment(this, UNUSED_REQUEST_CODE); // Show 'load wallpaper' error dialog now or stage it to be shown when the hosting // activity is in a state that allows committing fragment transactions. BasePreviewActivity activity = (BasePreviewActivity) getActivity(); if (activity != null && activity.isSafeToCommitFragmentTransaction()) { - dialogFragment.show(PreviewFragment.this.getFragmentManager(), - TAG_LOAD_WALLPAPER_ERROR_DIALOG_FRAGMENT); + dialogFragment.show(requireFragmentManager(), TAG_LOAD_WALLPAPER_ERROR_DIALOG_FRAGMENT); } else { mStagedLoadWallpaperErrorDialogFragment = dialogFragment; } @@ -903,59 +539,10 @@ public class PreviewFragment extends Fragment implements } /** - * Gets the appropriate ActivityInfo orientation for the current configuration orientation to - * enable locking screen rotation at API levels lower than 18. - */ - @ActivityInfoScreenOrientation - private int getCompatActivityInfoOrientation() { - int configOrientation = getResources().getConfiguration().orientation; - final Display display = getActivity().getWindowManager().getDefaultDisplay(); - int naturalOrientation = Configuration.ORIENTATION_LANDSCAPE; - switch (display.getRotation()) { - case Surface.ROTATION_0: - case Surface.ROTATION_180: - // We are currently in the same basic orientation as the natural orientation. - naturalOrientation = configOrientation; - break; - case Surface.ROTATION_90: - case Surface.ROTATION_270: - // We are currently in the other basic orientation to the natural orientation. - naturalOrientation = (configOrientation == Configuration.ORIENTATION_LANDSCAPE) - ? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE; - break; - default: - // continue below - } - - // Since the map starts at portrait, we need to offset if this device's natural orientation - // is landscape. - int indexOffset = 0; - if (naturalOrientation == Configuration.ORIENTATION_LANDSCAPE) { - indexOffset = 1; - } - - switch ((display.getRotation() + indexOffset) % 4) { - case 0: - return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; - case 1: - return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; - case 2: - return ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; - case 3: - return ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; - default: - Log.e(TAG, "Display rotation did not correspond to a valid ActivityInfo orientation" - + "with display rotation: " + display.getRotation() + " and index offset: " - + indexOffset + "."); - return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; - } - } - - /** * Returns whether layout direction is RTL (or false for LTR). Since native RTL layout support * was added in API 17, returns false for versions lower than 17. */ - private boolean isRtl() { + protected boolean isRtl() { return getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; } diff --git a/src/com/android/wallpaper/picker/StartRotationDialogFragment.java b/src/com/android/wallpaper/picker/StartRotationDialogFragment.java index d5fd017..f0e4dcd 100755 --- a/src/com/android/wallpaper/picker/StartRotationDialogFragment.java +++ b/src/com/android/wallpaper/picker/StartRotationDialogFragment.java @@ -29,8 +29,6 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; - -import androidx.appcompat.view.ContextThemeWrapper; import androidx.fragment.app.DialogFragment; import com.android.wallpaper.R; @@ -42,29 +40,14 @@ import com.android.wallpaper.module.InjectorProvider; */ public class StartRotationDialogFragment extends DialogFragment { private static final String KEY_IS_WIFI_ONLY_CHECKED = "key_is_wifi_only_checked"; - private static final String KEY_IS_LIVE_WALLPAPER_PREVIEW_NEEDED = "key_is_live_wallpaper_needed"; private static final boolean DEFAULT_IS_WIFI_ONLY = true; private boolean mIsWifiOnlyChecked; - private boolean mIsLiveWallpaperPreviewNeeded; - - public static StartRotationDialogFragment newInstance(boolean isLiveWallpaperPreviewNeeded) { - StartRotationDialogFragment dialogFragment = new StartRotationDialogFragment(); - Bundle args = new Bundle(); - args.putBoolean(KEY_IS_LIVE_WALLPAPER_PREVIEW_NEEDED, isLiveWallpaperPreviewNeeded); - dialogFragment.setArguments(args); - return dialogFragment; - } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Bundle args = getArguments(); - if (args != null) { - mIsLiveWallpaperPreviewNeeded = args.getBoolean(KEY_IS_LIVE_WALLPAPER_PREVIEW_NEEDED); - } - if (savedInstanceState == null) { mIsWifiOnlyChecked = DEFAULT_IS_WIFI_ONLY; } else { @@ -117,14 +100,10 @@ public class StartRotationDialogFragment extends DialogFragment { } private int getBodyTextResourceId() { - return mIsLiveWallpaperPreviewNeeded - ? R.string.start_rotation_dialog_body_live_wallpaper_needed - : R.string.start_rotation_dialog_body; + return R.string.start_rotation_dialog_body; } private int getPositiveButtonTextResourceId() { - return mIsLiveWallpaperPreviewNeeded - ? R.string.start_rotation_dialog_continue - : android.R.string.ok; + return android.R.string.ok; } } diff --git a/src/com/android/wallpaper/picker/individual/IndividualPickerActivity.java b/src/com/android/wallpaper/picker/individual/IndividualPickerActivity.java index 367181a..de5ba84 100755 --- a/src/com/android/wallpaper/picker/individual/IndividualPickerActivity.java +++ b/src/com/android/wallpaper/picker/individual/IndividualPickerActivity.java @@ -16,8 +16,6 @@ package com.android.wallpaper.picker.individual; import android.app.Activity; -import android.app.WallpaperManager; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.res.Resources.NotFoundException; @@ -44,12 +42,9 @@ import com.android.wallpaper.model.PickerIntentFactory; import com.android.wallpaper.model.WallpaperInfo; import com.android.wallpaper.module.Injector; import com.android.wallpaper.module.InjectorProvider; -import com.android.wallpaper.module.LiveWallpaperStatusChecker; -import com.android.wallpaper.module.NoBackupImageWallpaper; import com.android.wallpaper.module.WallpaperPersister; import com.android.wallpaper.picker.BaseActivity; import com.android.wallpaper.picker.PreviewActivity.PreviewActivityIntentFactory; -import com.android.wallpaper.util.ActivityUtils; import com.android.wallpaper.util.DiskBasedLogger; /** @@ -61,13 +56,11 @@ public class IndividualPickerActivity extends BaseActivity { private static final String EXTRA_CATEGORY_COLLECTION_ID = "com.android.wallpaper.category_collection_id"; private static final int PREVIEW_WALLPAPER_REQUEST_CODE = 0; - private static final int NO_BACKUP_IMAGE_WALLPAPER_REQUEST_CODE = 1; private static final int PREVIEW_LIVEWALLPAPER_REQUEST_CODE = 2; private static final String KEY_CATEGORY_COLLECTION_ID = "key_category_collection_id"; private InlinePreviewIntentFactory mPreviewIntentFactory; private WallpaperPersister mWallpaperPersister; - private LiveWallpaperStatusChecker mLiveWallpaperStatusChecker; private Category mCategory; private String mCategoryCollectionId; @@ -83,7 +76,6 @@ public class IndividualPickerActivity extends BaseActivity { mPreviewIntentFactory = new PreviewActivityIntentFactory(); Injector injector = InjectorProvider.getInjector(); mWallpaperPersister = injector.getWallpaperPersister(this); - mLiveWallpaperStatusChecker = injector.getLiveWallpaperStatusChecker(this); FragmentManager fm = getSupportFragmentManager(); Fragment fragment = fm.findFragmentById(R.id.fragment_container); @@ -160,8 +152,8 @@ public class IndividualPickerActivity extends BaseActivity { public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == android.R.id.home) { - // Handle Up as a Global back since the only entry point to IndividualPickerActivity is from - // TopLevelPickerActivity. + // Handle Up as a Global back since the only entry point to IndividualPickerActivity is + // from TopLevelPickerActivity. onBackPressed(); return true; } @@ -184,18 +176,6 @@ public class IndividualPickerActivity extends BaseActivity { finishWithResultOk(shouldShowMessage); } break; - - case NO_BACKUP_IMAGE_WALLPAPER_REQUEST_CODE: - // User clicked "Set wallpaper" in live wallpaper preview UI. - // NOTE: Don't check for the result code prior to KitKat MR2 because a bug on those versions - // caused the result code to be discarded from LivePicker so we can't rely on it. - if ((!BuildCompat.isAtLeastL() || resultCode == Activity.RESULT_OK) - && mLiveWallpaperStatusChecker.isNoBackupImageWallpaperSet() - && mCategory.getWallpaperRotationInitializer().startRotation(getApplicationContext())) { - finishWithResultOk(true); - } - break; - default: Log.e(TAG, "Invalid request code: " + requestCode); } @@ -211,18 +191,6 @@ public class IndividualPickerActivity extends BaseActivity { : PREVIEW_WALLPAPER_REQUEST_CODE); } - /** - * Shows the system live wallpaper preview for the {@link NoBackupImageWallpaper} which is used to - * draw rotating wallpapers on pre-N Android builds. - */ - public void showNoBackupImageWallpaperPreview() { - Intent intent = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER); - ComponentName componentName = new ComponentName(this, NoBackupImageWallpaper.class); - intent.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT, componentName); - ActivityUtils.startActivityForResultSafely( - this, intent, NO_BACKUP_IMAGE_WALLPAPER_REQUEST_CODE); - } - private void finishWithResultOk(boolean shouldShowMessage) { if (shouldShowMessage) { try { diff --git a/src/com/android/wallpaper/picker/individual/IndividualPickerFragment.java b/src/com/android/wallpaper/picker/individual/IndividualPickerFragment.java index db21da3..c0c4ce5 100755 --- a/src/com/android/wallpaper/picker/individual/IndividualPickerFragment.java +++ b/src/com/android/wallpaper/picker/individual/IndividualPickerFragment.java @@ -58,13 +58,11 @@ import com.android.wallpaper.model.WallpaperRotationInitializer; import com.android.wallpaper.model.WallpaperRotationInitializer.Listener; import com.android.wallpaper.model.WallpaperRotationInitializer.NetworkPreference; import com.android.wallpaper.model.WallpaperRotationInitializer.RotationInitializationState; -import com.android.wallpaper.model.WallpaperRotationInitializer.RotationStateListener; import com.android.wallpaper.module.FormFactorChecker; import com.android.wallpaper.module.FormFactorChecker.FormFactor; import com.android.wallpaper.module.Injector; import com.android.wallpaper.module.InjectorProvider; import com.android.wallpaper.module.PackageStatusNotifier; -import com.android.wallpaper.module.RotatingWallpaperComponentChecker; import com.android.wallpaper.module.WallpaperChangedNotifier; import com.android.wallpaper.module.WallpaperPersister; import com.android.wallpaper.module.WallpaperPersister.Destination; @@ -116,7 +114,6 @@ public class IndividualPickerFragment extends Fragment WallpaperPreferences mWallpaperPreferences; WallpaperChangedNotifier mWallpaperChangedNotifier; - RotatingWallpaperComponentChecker mRotatingWallpaperComponentChecker; RecyclerView mImageGrid; IndividualAdapter mAdapter; WallpaperCategory mCategory; @@ -250,8 +247,6 @@ public class IndividualPickerFragment extends Fragment mWallpaperChangedNotifier = WallpaperChangedNotifier.getInstance(); mWallpaperChangedNotifier.registerListener(mWallpaperChangedListener); - mRotatingWallpaperComponentChecker = injector.getRotatingWallpaperComponentChecker(); - mFormFactor = injector.getFormFactorChecker(appContext).getFormFactor(); mPackageStatusNotifier = injector.getPackageStatusNotifier(appContext); @@ -445,7 +440,8 @@ public class IndividualPickerFragment extends Fragment public void onResume() { super.onResume(); - WallpaperPreferences preferences = InjectorProvider.getInjector().getPreferences(getActivity()); + 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 @@ -541,31 +537,23 @@ public class IndividualPickerFragment extends Fragment * state of the user's device and binds the state of the current category's rotation to the "start * rotation" tile. */ - private void refreshRotationHolder(final RotationHolder rotationHolder) { + private void refreshRotationHolder(RotationHolder rotationHolder) { mWallpaperRotationInitializer.fetchRotationInitializationState(getContext(), - new RotationStateListener() { - @Override - public void onRotationStateReceived( - @RotationInitializationState final int rotationInitializationState) { - - // Update the UI state of the "start rotation" tile displayed on screen. Do this in a - // Handler so it is scheduled at the end of the message queue. This is necessary to - // ensure we do not remove or add data from the adapter while the layout is still being - // computed. RecyclerView documentation therefore recommends performing such changes in - // a Handler. - new android.os.Handler().post(new Runnable() { - @Override - public void run() { - // A config change may have destroyed the activity since the refresh started, so - // check for that to avoid an NPE. - if (getActivity() == null) { - return; - } + rotationState -> { + // Update the UI state of the "start rotation" tile displayed on screen. + // Do this in a Handler so it is scheduled at the end of the message queue. + // This is necessary to ensure we do not remove or add data from the adapter + // while the layout is still being computed. RecyclerView documentation + // therefore recommends performing such changes in a Handler. + new Handler().post(() -> { + // A config change may have destroyed the activity since the refresh + // started, so check for that to avoid an NPE. + if (getActivity() == null) { + return; + } - rotationHolder.bindRotationInitializationState(rotationInitializationState); - } - }); - } + rotationHolder.bindRotationInitializationState(rotationState); + }); }); } @@ -614,34 +602,33 @@ public class IndividualPickerFragment extends Fragment // app before the first wallpaper image in rotation finishes downloading. Activity activity = getActivity(); - if (activity != null - && mWallpaperRotationInitializer - .isNoBackupImageWallpaperPreviewNeeded(appContext)) { - ((IndividualPickerActivity) activity).showNoBackupImageWallpaperPreview(); - } else { - if (mWallpaperRotationInitializer.startRotation(appContext)) { - if (activity != null && mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) { - try { - Toast.makeText(getActivity(), R.string.wallpaper_set_successfully_message, - Toast.LENGTH_SHORT).show(); - } catch (NotFoundException e) { - Log.e(TAG, "Could not show toast " + e); - } - activity.setResult(Activity.RESULT_OK); - activity.finish(); - } else if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) { - mAdapter.updateSelectedTile(SPECIAL_FIXED_TILE_ADAPTER_POSITION); - } - } else { // Failed to start rotation. - showStartRotationErrorDialog(networkPreference); - - if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) { - DesktopRotationHolder rotationViewHolder = - (DesktopRotationHolder) mImageGrid.findViewHolderForAdapterPosition( - SPECIAL_FIXED_TILE_ADAPTER_POSITION); - rotationViewHolder.setSelectionState(SelectableHolder.SELECTION_STATE_DESELECTED); + 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); } } } @@ -690,11 +677,7 @@ public class IndividualPickerFragment extends Fragment * Returns whether rotation is enabled for this category. */ boolean isRotationEnabled() { - boolean isRotationSupported = - mRotatingWallpaperComponentChecker.getRotatingWallpaperSupport(getContext()) - == RotatingWallpaperComponentChecker.ROTATING_WALLPAPER_SUPPORT_SUPPORTED; - - return isRotationSupported && mWallpaperRotationInitializer != null; + return mWallpaperRotationInitializer != null; } @Override @@ -732,10 +715,10 @@ public class IndividualPickerFragment extends Fragment super(itemView); itemView.setOnClickListener(this); - mTileLayout = (FrameLayout) itemView.findViewById(R.id.daily_refresh); - mRotationMessage = (TextView) itemView.findViewById(R.id.rotation_tile_message); - mRotationTitle = (TextView) itemView.findViewById(R.id.rotation_tile_title); - mRefreshIcon = (ImageView) itemView.findViewById(R.id.rotation_tile_refresh_icon); + mTileLayout = itemView.findViewById(R.id.daily_refresh); + mRotationMessage = itemView.findViewById(R.id.rotation_tile_message); + mRotationTitle = itemView.findViewById(R.id.rotation_tile_title); + mRefreshIcon = itemView.findViewById(R.id.rotation_tile_refresh_icon); mTileLayout.getLayoutParams().height = mTileSizePx.y; // If the feature flag for "dynamic start rotation tile" is not enabled, fall back to the @@ -749,7 +732,8 @@ public class IndividualPickerFragment extends Fragment mRotationMessage.setTextColor( getResources().getColor(R.color.rotation_tile_enabled_subtitle_text_color)); mRefreshIcon.setColorFilter( - getResources().getColor(R.color.rotation_tile_enabled_refresh_icon_color), Mode.SRC_IN); + getResources().getColor(R.color.rotation_tile_enabled_refresh_icon_color), + Mode.SRC_IN); return; } @@ -765,10 +749,7 @@ public class IndividualPickerFragment extends Fragment @Override public void onClick(View v) { - boolean isLiveWallpaperNeeded = mWallpaperRotationInitializer - .isNoBackupImageWallpaperPreviewNeeded(getActivity().getApplicationContext()); - DialogFragment startRotationDialogFragment = StartRotationDialogFragment - .newInstance(isLiveWallpaperNeeded); + DialogFragment startRotationDialogFragment = new StartRotationDialogFragment(); startRotationDialogFragment.setTargetFragment( IndividualPickerFragment.this, UNUSED_REQUEST_CODE); startRotationDialogFragment.show(getFragmentManager(), TAG_START_ROTATION_DIALOG); |