Merge changes I65d2b1bc,I35bf9cb4 into sc-dev
* changes: Animate App Widget activity launch from Quickstep launchers Add a mechanism to temporarily defer App Widget updates
This commit is contained in:
committed by
Android (Google) Code Review
commit
5f35ff021d
@@ -79,6 +79,7 @@ import com.android.launcher3.util.MultiValueAlpha;
|
||||
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
|
||||
import com.android.launcher3.util.RunnableList;
|
||||
import com.android.launcher3.views.FloatingIconView;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetHostView;
|
||||
import com.android.quickstep.RemoteAnimationTargets;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.TaskViewUtils;
|
||||
@@ -86,6 +87,7 @@ import com.android.quickstep.util.MultiValueUpdateListener;
|
||||
import com.android.quickstep.util.RemoteAnimationProvider;
|
||||
import com.android.quickstep.util.StaggeredWorkspaceAnim;
|
||||
import com.android.quickstep.util.SurfaceTransactionApplier;
|
||||
import com.android.quickstep.views.FloatingWidgetView;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.systemui.shared.system.ActivityCompat;
|
||||
import com.android.systemui.shared.system.ActivityOptionsCompat;
|
||||
@@ -160,6 +162,9 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
|
||||
private static final int MAX_NUM_TASKS = 5;
|
||||
|
||||
// Cross-fade duration between App Widget and App
|
||||
private static final int WIDGET_CROSSFADE_DURATION_MILLIS = 125;
|
||||
|
||||
protected final BaseQuickstepLauncher mLauncher;
|
||||
|
||||
private final DragLayer mDragLayer;
|
||||
@@ -349,6 +354,29 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
}
|
||||
}
|
||||
|
||||
private void composeWidgetLaunchAnimator(
|
||||
@NonNull AnimatorSet anim,
|
||||
@NonNull LauncherAppWidgetHostView v,
|
||||
@NonNull RemoteAnimationTargetCompat[] appTargets,
|
||||
@NonNull RemoteAnimationTargetCompat[] wallpaperTargets,
|
||||
@NonNull RemoteAnimationTargetCompat[] nonAppTargets) {
|
||||
mLauncher.getStateManager().setCurrentAnimation(anim);
|
||||
|
||||
Rect windowTargetBounds = getWindowTargetBounds(appTargets, getRotationChange(appTargets));
|
||||
anim.play(getOpeningWindowAnimatorsForWidget(v, appTargets, wallpaperTargets, nonAppTargets,
|
||||
windowTargetBounds, areAllTargetsTranslucent(appTargets)));
|
||||
|
||||
anim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
mLauncher.addOnResumeCallback(() ->
|
||||
ObjectAnimator.ofFloat(mLauncher.getDepthController(), DEPTH,
|
||||
mLauncher.getStateManager().getState().getDepth(
|
||||
mLauncher)).start());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the window bounds of the opening target.
|
||||
* In multiwindow mode, we need to get the final size of the opening app window target to help
|
||||
@@ -737,6 +765,112 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
}
|
||||
});
|
||||
|
||||
animatorSet.playTogether(appAnimator, getBackgroundAnimator(appTargets));
|
||||
return animatorSet;
|
||||
}
|
||||
|
||||
private Animator getOpeningWindowAnimatorsForWidget(LauncherAppWidgetHostView v,
|
||||
RemoteAnimationTargetCompat[] appTargets,
|
||||
RemoteAnimationTargetCompat[] wallpaperTargets,
|
||||
RemoteAnimationTargetCompat[] nonAppTargets, Rect windowTargetBounds,
|
||||
boolean appTargetsAreTranslucent) {
|
||||
final RectF widgetBackgroundBounds = new RectF();
|
||||
final Rect appWindowCrop = new Rect();
|
||||
final Matrix matrix = new Matrix();
|
||||
|
||||
final float finalWindowRadius = mDeviceProfile.isMultiWindowMode
|
||||
? 0 : getWindowCornerRadius(mLauncher.getResources());
|
||||
final FloatingWidgetView floatingView = FloatingWidgetView.getFloatingWidgetView(mLauncher,
|
||||
v, widgetBackgroundBounds, windowTargetBounds, finalWindowRadius);
|
||||
final float initialWindowRadius = supportsRoundedCornersOnWindows(mLauncher.getResources())
|
||||
? floatingView.getInitialCornerRadius() : 0;
|
||||
|
||||
RemoteAnimationTargets openingTargets = new RemoteAnimationTargets(appTargets,
|
||||
wallpaperTargets, nonAppTargets, MODE_OPENING);
|
||||
SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(floatingView);
|
||||
openingTargets.addReleaseCheck(surfaceApplier);
|
||||
|
||||
AnimatorSet animatorSet = new AnimatorSet();
|
||||
ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
|
||||
appAnimator.setDuration(APP_LAUNCH_DURATION);
|
||||
appAnimator.setInterpolator(LINEAR);
|
||||
appAnimator.addListener(floatingView);
|
||||
appAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
openingTargets.release();
|
||||
}
|
||||
});
|
||||
floatingView.setFastFinishRunnable(animatorSet::end);
|
||||
|
||||
appAnimator.addUpdateListener(new MultiValueUpdateListener() {
|
||||
float mAppWindowScale = 1;
|
||||
final FloatProp mWidgetForegroundAlpha = new FloatProp(1 /* start */,
|
||||
0 /* end */, 0 /* delay */,
|
||||
WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* duration */, LINEAR);
|
||||
final FloatProp mWidgetFallbackBackgroundAlpha = new FloatProp(0 /* start */,
|
||||
1 /* end */, 0 /* delay */, 75 /* duration */, LINEAR);
|
||||
final FloatProp mPreviewAlpha = new FloatProp(0 /* start */, 1 /* end */,
|
||||
WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* delay */,
|
||||
WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* duration */, LINEAR);
|
||||
final FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius,
|
||||
0 /* start */, RADIUS_DURATION, LINEAR);
|
||||
final FloatProp mCornerRadiusProgress = new FloatProp(0, 1, 0, RADIUS_DURATION, LINEAR);
|
||||
|
||||
// Window & widget background positioning bounds
|
||||
final FloatProp mDx = new FloatProp(widgetBackgroundBounds.centerX(),
|
||||
windowTargetBounds.centerX(), 0 /* delay */, APP_LAUNCH_CURVED_DURATION,
|
||||
EXAGGERATED_EASE);
|
||||
final FloatProp mDy = new FloatProp(widgetBackgroundBounds.centerY(),
|
||||
windowTargetBounds.centerY(), 0 /* delay */, APP_LAUNCH_DURATION,
|
||||
EXAGGERATED_EASE);
|
||||
final FloatProp mWidth = new FloatProp(widgetBackgroundBounds.width(),
|
||||
windowTargetBounds.width(), 0 /* delay */, APP_LAUNCH_DURATION,
|
||||
EXAGGERATED_EASE);
|
||||
final FloatProp mHeight = new FloatProp(widgetBackgroundBounds.height(),
|
||||
windowTargetBounds.height(), 0 /* delay */, APP_LAUNCH_DURATION,
|
||||
EXAGGERATED_EASE);
|
||||
|
||||
@Override
|
||||
public void onUpdate(float percent) {
|
||||
widgetBackgroundBounds.set(mDx.value - mWidth.value / 2f,
|
||||
mDy.value - mHeight.value / 2f, mDx.value + mWidth.value / 2f,
|
||||
mDy.value + mHeight.value / 2f);
|
||||
// Set app window scaling factor to match widget background width
|
||||
mAppWindowScale = widgetBackgroundBounds.width() / windowTargetBounds.width();
|
||||
// Crop scaled app window to match widget
|
||||
appWindowCrop.set(0 /* left */, 0 /* top */,
|
||||
Math.round(windowTargetBounds.width()) /* right */,
|
||||
Math.round(widgetBackgroundBounds.height() / mAppWindowScale) /* bottom */);
|
||||
matrix.setTranslate(widgetBackgroundBounds.left, widgetBackgroundBounds.top);
|
||||
matrix.postScale(mAppWindowScale, mAppWindowScale, widgetBackgroundBounds.left,
|
||||
widgetBackgroundBounds.top);
|
||||
|
||||
SurfaceParams[] params = new SurfaceParams[appTargets.length];
|
||||
float floatingViewAlpha = appTargetsAreTranslucent ? 1 - mPreviewAlpha.value : 1;
|
||||
for (int i = appTargets.length - 1; i >= 0; i--) {
|
||||
RemoteAnimationTargetCompat target = appTargets[i];
|
||||
SurfaceParams.Builder builder = new SurfaceParams.Builder(target.leash);
|
||||
if (target.mode == MODE_OPENING) {
|
||||
floatingView.update(widgetBackgroundBounds, floatingViewAlpha,
|
||||
mWidgetForegroundAlpha.value, mWidgetFallbackBackgroundAlpha.value,
|
||||
mCornerRadiusProgress.value);
|
||||
builder.withMatrix(matrix)
|
||||
.withWindowCrop(appWindowCrop)
|
||||
.withAlpha(mPreviewAlpha.value)
|
||||
.withCornerRadius(mWindowRadius.value / mAppWindowScale);
|
||||
}
|
||||
params[i] = builder.build();
|
||||
}
|
||||
surfaceApplier.scheduleApply(params);
|
||||
}
|
||||
});
|
||||
|
||||
animatorSet.playTogether(appAnimator, getBackgroundAnimator(appTargets));
|
||||
return animatorSet;
|
||||
}
|
||||
|
||||
private ObjectAnimator getBackgroundAnimator(RemoteAnimationTargetCompat[] appTargets) {
|
||||
// When launching an app from overview that doesn't map to a task, we still want to just
|
||||
// blur the wallpaper instead of the launcher surface as well
|
||||
boolean allowBlurringLauncher = mLauncher.getStateManager().getState() != OVERVIEW;
|
||||
@@ -754,9 +888,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
animatorSet.playTogether(appAnimator, backgroundRadiusAnim);
|
||||
return animatorSet;
|
||||
return backgroundRadiusAnim;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1120,9 +1252,13 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
boolean launcherClosing =
|
||||
launcherIsATargetWithMode(appTargets, MODE_CLOSING);
|
||||
|
||||
final boolean launchingFromWidget = mV instanceof LauncherAppWidgetHostView;
|
||||
final boolean launchingFromRecents = isLaunchingFromRecents(mV, appTargets);
|
||||
final boolean launchingFromTaskbar = mLauncher.isViewInTaskbar(mV);
|
||||
if (launchingFromRecents) {
|
||||
if (launchingFromWidget) {
|
||||
composeWidgetLaunchAnimator(anim, (LauncherAppWidgetHostView) mV, appTargets,
|
||||
wallpaperTargets, nonAppTargets);
|
||||
} else if (launchingFromRecents) {
|
||||
composeRecentsLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets,
|
||||
launcherClosing);
|
||||
} else if (launchingFromTaskbar) {
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.quickstep.views;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.widget.RemoteViews.RemoteViewOutlineProvider;
|
||||
|
||||
import com.android.launcher3.util.Themes;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetHostView;
|
||||
import com.android.launcher3.widget.RoundedCornerEnforcement;
|
||||
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* Mimics the appearance of the background view of a {@link LauncherAppWidgetHostView} through a
|
||||
* an App Widget activity launch animation.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.S)
|
||||
final class FloatingWidgetBackgroundView extends View {
|
||||
private final ColorDrawable mFallbackDrawable = new ColorDrawable();
|
||||
private final DrawableProperties mForegroundProperties = new DrawableProperties();
|
||||
private final DrawableProperties mBackgroundProperties = new DrawableProperties();
|
||||
|
||||
private Drawable mOriginalForeground;
|
||||
private Drawable mOriginalBackground;
|
||||
private float mFinalRadius;
|
||||
private float mInitialOutlineRadius;
|
||||
private float mOutlineRadius;
|
||||
private boolean mIsUsingFallback;
|
||||
private View mSourceView;
|
||||
|
||||
FloatingWidgetBackgroundView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
setOutlineProvider(new ViewOutlineProvider() {
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline) {
|
||||
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mOutlineRadius);
|
||||
}
|
||||
});
|
||||
setClipToOutline(true);
|
||||
}
|
||||
|
||||
void init(LauncherAppWidgetHostView hostView, View backgroundView, float finalRadius) {
|
||||
mFinalRadius = finalRadius;
|
||||
mSourceView = backgroundView;
|
||||
mInitialOutlineRadius = getOutlineRadius(hostView, backgroundView);
|
||||
mIsUsingFallback = false;
|
||||
if (isSupportedDrawable(backgroundView.getForeground())) {
|
||||
mOriginalForeground = backgroundView.getForeground();
|
||||
mForegroundProperties.init(
|
||||
mOriginalForeground.getConstantState().newDrawable().mutate());
|
||||
setForeground(mForegroundProperties.mDrawable);
|
||||
mSourceView.setForeground(null);
|
||||
}
|
||||
if (isSupportedDrawable(backgroundView.getBackground())) {
|
||||
mOriginalBackground = backgroundView.getBackground();
|
||||
mBackgroundProperties.init(
|
||||
mOriginalBackground.getConstantState().newDrawable().mutate());
|
||||
setBackground(mBackgroundProperties.mDrawable);
|
||||
mSourceView.setBackground(null);
|
||||
} else if (mOriginalForeground == null) {
|
||||
mFallbackDrawable.setColor(Themes.getColorBackground(backgroundView.getContext()));
|
||||
setBackground(mFallbackDrawable);
|
||||
mIsUsingFallback = true;
|
||||
}
|
||||
}
|
||||
|
||||
/** Update the animated properties of the drawables. */
|
||||
void update(float cornerRadiusProgress, float fallbackAlpha) {
|
||||
if (isUninitialized()) return;
|
||||
mOutlineRadius = mInitialOutlineRadius + (mFinalRadius - mInitialOutlineRadius)
|
||||
* cornerRadiusProgress;
|
||||
mForegroundProperties.updateDrawable(mFinalRadius, cornerRadiusProgress);
|
||||
mBackgroundProperties.updateDrawable(mFinalRadius, cornerRadiusProgress);
|
||||
setAlpha(mIsUsingFallback ? fallbackAlpha : 1f);
|
||||
}
|
||||
|
||||
/** Restores the drawables to the source view. */
|
||||
void finish() {
|
||||
if (isUninitialized()) return;
|
||||
mSourceView.setForeground(mOriginalForeground);
|
||||
mSourceView.setBackground(mOriginalBackground);
|
||||
}
|
||||
|
||||
void recycle() {
|
||||
mSourceView = null;
|
||||
mOriginalForeground = null;
|
||||
mOriginalBackground = null;
|
||||
mOutlineRadius = 0;
|
||||
mFinalRadius = 0;
|
||||
setForeground(null);
|
||||
setBackground(null);
|
||||
}
|
||||
|
||||
/** Get the largest of drawable corner radii or background view outline radius. */
|
||||
float getMaximumRadius() {
|
||||
if (isUninitialized()) return 0;
|
||||
return Math.max(mInitialOutlineRadius, Math.max(getMaxRadius(mOriginalForeground),
|
||||
getMaxRadius(mOriginalBackground)));
|
||||
}
|
||||
|
||||
private boolean isUninitialized() {
|
||||
return mSourceView == null;
|
||||
}
|
||||
|
||||
/** Returns the maximum corner radius of {@param drawable}. */
|
||||
private static float getMaxRadius(Drawable drawable) {
|
||||
if (!(drawable instanceof GradientDrawable)) return 0;
|
||||
float[] cornerRadii = ((GradientDrawable) drawable).getCornerRadii();
|
||||
float cornerRadius = ((GradientDrawable) drawable).getCornerRadius();
|
||||
double radiiMax = cornerRadii == null ? 0 : IntStream.range(0, cornerRadii.length)
|
||||
.mapToDouble(i -> cornerRadii[i]).max().orElse(0);
|
||||
return Math.max(cornerRadius, (float) radiiMax);
|
||||
}
|
||||
|
||||
/** Returns whether the given drawable type is supported. */
|
||||
private static boolean isSupportedDrawable(Drawable drawable) {
|
||||
return drawable instanceof ColorDrawable || (drawable instanceof GradientDrawable
|
||||
&& ((GradientDrawable) drawable).getShape() == GradientDrawable.RECTANGLE);
|
||||
}
|
||||
|
||||
/** Corner radius from source view's outline, or enforced view. */
|
||||
private static float getOutlineRadius(LauncherAppWidgetHostView hostView, View v) {
|
||||
if (RoundedCornerEnforcement.isRoundedCornerEnabled()
|
||||
&& hostView.hasEnforcedCornerRadius()) {
|
||||
return hostView.getEnforcedCornerRadius();
|
||||
} else if (v.getOutlineProvider() instanceof RemoteViewOutlineProvider
|
||||
&& v.getClipToOutline()) {
|
||||
return ((RemoteViewOutlineProvider) v.getOutlineProvider()).getRadius();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Stores and modifies a drawable's properties through an animation. */
|
||||
private static class DrawableProperties {
|
||||
private Drawable mDrawable;
|
||||
private float mOriginalRadius;
|
||||
private float[] mOriginalRadii;
|
||||
private final float[] mTmpRadii = new float[8];
|
||||
|
||||
/** Store a drawable's animated properties. */
|
||||
void init(Drawable drawable) {
|
||||
mDrawable = drawable;
|
||||
if (!(drawable instanceof GradientDrawable)) return;
|
||||
mOriginalRadius = ((GradientDrawable) drawable).getCornerRadius();
|
||||
mOriginalRadii = ((GradientDrawable) drawable).getCornerRadii();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the drawable for the given animation state.
|
||||
*
|
||||
* @param finalRadius the radius of each corner when {@param progress} is 1
|
||||
* @param progress the linear progress of the corner radius from its original value to
|
||||
* {@param finalRadius}
|
||||
*/
|
||||
void updateDrawable(float finalRadius, float progress) {
|
||||
if (!(mDrawable instanceof GradientDrawable)) return;
|
||||
GradientDrawable d = (GradientDrawable) mDrawable;
|
||||
if (mOriginalRadii != null) {
|
||||
for (int i = 0; i < mOriginalRadii.length; i++) {
|
||||
mTmpRadii[i] = mOriginalRadii[i] + (finalRadius - mOriginalRadii[i]) * progress;
|
||||
}
|
||||
d.setCornerRadii(mTmpRadii);
|
||||
} else {
|
||||
d.setCornerRadius(mOriginalRadius + (finalRadius - mOriginalRadius) * progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.quickstep.views;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.Animator.AnimatorListener;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.GhostView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.dragndrop.DragLayer;
|
||||
import com.android.launcher3.views.ListenerView;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetHostView;
|
||||
import com.android.launcher3.widget.RoundedCornerEnforcement;
|
||||
|
||||
/** A view that mimics an App Widget through a launch animation. */
|
||||
@TargetApi(Build.VERSION_CODES.S)
|
||||
public class FloatingWidgetView extends FrameLayout implements AnimatorListener {
|
||||
private static final Matrix sTmpMatrix = new Matrix();
|
||||
|
||||
private final Launcher mLauncher;
|
||||
private final ListenerView mListenerView;
|
||||
private final FloatingWidgetBackgroundView mBackgroundView;
|
||||
private final RectF mBackgroundOffset = new RectF();
|
||||
|
||||
private LauncherAppWidgetHostView mAppWidgetView;
|
||||
private View mAppWidgetBackgroundView;
|
||||
private RectF mBackgroundPosition;
|
||||
private GhostView mForegroundOverlayView;
|
||||
|
||||
private Runnable mEndRunnable;
|
||||
private Runnable mFastFinishRunnable;
|
||||
|
||||
public FloatingWidgetView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public FloatingWidgetView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public FloatingWidgetView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
mLauncher = Launcher.getLauncher(context);
|
||||
mListenerView = new ListenerView(context, attrs);
|
||||
mBackgroundView = new FloatingWidgetBackgroundView(context, attrs, defStyleAttr);
|
||||
addView(mBackgroundView);
|
||||
setWillNotDraw(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animator) {
|
||||
Runnable endRunnable = mEndRunnable;
|
||||
mEndRunnable = null;
|
||||
if (endRunnable != null) {
|
||||
endRunnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(Animator animator) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animator) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animator animator) {
|
||||
}
|
||||
|
||||
/** Sets a runnable that is called after a call to {@link #fastFinish()}. */
|
||||
public void setFastFinishRunnable(Runnable runnable) {
|
||||
mFastFinishRunnable = runnable;
|
||||
}
|
||||
|
||||
/** Callback at the end or early exit of the animation. */
|
||||
public void fastFinish() {
|
||||
if (isUninitialized()) return;
|
||||
Runnable fastFinishRunnable = mFastFinishRunnable;
|
||||
if (fastFinishRunnable != null) {
|
||||
fastFinishRunnable.run();
|
||||
}
|
||||
Runnable endRunnable = mEndRunnable;
|
||||
mEndRunnable = null;
|
||||
if (endRunnable != null) {
|
||||
endRunnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
private void init(DragLayer dragLayer, LauncherAppWidgetHostView originalView,
|
||||
RectF widgetBackgroundPosition, Rect windowTargetBounds, float windowCornerRadius) {
|
||||
mAppWidgetView = originalView;
|
||||
mAppWidgetView.beginDeferringUpdates();
|
||||
mBackgroundPosition = widgetBackgroundPosition;
|
||||
mEndRunnable = () -> finish(dragLayer);
|
||||
|
||||
mAppWidgetBackgroundView = RoundedCornerEnforcement.findBackground(mAppWidgetView);
|
||||
if (mAppWidgetBackgroundView == null) {
|
||||
mAppWidgetBackgroundView = mAppWidgetView;
|
||||
}
|
||||
|
||||
getRelativePosition(mAppWidgetBackgroundView, dragLayer, mBackgroundPosition);
|
||||
getRelativePosition(mAppWidgetBackgroundView, mAppWidgetView, mBackgroundOffset);
|
||||
mBackgroundView.init(mAppWidgetView, mAppWidgetBackgroundView, windowCornerRadius);
|
||||
// Layout call before GhostView creation so that the overlaid view isn't clipped
|
||||
layout(0, 0, windowTargetBounds.width(), windowTargetBounds.height());
|
||||
mForegroundOverlayView = GhostView.addGhost(mAppWidgetView, this);
|
||||
positionViews();
|
||||
|
||||
mListenerView.setListener(this::fastFinish);
|
||||
dragLayer.addView(mListenerView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the position and opacity of the floating widget's components.
|
||||
*
|
||||
* @param backgroundPosition the new position of the widget's background relative to the
|
||||
* {@link FloatingWidgetView}'s parent
|
||||
* @param floatingWidgetAlpha the overall opacity of the {@link FloatingWidgetView}
|
||||
* @param foregroundAlpha the opacity of the foreground layer
|
||||
* @param fallbackBackgroundAlpha the opacity of the fallback background used when the App
|
||||
* Widget doesn't have a background
|
||||
* @param cornerRadiusProgress progress of the corner radius animation, where 0 is the
|
||||
* original radius and 1 is the window radius
|
||||
*/
|
||||
public void update(RectF backgroundPosition, float floatingWidgetAlpha, float foregroundAlpha,
|
||||
float fallbackBackgroundAlpha, float cornerRadiusProgress) {
|
||||
if (isUninitialized()) return;
|
||||
setAlpha(floatingWidgetAlpha);
|
||||
mBackgroundView.update(cornerRadiusProgress, fallbackBackgroundAlpha);
|
||||
mAppWidgetView.setAlpha(foregroundAlpha);
|
||||
mBackgroundPosition = backgroundPosition;
|
||||
positionViews();
|
||||
}
|
||||
|
||||
/** Sets the layout parameters of the floating view and its background view child. */
|
||||
private void positionViews() {
|
||||
LayoutParams layoutParams = (LayoutParams) getLayoutParams();
|
||||
layoutParams.setMargins(0, 0, 0, 0);
|
||||
setLayoutParams(layoutParams);
|
||||
|
||||
// FloatingWidgetView layout is forced LTR
|
||||
mBackgroundView.setTranslationX(mBackgroundPosition.left);
|
||||
mBackgroundView.setTranslationY(mBackgroundPosition.top);
|
||||
LayoutParams backgroundParams = (LayoutParams) mBackgroundView.getLayoutParams();
|
||||
backgroundParams.leftMargin = 0;
|
||||
backgroundParams.topMargin = 0;
|
||||
backgroundParams.width = (int) mBackgroundPosition.width();
|
||||
backgroundParams.height = (int) mBackgroundPosition.height();
|
||||
mBackgroundView.setLayoutParams(backgroundParams);
|
||||
|
||||
sTmpMatrix.reset();
|
||||
float foregroundScale = mBackgroundPosition.width() / mAppWidgetBackgroundView.getWidth();
|
||||
sTmpMatrix.setTranslate(-mBackgroundOffset.left - mAppWidgetView.getLeft(),
|
||||
-mBackgroundOffset.top - mAppWidgetView.getTop());
|
||||
sTmpMatrix.postScale(foregroundScale, foregroundScale);
|
||||
sTmpMatrix.postTranslate(mBackgroundPosition.left, mBackgroundPosition.top);
|
||||
mForegroundOverlayView.setMatrix(sTmpMatrix);
|
||||
}
|
||||
|
||||
private void finish(DragLayer dragLayer) {
|
||||
mAppWidgetView.setAlpha(1f);
|
||||
GhostView.removeGhost(mAppWidgetView);
|
||||
((ViewGroup) dragLayer.getParent()).removeView(this);
|
||||
dragLayer.removeView(mListenerView);
|
||||
mBackgroundView.finish();
|
||||
mAppWidgetView.endDeferringUpdates();
|
||||
recycle();
|
||||
mLauncher.getViewCache().recycleView(R.layout.floating_widget_view, this);
|
||||
}
|
||||
|
||||
public float getInitialCornerRadius() {
|
||||
return mBackgroundView.getMaximumRadius();
|
||||
}
|
||||
|
||||
private boolean isUninitialized() {
|
||||
return mForegroundOverlayView == null;
|
||||
}
|
||||
|
||||
private void recycle() {
|
||||
mEndRunnable = null;
|
||||
mFastFinishRunnable = null;
|
||||
mBackgroundPosition = null;
|
||||
mListenerView.setListener(null);
|
||||
mAppWidgetView = null;
|
||||
mForegroundOverlayView = null;
|
||||
mAppWidgetBackgroundView = null;
|
||||
mBackgroundView.recycle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures and returns a an instance of {@link FloatingWidgetView} matching the appearance of
|
||||
* {@param originalView}.
|
||||
*
|
||||
* @param widgetBackgroundPosition a {@link RectF} that will be updated with the widget's
|
||||
* background bounds
|
||||
* @param windowTargetBounds the bounds of the window when launched
|
||||
* @param windowCornerRadius the corner radius of the window
|
||||
*/
|
||||
public static FloatingWidgetView getFloatingWidgetView(Launcher launcher,
|
||||
LauncherAppWidgetHostView originalView, RectF widgetBackgroundPosition,
|
||||
Rect windowTargetBounds, float windowCornerRadius) {
|
||||
final DragLayer dragLayer = launcher.getDragLayer();
|
||||
ViewGroup parent = (ViewGroup) dragLayer.getParent();
|
||||
FloatingWidgetView floatingView =
|
||||
launcher.getViewCache().getView(R.layout.floating_widget_view, launcher, parent);
|
||||
floatingView.recycle();
|
||||
|
||||
floatingView.init(dragLayer, originalView, widgetBackgroundPosition, windowTargetBounds,
|
||||
windowCornerRadius);
|
||||
parent.addView(floatingView);
|
||||
return floatingView;
|
||||
}
|
||||
|
||||
private static void getRelativePosition(View descendant, View ancestor, RectF position) {
|
||||
float[] points = new float[]{0, 0, descendant.getWidth(), descendant.getHeight()};
|
||||
Utilities.getDescendantCoordRelativeToAncestor(descendant, ancestor, points,
|
||||
false /* includeRootScroll */);
|
||||
position.set(
|
||||
Math.min(points[0], points[2]),
|
||||
Math.min(points[1], points[3]),
|
||||
Math.max(points[0], points[2]),
|
||||
Math.max(points[1], points[3]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2021 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.
|
||||
-->
|
||||
<com.android.quickstep.views.FloatingWidgetView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layoutDirection="ltr" />
|
||||
@@ -72,6 +72,8 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
|
||||
|
||||
// Maintains a list of widget ids which are supposed to be auto advanced.
|
||||
private static final SparseBooleanArray sAutoAdvanceWidgetIds = new SparseBooleanArray();
|
||||
// Maximum duration for which updates can be deferred.
|
||||
private static final long UPDATE_LOCK_TIMEOUT_MILLIS = 1000;
|
||||
|
||||
protected final LayoutInflater mInflater;
|
||||
|
||||
@@ -110,6 +112,9 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
|
||||
}
|
||||
}
|
||||
};
|
||||
private final Object mUpdateLock = new Object();
|
||||
private long mDeferUpdatesUntilMillis = 0;
|
||||
private RemoteViews mMostRecentRemoteViews;
|
||||
|
||||
public LauncherAppWidgetHostView(Context context) {
|
||||
super(context);
|
||||
@@ -165,6 +170,11 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
|
||||
|
||||
@Override
|
||||
public void updateAppWidget(RemoteViews remoteViews) {
|
||||
synchronized (mUpdateLock) {
|
||||
mMostRecentRemoteViews = remoteViews;
|
||||
if (SystemClock.uptimeMillis() < mDeferUpdatesUntilMillis) return;
|
||||
}
|
||||
|
||||
super.updateAppWidget(remoteViews);
|
||||
|
||||
// The provider info or the views might have changed.
|
||||
@@ -198,6 +208,34 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin deferring the application of any {@link RemoteViews} updates made through
|
||||
* {@link #updateAppWidget(RemoteViews)} until {@link #endDeferringUpdates()} has been called or
|
||||
* the next {@link #updateAppWidget(RemoteViews)} call after {@link #UPDATE_LOCK_TIMEOUT_MILLIS}
|
||||
* have elapsed.
|
||||
*/
|
||||
public void beginDeferringUpdates() {
|
||||
synchronized (mUpdateLock) {
|
||||
mDeferUpdatesUntilMillis = SystemClock.uptimeMillis() + UPDATE_LOCK_TIMEOUT_MILLIS;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop deferring the application of {@link RemoteViews} updates made through
|
||||
* {@link #updateAppWidget(RemoteViews)} and apply the most recently received update.
|
||||
*/
|
||||
public void endDeferringUpdates() {
|
||||
RemoteViews remoteViews;
|
||||
synchronized (mUpdateLock) {
|
||||
mDeferUpdatesUntilMillis = 0;
|
||||
remoteViews = mMostRecentRemoteViews;
|
||||
mMostRecentRemoteViews = null;
|
||||
}
|
||||
if (remoteViews != null) {
|
||||
updateAppWidget(remoteViews);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
DragLayer dragLayer = mLauncher.getDragLayer();
|
||||
|
||||
Reference in New Issue
Block a user