blob: 337a31f41b5d7df5d55723cba6d42548877e4484 [file] [log] [blame]
/*
* 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.android.wallpaper.widget.BottomActionBar.BottomAction.APPLY;
import static com.android.wallpaper.widget.BottomActionBar.BottomAction.CUSTOMIZE;
import static com.android.wallpaper.widget.BottomActionBar.BottomAction.DELETE;
import static com.android.wallpaper.widget.BottomActionBar.BottomAction.INFORMATION;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.WallpaperInfo;
import android.app.WallpaperManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
import android.service.wallpaper.IWallpaperConnection;
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.MotionEvent;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.cardview.widget.CardView;
import androidx.lifecycle.LiveData;
import androidx.slice.Slice;
import androidx.slice.widget.SliceLiveData;
import androidx.slice.widget.SliceView;
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.util.ScreenSizeCalculator;
import com.android.wallpaper.util.SizeCalculator;
import com.android.wallpaper.util.WallpaperConnection;
import com.android.wallpaper.widget.BottomActionBar;
import com.android.wallpaper.widget.LiveTileOverlay;
import com.android.wallpaper.widget.LockScreenOverlayUpdater;
import com.android.wallpaper.widget.WallpaperInfoView;
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 implements
WallpaperConnection.WallpaperConnectionListener {
public static final String EXTRA_LIVE_WALLPAPER_INFO = "android.live_wallpaper.info";
public static final String KEY_ACTION_DELETE_LIVE_WALLPAPER = "action_delete_live_wallpaper";
private static final String TAG = "LivePreviewFragment";
/**
* Instance of {@link WallpaperConnection} used to bind to the live wallpaper service to show
* it in this preview fragment.
* @see IWallpaperConnection
*/
protected WallpaperConnection mWallpaperConnection;
private final int[] mLivePreviewLocation = new int[2];
private final Rect mPreviewLocalRect = new Rect();
private final Rect mPreviewGlobalRect = new Rect();
private Intent mDeleteIntent;
private Intent mSettingsIntent;
private List<Pair<String, View>> mPages;
private SliceView mSettingsSliceView;
private LiveData<Slice> mSettingsLiveData;
private View mLoadingScrim;
private InfoPageController mInfoPageController;
private Point mScreenSize;
private ViewGroup mViewGroup;
private CardView mHomePreviewCard;
private TouchForwardingLayout mTouchForwardingLayout;
private ImageView mHomePreview;
private View mTab;
private TextView mHomeTextView;
private TextView mLockTextView;
private WallpaperInfoView mWallpaperInfoView;
private SurfaceView mWorkspaceSurface;
private ViewGroup mLockScreenOverlay;
private LockScreenOverlayUpdater mLockScreenOverlayUpdater;
private WorkspaceSurfaceHolderCallback mWorkspaceSurfaceCallback;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
android.app.WallpaperInfo info = mWallpaper.getWallpaperComponent();
setUpExploreIntentAndLabel(null);
String deleteAction = getDeleteAction(info);
if (!TextUtils.isEmpty(deleteAction)) {
mDeleteIntent = new Intent(deleteAction);
mDeleteIntent.setPackage(info.getPackageName());
mDeleteIntent.putExtra(EXTRA_LIVE_WALLPAPER_INFO, info);
}
String settingsActivity = getSettingsActivity(info);
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;
}
}
}
@Nullable
protected String getSettingsActivity(WallpaperInfo info) {
return info.getSettingsActivity();
}
protected Intent getWallpaperIntent(WallpaperInfo info) {
return new Intent(WallpaperService.SERVICE_INTERFACE)
.setClassName(info.getPackageName(), info.getServiceName());
}
@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);
setUpLoadingIndicator();
mScreenSize = ScreenSizeCalculator.getInstance().getScreenSize(
activity.getWindowManager().getDefaultDisplay());
mViewGroup = view.findViewById(R.id.live_wallpaper_preview);
mHomePreviewCard = mViewGroup.findViewById(R.id.wallpaper_full_preview_card);
mTouchForwardingLayout = view.findViewById(R.id.touch_forwarding_layout);
mHomePreview = mHomePreviewCard.findViewById(R.id.wallpaper_preview_image);
mTouchForwardingLayout.setTargetView(mHomePreview);
mTouchForwardingLayout.setForwardingEnabled(true);
mLockScreenOverlay = mViewGroup.findViewById(R.id.lock_overlay);
mLockScreenOverlayUpdater =
new LockScreenOverlayUpdater(getContext(), mLockScreenOverlay, getLifecycle());
mLockScreenOverlayUpdater.adjustOverlayLayout(/* isFullScreen= */ true);
mTab = view.findViewById(R.id.tabs_container);
mHomeTextView = mTab.findViewById(R.id.home);
mLockTextView = mTab.findViewById(R.id.lock);
mWorkspaceSurface = mHomePreviewCard.findViewById(R.id.workspace_surface);
mWorkspaceSurfaceCallback = new WorkspaceSurfaceHolderCallback(
mWorkspaceSurface, getContext());
updateScreenTab(/* isHomeSelected= */ mViewAsHome);
view.addOnLayoutChangeListener((thisView, left, top, right, bottom,
oldLeft, oldTop, oldRight, oldBottom) ->
setupPreview()
);
setupCurrentWallpaperPreview(view);
previewLiveWallpaper(container, mHomePreview);
setupPreview();
renderWorkspaceSurface();
onBottomActionBarReady(mBottomActionBar);
return view;
}
private void updateScreenTab(boolean isHomeSelected) {
mHomeTextView.setSelected(isHomeSelected);
mLockTextView.setSelected(!isHomeSelected);
mWorkspaceSurface.setVisibility(isHomeSelected ? View.VISIBLE : View.GONE);
mLockScreenOverlay.setVisibility(isHomeSelected ? View.GONE : View.VISIBLE);
}
private void setupPreview() {
float screenAspectRatio = (float) mScreenSize.y / mScreenSize.x;
int measuredViewHeight = mViewGroup.getMeasuredHeight();
int measuredViewWidth = mViewGroup.getMeasuredWidth();
int absoluteViewWidth = (int) ((measuredViewHeight - mViewGroup.getPaddingBottom()
- mViewGroup.getPaddingTop()) / screenAspectRatio);
int horizontalPadding = (measuredViewWidth - absoluteViewWidth) / 2;
mViewGroup.setPaddingRelative(
horizontalPadding,
mViewGroup.getPaddingTop(),
horizontalPadding,
mViewGroup.getPaddingBottom());
mHomeTextView.setOnClickListener(view ->
updateScreenTab(/* isHomeSelected= */ true)
);
mLockTextView.setOnClickListener(view ->
updateScreenTab(/* isHomeSelected= */ false)
);
repositionPreview(mHomePreview);
((CardView) mHomePreview.getParent())
.setRadius(SizeCalculator.getPreviewCornerRadius(
getActivity(), mHomePreviewCard.getMeasuredWidth()));
}
private void repositionPreview(ImageView previewView) {
previewView.getLocationOnScreen(mLivePreviewLocation);
mPreviewGlobalRect.set(0, 0, previewView.getMeasuredWidth(),
previewView.getMeasuredHeight());
mPreviewLocalRect.set(mPreviewGlobalRect);
mPreviewGlobalRect.offset(mLivePreviewLocation[0], mLivePreviewLocation[1]);
if (mWallpaperConnection != null) {
mWallpaperConnection.updatePreviewPosition(mPreviewGlobalRect);
}
}
private void setupCurrentWallpaperPreview(View view) {
showCurrentWallpaper(view, /* show= */ true);
mHomePreview.setOnTouchListener((v, ev) -> {
if (mWallpaperConnection != null && mWallpaperConnection.getEngine() != null) {
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
mBottomActionBar.expandBottomSheet(false);
}
MotionEvent dup = MotionEvent.obtainNoHistory(ev);
try {
mWallpaperConnection.getEngine().dispatchPointer(dup);
if (action == MotionEvent.ACTION_UP) {
mWallpaperConnection.getEngine().dispatchWallpaperCommand(
WallpaperManager.COMMAND_TAP,
(int) ev.getX(), (int) ev.getY(), 0, null);
} else if (action == MotionEvent.ACTION_POINTER_UP) {
int pointerIndex = ev.getActionIndex();
mWallpaperConnection.getEngine().dispatchWallpaperCommand(
WallpaperManager.COMMAND_SECONDARY_TAP,
(int) ev.getX(pointerIndex), (int) ev.getY(pointerIndex), 0, null);
}
} catch (RemoteException e) {
Log.e(TAG, "Remote exception of wallpaper connection");
}
}
return false;
});
}
private void showCurrentWallpaper(View rootView, boolean show) {
rootView.findViewById(R.id.live_wallpaper_preview)
.setVisibility(show ? View.VISIBLE : View.GONE);
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (mSettingsLiveData != null && mSettingsLiveData.hasObservers()) {
mSettingsLiveData.removeObserver(mSettingsSliceView);
mSettingsLiveData = null;
}
if (mWallpaperConnection != null) {
mWallpaperConnection.disconnect();
}
mWallpaperConnection = null;
mWorkspaceSurfaceCallback.cleanUp();
super.onDestroy();
}
private void previewLiveWallpaper(ViewGroup container, ImageView thumbnailView) {
container.post(() -> {
mWallpaper.getThumbAsset(requireActivity().getApplicationContext()).loadPreviewImage(
requireActivity(), thumbnailView,
getResources().getColor(R.color.secondary_color));
LiveTileOverlay.INSTANCE.detach(thumbnailView.getOverlay());
setUpLiveWallpaperPreview(mWallpaper, thumbnailView,
new ColorDrawable(getResources().getColor(
R.color.secondary_color, getActivity().getTheme())));
});
}
private void setUpLiveWallpaperPreview(com.android.wallpaper.model.WallpaperInfo homeWallpaper,
ImageView previewView, Drawable thumbnail) {
Activity activity = getActivity();
if (activity == null) {
return;
}
if (mWallpaperConnection != null) {
mWallpaperConnection.disconnect();
}
if (thumbnail != null) {
thumbnail.setBounds(previewView.getLeft(), previewView.getTop(), previewView.getRight(),
previewView.getBottom());
}
repositionPreview(previewView);
mWallpaperConnection = new WallpaperConnection(
getWallpaperIntent(homeWallpaper.getWallpaperComponent()), activity,
new WallpaperConnection.WallpaperConnectionListener() {
@Override
public void onEngineShown() {
mLoadingScrim.post(() -> mLoadingScrim.animate()
.alpha(0f)
.setDuration(220)
.setStartDelay(300)
.setInterpolator(AnimationUtils.loadInterpolator(getActivity(),
android.R.interpolator.fast_out_linear_in))
.withEndAction(() -> {
if (mLoadingProgressBar != null) {
mLoadingProgressBar.hide();
}
mLoadingScrim.setVisibility(View.GONE);
if (mWallpaperInfoView != null) {
mWallpaperInfoView.populateWallpaperInfo(
mWallpaper,
mActionLabel,
mExploreIntent,
LivePreviewFragment.this::onExploreClicked);
}
}));
final Drawable placeholder = previewView.getDrawable() == null
? new ColorDrawable(getResources().getColor(R.color.secondary_color,
activity.getTheme()))
: previewView.getDrawable();
LiveTileOverlay.INSTANCE.setForegroundDrawable(placeholder);
LiveTileOverlay.INSTANCE.attach(previewView.getOverlay());
previewView.animate()
.setStartDelay(50)
.setDuration(200)
.setInterpolator(AnimationUtils.loadInterpolator(getContext(),
android.R.interpolator.fast_out_linear_in))
.setUpdateListener(value -> placeholder.setAlpha(
(int) (255 * (1 - value.getAnimatedFraction()))))
.withEndAction(() -> {
LiveTileOverlay.INSTANCE.setForegroundDrawable(null);
}).start();
}
}, 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());
}
});
}
private void renderWorkspaceSurface() {
mWorkspaceSurface.setZOrderMediaOverlay(true);
mWorkspaceSurface.getHolder().addCallback(mWorkspaceSurfaceCallback);
}
protected void onBottomActionBarReady(BottomActionBar bottomActionBar) {
super.onBottomActionBarReady(bottomActionBar);
mBottomActionBar.showActionsOnly(INFORMATION, DELETE, CUSTOMIZE, APPLY);
mBottomActionBar.bindBackButtonToSystemBackKey(getActivity());
mBottomActionBar.setActionClickListener(APPLY, unused ->
this.onSetWallpaperClicked(null));
mWallpaperInfoView = (WallpaperInfoView) LayoutInflater.from(getContext())
.inflate(R.layout.wallpaper_info_view, /* root= */ null);
mBottomActionBar.attachViewToBottomSheetAndBindAction(mWallpaperInfoView, INFORMATION);
final Uri uriSettingsSlice = getSettingsSliceUri(mWallpaper.getWallpaperComponent());
if (uriSettingsSlice != null) {
View previewPage = LayoutInflater.from(getContext())
.inflate(R.layout.preview_customize_settings, null);
mSettingsSliceView = previewPage.findViewById(R.id.settings_slice);
mSettingsSliceView.setMode(SliceView.MODE_LARGE);
mSettingsSliceView.setScrollable(false);
mSettingsLiveData = SliceLiveData.fromUri(requireContext(), uriSettingsSlice);
mSettingsLiveData.observeForever(mSettingsSliceView);
mBottomActionBar.attachViewToBottomSheetAndBindAction(previewPage, CUSTOMIZE);
} else {
if (mSettingsIntent != null) {
mBottomActionBar.setActionClickListener(CUSTOMIZE, listener ->
startActivity(mSettingsIntent));
} else {
mBottomActionBar.hideActions(CUSTOMIZE);
}
}
final String deleteAction = getDeleteAction(mWallpaper.getWallpaperComponent());
if (TextUtils.isEmpty(deleteAction)) {
mBottomActionBar.hideActions(DELETE);
} else {
mBottomActionBar.setActionClickListener(DELETE, listener ->
showDeleteConfirmDialog());
}
mBottomActionBar.show();
}
@Override
public void onEngineShown() {
mLoadingScrim.post(() -> mLoadingScrim.animate()
.alpha(0f)
.setDuration(220)
.setStartDelay(300)
.setInterpolator(AnimationUtils.loadInterpolator(getActivity(),
android.R.interpolator.fast_out_linear_in))
.withEndAction(() -> {
if (mLoadingProgressBar != null) {
mLoadingProgressBar.hide();
}
mLoadingScrim.setVisibility(View.INVISIBLE);
populateInfoPage(mInfoPageController);
}));
}
@Override
protected boolean isLoaded() {
return mWallpaperConnection != null && mWallpaperConnection.isEngineReady();
}
@Override
protected CharSequence getExploreButtonLabel(Context context) {
CharSequence exploreLabel = ((LiveWallpaperInfo) mWallpaper).getActionDescription(context);
if (TextUtils.isEmpty(exploreLabel)) {
exploreLabel = context.getString(mWallpaper.getActionLabelRes(context));
}
return exploreLabel;
}
@SuppressLint("NewApi") //Already checking with isAtLeastQ
protected Uri getSettingsSliceUri(android.app.WallpaperInfo info) {
if (BuildCompat.isAtLeastQ()) {
return info.getSettingsSliceUri();
}
return null;
}
@Override
protected int getLayoutResId() {
return R.layout.fragment_live_preview_v2;
}
@Override
protected int getLoadingIndicatorResId() {
return R.id.loading_indicator;
}
@Override
protected void setCurrentWallpaper(int destination) {
mWallpaperSetter.setCurrentWallpaper(getActivity(), mWallpaper, null,
destination, 0, null, new SetWallpaperCallback() {
@Override
public void onSuccess(com.android.wallpaper.model.WallpaperInfo wallpaperInfo) {
finishActivityWithResultOk();
}
@Override
public void onError(@Nullable Throwable throwable) {
showSetWallpaperErrorDialog(destination);
}
});
}
@Nullable
protected String getDeleteAction(android.app.WallpaperInfo wallpaperInfo) {
android.app.WallpaperInfo currentInfo =
WallpaperManager.getInstance(requireContext()).getWallpaperInfo();
ServiceInfo serviceInfo = wallpaperInfo.getServiceInfo();
if (!isPackagePreInstalled(serviceInfo.applicationInfo)) {
Log.d(TAG, "This wallpaper is not pre-installed: " + serviceInfo.name);
return null;
}
ServiceInfo currentService = currentInfo == null ? null : currentInfo.getServiceInfo();
// 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);
}
}
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;
}
}