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
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 5b1b59b..1a464b1 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -79,6 +79,7 @@
 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.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 @@
 
     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 @@
         }
     }
 
+    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 @@
             }
         });
 
+        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 @@
                 }
             });
         }
-
-        animatorSet.playTogether(appAnimator, backgroundRadiusAnim);
-        return animatorSet;
+        return backgroundRadiusAnim;
     }
 
     /**
@@ -1120,9 +1252,13 @@
             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) {
diff --git a/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
new file mode 100644
index 0000000..f74aa55
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
@@ -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);
+            }
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java b/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java
new file mode 100644
index 0000000..d23884c
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java
@@ -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]));
+    }
+}
diff --git a/res/layout/floating_widget_view.xml b/res/layout/floating_widget_view.xml
new file mode 100644
index 0000000..eea7a92
--- /dev/null
+++ b/res/layout/floating_widget_view.xml
@@ -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" />
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index f77c740..620604a 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -72,6 +72,8 @@
 
     // 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 @@
             }
         }
     };
+    private final Object mUpdateLock = new Object();
+    private long mDeferUpdatesUntilMillis = 0;
+    private RemoteViews mMostRecentRemoteViews;
 
     public LauncherAppWidgetHostView(Context context) {
         super(context);
@@ -165,6 +170,11 @@
 
     @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 @@
         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();