blob: 62e15b9d8bfc61fca3d9dcc65f6e3d9f8d4faf54 [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 android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
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.EDIT;
import static com.android.wallpaper.widget.BottomActionBar.BottomAction.INFORMATION;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.WallpaperColors;
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.Color;
import android.graphics.Point;
import android.net.Uri;
import android.os.Build;
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.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.cardview.widget.CardView;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
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.model.SetWallpaperViewModel;
import com.android.wallpaper.model.WallpaperInfo.ColorInfo;
import com.android.wallpaper.module.LargeScreenMultiPanesChecker;
import com.android.wallpaper.util.FullScreenAnimation;
import com.android.wallpaper.util.ResourceUtils;
import com.android.wallpaper.util.ScreenSizeCalculator;
import com.android.wallpaper.util.SizeCalculator;
import com.android.wallpaper.util.WallpaperConnection;
import com.android.wallpaper.util.WallpaperSurfaceCallback;
import com.android.wallpaper.widget.BottomActionBar;
import com.android.wallpaper.widget.BottomActionBar.AccessibilityCallback;
import com.android.wallpaper.widget.BottomActionBar.BottomSheetContent;
import com.android.wallpaper.widget.LockScreenPreviewer;
import com.android.wallpaper.widget.WallpaperColorsLoader;
import java.util.Locale;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* 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;
protected CardView mHomePreviewCard;
protected SurfaceView mWorkspaceSurface;
protected WallpaperSurfaceCallback mWallpaperSurfaceCallback;
protected WorkspaceSurfaceHolderCallback mWorkspaceSurfaceCallback;
protected ViewGroup mLockPreviewContainer;
protected LockScreenPreviewer mLockScreenPreviewer;
private Intent mDeleteIntent;
private Intent mSettingsIntent;
private SliceView mSettingsSliceView;
private LiveData<Slice> mSettingsLiveData;
private Point mScreenSize;
private ViewGroup mPreviewContainer;
private TouchForwardingLayout mTouchForwardingLayout;
private SurfaceView mWallpaperSurface;
private Future<ColorInfo> mColorFuture;
private WallpaperColors mWallpaperColors;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
android.app.WallpaperInfo info = mWallpaper.getWallpaperComponent();
mColorFuture = mWallpaper.computeColorInfo(getContext());
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) {
View view = super.onCreateView(inflater, container, savedInstanceState);
Activity activity = requireActivity();
mScreenSize = ScreenSizeCalculator.getInstance().getScreenSize(
activity.getWindowManager().getDefaultDisplay());
mPreviewContainer = view.findViewById(R.id.container);
mTouchForwardingLayout = view.findViewById(R.id.touch_forwarding_layout);
// Update preview header color which covers toolbar and status bar area.
updatePreviewHeader(view);
// Set aspect ratio on the preview card.
ConstraintSet set = new ConstraintSet();
set.clone((ConstraintLayout) mPreviewContainer);
String ratio = String.format(Locale.US, "%d:%d", mScreenSize.x, mScreenSize.y);
set.setDimensionRatio(mTouchForwardingLayout.getId(), ratio);
set.applyTo((ConstraintLayout) mPreviewContainer);
mHomePreviewCard = mPreviewContainer.findViewById(R.id.wallpaper_full_preview_card);
mLockPreviewContainer = mPreviewContainer.findViewById(R.id.lock_screen_preview_container);
mLockScreenPreviewer = new LockScreenPreviewer(getLifecycle(), getContext(),
mLockPreviewContainer);
mLockScreenPreviewer.setDateViewVisibility(!mFullScreenAnimation.isFullScreen());
mFullScreenAnimation.setFullScreenStatusListener(
isFullScreen -> {
mLockScreenPreviewer.setDateViewVisibility(!isFullScreen);
if (!isFullScreen) {
mBottomActionBar.focusAccessibilityAction(EDIT);
}
});
mWallpaperSurface = mHomePreviewCard.findViewById(R.id.wallpaper_surface);
mTouchForwardingLayout.setTargetView(mHomePreviewCard);
mTouchForwardingLayout.setForwardingEnabled(true);
mWorkspaceSurface = mHomePreviewCard.findViewById(R.id.workspace_surface);
mWorkspaceSurfaceCallback = createWorkspaceSurfaceCallback(mWorkspaceSurface);
mWallpaperSurfaceCallback = new WallpaperSurfaceCallback(getContext(), mHomePreviewCard,
mWallpaperSurface, mColorFuture,
new WallpaperSurfaceCallback.SurfaceListener() {
@Override
public void onSurfaceCreated() {
previewLiveWallpaper(null);
}
});
setUpTabs(view.findViewById(R.id.separated_tabs));
view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View thisView, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
mHomePreviewCard.setRadius(SizeCalculator.getPreviewCornerRadius(activity,
mHomePreviewCard.getMeasuredWidth()));
view.removeOnLayoutChangeListener(this);
}
});
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
updateWallpaperSurface();
setupCurrentWallpaperPreview();
renderWorkspaceSurface();
}
private void updateWallpaperSurface() {
mWallpaperSurface.getHolder().addCallback(mWallpaperSurfaceCallback);
mWallpaperSurface.setZOrderMediaOverlay(true);
}
@Override
protected void updateScreenPreview(boolean isHomeSelected) {
mWorkspaceSurface.setVisibility(isHomeSelected ? View.VISIBLE : View.INVISIBLE);
mLockPreviewContainer.setVisibility(isHomeSelected ? View.INVISIBLE : View.VISIBLE);
mFullScreenAnimation.setIsHomeSelected(isHomeSelected);
}
private void setupCurrentWallpaperPreview() {
mHomePreviewCard.setOnTouchListener((v, ev) -> {
if (mWallpaperConnection != null && mWallpaperConnection.getEngine() != null) {
float scaleRatio =
(float) mTouchForwardingLayout.getWidth() / (float) mScreenSize.x;
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
mBottomActionBar.collapseBottomSheetIfExpanded();
}
MotionEvent dup = MotionEvent.obtainNoHistory(ev);
dup.setLocation(ev.getX() / scaleRatio, ev.getY() / scaleRatio);
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;
});
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (mSettingsLiveData != null && mSettingsLiveData.hasObservers()
&& mSettingsSliceView != null) {
mSettingsLiveData.removeObserver(mSettingsSliceView);
mSettingsLiveData = null;
}
if (mWallpaperConnection != null) {
mWallpaperConnection.disconnect();
mWallpaperConnection = null;
}
if (mLockScreenPreviewer != null) {
mLockScreenPreviewer.release();
}
mWorkspaceSurfaceCallback.cleanUp();
mWorkspaceSurface.getHolder().removeCallback(mWorkspaceSurfaceCallback);
mWallpaperSurfaceCallback.cleanUp();
mWallpaperSurface.getHolder().removeCallback(mWallpaperSurfaceCallback);
}
protected void previewLiveWallpaper(ImageView thumbnailView) {
mWallpaperSurface.post(() -> {
Activity activity = getActivity();
if (activity == null) {
return;
}
if (mWallpaperSurfaceCallback.getHomeImageWallpaper() != null) {
mWallpaperSurfaceCallback.setHomeImageWallpaperBlur(true);
ColorInfo colorInfo = getColorInfo();
Integer placeholderColor = colorInfo.getPlaceholderColor();
mWallpaper.getThumbAsset(activity.getApplicationContext())
.loadLowResDrawable(activity,
mWallpaperSurfaceCallback.getHomeImageWallpaper(),
placeholderColor != null
? placeholderColor
: ResourceUtils.getColorAttr(activity,
android.R.attr.colorBackground),
mPreviewBitmapTransformation);
}
setUpLiveWallpaperPreview(mWallpaper);
});
}
private ColorInfo getColorInfo() {
ColorInfo colorInfo = null;
try {
colorInfo = mColorFuture.get(50, TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
Log.i(TAG, "Couldn't obtain placeholder color", e);
}
if (colorInfo == null) {
colorInfo = new ColorInfo(new WallpaperColors(Color.valueOf(Color.TRANSPARENT),
/* secondaryColor= */ null, /* tertiaryColor= */ null),
/* placeholderColor= */ null);
}
return colorInfo;
}
protected void setUpLiveWallpaperPreview(
com.android.wallpaper.model.WallpaperInfo homeWallpaper) {
Activity activity = getActivity();
if (activity == null || activity.isFinishing()) {
return;
}
if (mWallpaperConnection != null) {
mWallpaperConnection.disconnect();
}
if (WallpaperConnection.isPreviewAvailable()) {
mWallpaperConnection = new WallpaperConnection(
getWallpaperIntent(homeWallpaper.getWallpaperComponent()),
activity,
/* listener= */ this,
mWallpaperSurface);
mWallpaperConnection.setVisibility(true);
} else {
WallpaperColorsLoader.getWallpaperColors(
activity,
homeWallpaper.getThumbAsset(activity),
colors -> onWallpaperColorsChanged(colors, 0));
}
if (mWallpaperConnection != null && !mWallpaperConnection.connect()) {
mWallpaperConnection = null;
}
}
private void renderWorkspaceSurface() {
mWorkspaceSurface.setZOrderMediaOverlay(true);
mWorkspaceSurface.getHolder().addCallback(mWorkspaceSurfaceCallback);
}
@Override
protected void onBottomActionBarReady(BottomActionBar bottomActionBar) {
super.onBottomActionBarReady(bottomActionBar);
Activity activity = getActivity();
LargeScreenMultiPanesChecker checker = new LargeScreenMultiPanesChecker();
if (activity != null
&& (activity.isInMultiWindowMode() || checker.isMultiPanesEnabled(getContext()))) {
mBottomActionBar.showActionsOnly(INFORMATION, DELETE, CUSTOMIZE, APPLY);
} else {
mBottomActionBar.showActionsOnly(INFORMATION, DELETE, EDIT, CUSTOMIZE, APPLY);
}
mBottomActionBar.setActionClickListener(APPLY,
unused -> onSetWallpaperClicked(null, mWallpaper));
mBottomActionBar.bindBottomSheetContentWithAction(
new WallpaperInfoContent(getContext()), INFORMATION);
View separatedTabsContainer = getView().findViewById(R.id.separated_tabs_container);
// Update target view's accessibility param since it will be blocked by the bottom sheet
// when expanded.
mBottomActionBar.setAccessibilityCallback(new AccessibilityCallback() {
@Override
public void onBottomSheetCollapsed() {
mPreviewContainer.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
separatedTabsContainer.setImportantForAccessibility(
IMPORTANT_FOR_ACCESSIBILITY_YES);
}
@Override
public void onBottomSheetExpanded() {
mPreviewContainer.setImportantForAccessibility(
IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
separatedTabsContainer.setImportantForAccessibility(
IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
}
});
final Uri uriSettingsSlice = getSettingsSliceUri(mWallpaper.getWallpaperComponent());
if (uriSettingsSlice != null) {
mSettingsLiveData = SliceLiveData.fromUri(requireContext(), uriSettingsSlice);
mBottomActionBar.bindBottomSheetContentWithAction(
new PreviewCustomizeSettingsContent(getContext()), CUSTOMIZE);
} else {
if (mSettingsIntent != null) {
mBottomActionBar.setActionClickListener(CUSTOMIZE, listener ->
startActivity(mSettingsIntent));
} else {
mBottomActionBar.hideActions(CUSTOMIZE);
}
}
if (TextUtils.isEmpty(getDeleteAction(mWallpaper.getWallpaperComponent()))) {
mBottomActionBar.hideActions(DELETE);
} else {
mBottomActionBar.setActionClickListener(DELETE, listener ->
showDeleteConfirmDialog());
}
mBottomActionBar.show();
// Action buttons are disabled when live wallpaper is not loaded.
mBottomActionBar.disableActions();
// Enable buttons if loaded, or wait for it.
if (isLoaded()) {
mBottomActionBar.enableActions();
}
}
@Override
public void onEngineShown() {
Activity activity = getActivity();
if (activity == null) {
return;
}
mWallpaperSurfaceCallback.getHomeImageWallpaper().animate()
.setStartDelay(250)
.setDuration(250)
.alpha(0f)
.setInterpolator(ALPHA_OUT)
.start();
if (mBottomActionBar != null) {
mBottomActionBar.enableActions();
}
}
@Override
public void onWallpaperColorsChanged(WallpaperColors colors, int displayId) {
mWallpaperColors = colors;
mLockScreenPreviewer.setColor(colors);
mFullScreenAnimation.setFullScreenTextColor(
colors == null || (colors.getColorHints()
& WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0
? FullScreenAnimation.FullScreenTextColor.LIGHT
: FullScreenAnimation.FullScreenTextColor.DARK);
}
@Override
protected boolean isLoaded() {
return mWallpaperConnection != null && mWallpaperConnection.isEngineReady();
}
@SuppressLint("NewApi") //Already checking with isAtLeastQ
protected Uri getSettingsSliceUri(android.app.WallpaperInfo info) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return info.getSettingsSliceUri();
}
return null;
}
@Override
protected int getLayoutResId() {
return R.layout.fragment_live_preview;
}
@Override
protected void setCurrentWallpaper(int destination) {
mWallpaperSetter.setCurrentWallpaper(getActivity(), mWallpaper, null,
destination, 0, null,
mWallpaperColors != null ? mWallpaperColors : getColorInfo().getWallpaperColors(),
SetWallpaperViewModel.getCallback(mViewModelProvider));
}
@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);
}
}
@Override
public void onStop() {
super.onStop();
if (mWallpaperConnection != null) {
mWallpaperConnection.disconnect();
mWallpaperConnection = null;
}
}
private void showDeleteConfirmDialog() {
final AlertDialog alertDialog = new AlertDialog.Builder(getContext())
.setMessage(R.string.delete_wallpaper_confirmation)
.setOnDismissListener(dialog -> mBottomActionBar.deselectAction(DELETE))
.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);
finishActivity(/* success= */ false);
}
}
private boolean isPackagePreInstalled(ApplicationInfo info) {
return info != null && (info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
}
private final class PreviewCustomizeSettingsContent extends BottomSheetContent<View> {
private PreviewCustomizeSettingsContent(Context context) {
super(context);
}
@Override
public int getViewId() {
return R.layout.preview_customize_settings;
}
@Override
public void onViewCreated(View previewPage) {
mSettingsSliceView = previewPage.findViewById(R.id.settings_slice);
mSettingsSliceView.setMode(SliceView.MODE_LARGE);
mSettingsSliceView.setScrollable(false);
if (mSettingsLiveData != null) {
mSettingsLiveData.observeForever(mSettingsSliceView);
}
}
@Override
public void onRecreateView(View oldPreviewPage) {
super.onRecreateView(oldPreviewPage);
if (mSettingsLiveData != null && mSettingsLiveData.hasObservers()
&& mSettingsSliceView != null) {
mSettingsLiveData.removeObserver(mSettingsSliceView);
}
}
}
}