Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d8aa1c31 authored by Ats Jenk's avatar Ats Jenk
Browse files

Animation for bubbles home gesture

Move bubbles expanded view up when user is performing the home gesture.
This gives immediate visual feedback to the user when swiping up to
collapse the expanded bubble.
Updating the expanded view collapse animation so that it works well
together with swipe up gesture.

Bug: 170163464
Test: atest PlatformScenarioTests:android.platform.test.scenario.sysui.bubble
Test: atest frameworks/base/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles
Change-Id: I834ef8de73dc4f9682583fa727ef42db21c19a7f
parent bb30e296
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -52,6 +52,19 @@ public class Interpolators {
     */
    public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);

    /**
     * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
     * is disappearing e.g. when moving off screen.
     */
    public static final Interpolator EMPHASIZED_ACCELERATE = new PathInterpolator(
            0.3f, 0f, 0.8f, 0.15f);

    /**
     * The decelerating emphasized interpolator. Used for hero / emphasized movement of content that
     * is appearing e.g. when coming from off screen
     */
    public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
            0.05f, 0.7f, 0.1f, 1f);
    /**
     * Interpolator to be used when animating a move based on a click. Pair with enough duration.
     */
+1 −1
Original line number Diff line number Diff line
@@ -1512,7 +1512,7 @@ public class BubbleController {
        public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
            mBubblePositioner.setImeVisible(imeVisible, imeHeight);
            if (mStackView != null) {
                mStackView.animateForIme(imeVisible);
                mStackView.setImeVisible(imeVisible);
            }
        }
    }
+3 −0
Original line number Diff line number Diff line
@@ -46,6 +46,9 @@ public class BubbleDebugConfig {
    static final boolean DEBUG_OVERFLOW = false;
    static final boolean DEBUG_USER_EDUCATION = false;
    static final boolean DEBUG_POSITIONER = false;
    public static final boolean DEBUG_COLLAPSE_ANIMATOR = false;
    static final boolean DEBUG_BUBBLE_GESTURE = false;
    public static boolean DEBUG_EXPANDED_VIEW_DRAGGING = false;

    private static final boolean FORCE_SHOW_USER_EDUCATION = false;
    private static final String FORCE_SHOW_USER_EDUCATION_SETTING =
+248 −28
Original line number Diff line number Diff line
@@ -43,10 +43,13 @@ import android.graphics.CornerPathEffect;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Picture;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
import android.os.RemoteException;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.IntProperty;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
@@ -75,6 +78,48 @@ import java.io.PrintWriter;
public class BubbleExpandedView extends LinearLayout {
    private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleExpandedView" : TAG_BUBBLES;

    /** {@link IntProperty} for updating bottom clip */
    public static final IntProperty<BubbleExpandedView> BOTTOM_CLIP_PROPERTY =
            new IntProperty<BubbleExpandedView>("bottomClip") {
                @Override
                public void setValue(BubbleExpandedView expandedView, int value) {
                    expandedView.setBottomClip(value);
                }

                @Override
                public Integer get(BubbleExpandedView expandedView) {
                    return expandedView.mBottomClip;
                }
            };

    /** {@link FloatProperty} for updating taskView or overflow alpha */
    public static final FloatProperty<BubbleExpandedView> CONTENT_ALPHA =
            new FloatProperty<BubbleExpandedView>("contentAlpha") {
                @Override
                public void setValue(BubbleExpandedView expandedView, float value) {
                    expandedView.setContentAlpha(value);
                }

                @Override
                public Float get(BubbleExpandedView expandedView) {
                    return expandedView.getContentAlpha();
                }
            };

    /** {@link FloatProperty} for updating manage button alpha */
    public static final FloatProperty<BubbleExpandedView> MANAGE_BUTTON_ALPHA =
            new FloatProperty<BubbleExpandedView>("manageButtonAlpha") {
                @Override
                public void setValue(BubbleExpandedView expandedView, float value) {
                    expandedView.mManageButton.setAlpha(value);
                }

                @Override
                public Float get(BubbleExpandedView expandedView) {
                    return expandedView.mManageButton.getAlpha();
                }
            };

    // The triangle pointing to the expanded view
    private View mPointerView;
    @Nullable private int[] mExpandedViewContainerLocation;
@@ -90,7 +135,7 @@ public class BubbleExpandedView extends LinearLayout {

    /**
     * Whether we want the {@code TaskView}'s content to be visible (alpha = 1f). If
     * {@link #mIsAlphaAnimating} is true, this may not reflect the {@code TaskView}'s actual alpha
     * {@link #mIsAnimating} is true, this may not reflect the {@code TaskView}'s actual alpha
     * value until the animation ends.
     */
    private boolean mIsContentVisible = false;
@@ -99,12 +144,13 @@ public class BubbleExpandedView extends LinearLayout {
     * Whether we're animating the {@code TaskView}'s alpha value. If so, we will hold off on
     * applying alpha changes from {@link #setContentVisibility} until the animation ends.
     */
    private boolean mIsAlphaAnimating = false;
    private boolean mIsAnimating = false;

    private int mPointerWidth;
    private int mPointerHeight;
    private float mPointerRadius;
    private float mPointerOverlap;
    private final PointF mPointerPos = new PointF();
    private CornerPathEffect mPointerEffect;
    private ShapeDrawable mCurrentPointer;
    private ShapeDrawable mTopPointer;
@@ -113,11 +159,13 @@ public class BubbleExpandedView extends LinearLayout {
    private float mCornerRadius = 0f;
    private int mBackgroundColorFloating;
    private boolean mUsingMaxHeight;

    private int mTopClip = 0;
    private int mBottomClip = 0;
    @Nullable private Bubble mBubble;
    private PendingIntent mPendingIntent;
    // TODO(b/170891664): Don't use a flag, set the BubbleOverflow object instead
    private boolean mIsOverflow;
    private boolean mIsClipping;

    private BubbleController mController;
    private BubbleStackView mStackView;
@@ -268,7 +316,8 @@ public class BubbleExpandedView extends LinearLayout {
        mExpandedViewContainer.setOutlineProvider(new ViewOutlineProvider() {
            @Override
            public void getOutline(View view, Outline outline) {
                outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius);
                Rect clip = new Rect(0, mTopClip, view.getWidth(), view.getHeight() - mBottomClip);
                outline.setRoundRect(clip, mCornerRadius);
            }
        });
        mExpandedViewContainer.setClipToOutline(true);
@@ -429,7 +478,7 @@ public class BubbleExpandedView extends LinearLayout {
     * ordering surfaces during animations. When content is drawn on top of the app (e.g. bubble
     * being dragged out, the manage menu) this is set to false, otherwise it should be true.
     */
    void setSurfaceZOrderedOnTop(boolean onTop) {
    public void setSurfaceZOrderedOnTop(boolean onTop) {
        if (mTaskView == null) {
            return;
        }
@@ -510,12 +559,12 @@ public class BubbleExpandedView extends LinearLayout {
    }

    /**
     * Whether we are currently animating the {@code TaskView}'s alpha value. If this is set to
     * Whether we are currently animating the {@code TaskView}. If this is set to
     * true, calls to {@link #setContentVisibility} will not be applied until this is set to false
     * again.
     */
    void setAlphaAnimating(boolean animating) {
        mIsAlphaAnimating = animating;
    public void setAnimating(boolean animating) {
        mIsAnimating = animating;

        // If we're done animating, apply the correct
        if (!animating) {
@@ -524,15 +573,128 @@ public class BubbleExpandedView extends LinearLayout {
    }

    /**
     * Sets the alpha of the underlying {@code TaskView}, since changing the expanded view's alpha
     * does not affect the {@code TaskView} since it uses a Surface.
     * Get alpha from underlying {@code TaskView} if this view is for a bubble.
     * Or get alpha for the overflow view if this view is for overflow.
     *
     * @return alpha for the content being shown
     */
    void setTaskViewAlpha(float alpha) {
    public float getContentAlpha() {
        if (mIsOverflow) {
            return mOverflowView.getAlpha();
        }
        if (mTaskView != null) {
            return mTaskView.getAlpha();
        }
        return 1f;
    }

    /**
     * Set alpha of the underlying {@code TaskView} if this view is for a bubble.
     * Or set alpha for the overflow view if this view is for overflow.
     *
     * Changing expanded view's alpha does not affect the {@code TaskView} since it uses a Surface.
     */
    public void setContentAlpha(float alpha) {
        if (mIsOverflow) {
            mOverflowView.setAlpha(alpha);
        } else if (mTaskView != null) {
            mTaskView.setAlpha(alpha);
        }
        mPointerView.setAlpha(alpha);
        setAlpha(alpha);
    }

    /**
     * Set translation Y for the expanded view content.
     * Excludes manage button and pointer.
     */
    public void setContentTranslationY(float translationY) {
        mExpandedViewContainer.setTranslationY(translationY);

        // Left or right pointer can become detached when moving the view up
        if (translationY <= 0 && (isShowingLeftPointer() || isShowingRightPointer())) {
            // Y coordinate where the pointer would start to get detached from the expanded view.
            // Takes into account bottom clipping and rounded corners
            float detachPoint =
                    mExpandedViewContainer.getBottom() - mBottomClip - mCornerRadius + translationY;
            float pointerBottom = mPointerPos.y + mPointerHeight;
            // If pointer bottom is past detach point, move it in by that many pixels
            float horizontalShift = 0;
            if (pointerBottom > detachPoint) {
                horizontalShift = pointerBottom - detachPoint;
            }
            if (isShowingLeftPointer()) {
                // Move left pointer right
                movePointerBy(horizontalShift, 0);
            } else {
                // Move right pointer left
                movePointerBy(-horizontalShift, 0);
            }
            // Hide pointer if it is moved by entire width
            mPointerView.setVisibility(
                    horizontalShift > mPointerWidth ? View.INVISIBLE : View.VISIBLE);
        }
    }

    /**
     * Update alpha value for the manage button
     */
    public void setManageButtonAlpha(float alpha) {
        mManageButton.setAlpha(alpha);
    }

    /**
     * Set {@link #setTranslationY(float) translationY} for the manage button
     */
    public void setManageButtonTranslationY(float translationY) {
        mManageButton.setTranslationY(translationY);
    }

    /**
     * Set top clipping for the view
     */
    public void setTopClip(int clip) {
        mTopClip = clip;
        onContainerClipUpdate();
    }

    /**
     * Set bottom clipping for the view
     */
    public void setBottomClip(int clip) {
        mBottomClip = clip;
        onContainerClipUpdate();
    }

    private void onContainerClipUpdate() {
        if (mTopClip == 0 && mBottomClip == 0) {
            if (mIsClipping) {
                mIsClipping = false;
                if (mTaskView != null) {
                    mTaskView.setClipBounds(null);
                    mTaskView.setEnableSurfaceClipping(false);
                }
                mExpandedViewContainer.invalidateOutline();
            }
        } else {
            if (!mIsClipping) {
                mIsClipping = true;
                if (mTaskView != null) {
                    mTaskView.setEnableSurfaceClipping(true);
                }
            }
            mExpandedViewContainer.invalidateOutline();
            if (mTaskView != null) {
                mTaskView.setClipBounds(new Rect(0, mTopClip, mTaskView.getWidth(),
                        mTaskView.getHeight() - mBottomClip));
            }
        }
    }

    /**
     * Move pointer from base position
     */
    public void movePointerBy(float x, float y) {
        mPointerView.setTranslationX(mPointerPos.x + x);
        mPointerView.setTranslationY(mPointerPos.y + y);
    }

    /**
@@ -543,13 +705,13 @@ public class BubbleExpandedView extends LinearLayout {
     * Note that this contents visibility doesn't affect visibility at {@link android.view.View},
     * and setting {@code false} actually means rendering the contents in transparent.
     */
    void setContentVisibility(boolean visibility) {
    public void setContentVisibility(boolean visibility) {
        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
            Log.d(TAG, "setContentVisibility: visibility=" + visibility
                    + " bubble=" + getBubbleKey());
        }
        mIsContentVisible = visibility;
        if (mTaskView != null && !mIsAlphaAnimating) {
        if (mTaskView != null && !mIsAnimating) {
            mTaskView.setAlpha(visibility ? 1f : 0f);
            mPointerView.setAlpha(visibility ? 1f : 0f);
        }
@@ -560,6 +722,44 @@ public class BubbleExpandedView extends LinearLayout {
        return mTaskView;
    }

    @VisibleForTesting
    public BubbleOverflowContainerView getOverflow() {
        return mOverflowView;
    }


    /**
     * Return content height: taskView or overflow.
     * Takes into account clippings set by {@link #setTopClip(int)} and {@link #setBottomClip(int)}
     *
     * @return if bubble is for overflow, return overflow height, otherwise return taskView height
     */
    public int getContentHeight() {
        if (mIsOverflow) {
            return mOverflowView.getHeight() - mTopClip - mBottomClip;
        }
        if (mTaskView != null) {
            return mTaskView.getHeight() - mTopClip - mBottomClip;
        }
        return 0;
    }

    /**
     * Return bottom position of the content on screen
     *
     * @return if bubble is for overflow, return value for overflow, otherwise taskView
     */
    public int getContentBottomOnScreen() {
        Rect out = new Rect();
        if (mIsOverflow) {
            mOverflowView.getBoundsOnScreen(out);
        }
        if (mTaskView != null) {
            mTaskView.getBoundsOnScreen(out);
        }
        return out.bottom;
    }

    int getTaskId() {
        return mTaskId;
    }
@@ -728,27 +928,47 @@ public class BubbleExpandedView extends LinearLayout {
        post(() -> {
            mCurrentPointer = showVertically ? onLeft ? mLeftPointer : mRightPointer : mTopPointer;
            updatePointerView();
            float pointerY;
            float pointerX;
            if (showVertically) {
                pointerY = bubbleCenter - (mPointerWidth / 2f);
                pointerX = onLeft
                mPointerPos.y = bubbleCenter - (mPointerWidth / 2f);
                mPointerPos.x = onLeft
                        ? -mPointerHeight + mPointerOverlap
                        : getWidth() - mPaddingRight - mPointerOverlap;
            } else {
                pointerY = mPointerOverlap;
                pointerX = bubbleCenter - (mPointerWidth / 2f);
                mPointerPos.y = mPointerOverlap;
                mPointerPos.x = bubbleCenter - (mPointerWidth / 2f);
            }
            if (animate) {
                mPointerView.animate().translationX(pointerX).translationY(pointerY).start();
                mPointerView.animate().translationX(mPointerPos.x).translationY(
                        mPointerPos.y).start();
            } else {
                mPointerView.setTranslationY(pointerY);
                mPointerView.setTranslationX(pointerX);
                mPointerView.setTranslationY(mPointerPos.y);
                mPointerView.setTranslationX(mPointerPos.x);
                mPointerView.setVisibility(VISIBLE);
            }
        });
    }

    /**
     * Return true if pointer is shown on the left
     */
    public boolean isShowingLeftPointer() {
        return mCurrentPointer == mLeftPointer;
    }

    /**
     * Return true if pointer is shown on the right
     */
    public boolean isShowingRightPointer() {
        return mCurrentPointer == mRightPointer;
    }

    /**
     * Return width of the current pointer
     */
    public int getPointerWidth() {
        return mPointerWidth;
    }

    /**
     * Position of the manage button displayed in the expanded view. Used for placing user
     * education about the manage button.
+25 −0
Original line number Diff line number Diff line
@@ -344,6 +344,14 @@ public class BubblePositioner {
        return mImeVisible ? mImeHeight : 0;
    }

    /** Return top position of the IME if it's visible */
    public int getImeTop() {
        if (mImeVisible) {
            return getScreenRect().bottom - getImeHeight() - getInsets().bottom;
        }
        return 0;
    }

    /** Sets whether the IME is visible. **/
    public void setImeVisible(boolean visible, int height) {
        mImeVisible = visible;
@@ -706,4 +714,21 @@ public class BubblePositioner {
    public void setPinnedLocation(PointF point) {
        mPinLocation = point;
    }

    /**
     * Navigation bar has an area where system gestures can be started from.
     *
     * @return {@link Rect} for system navigation bar gesture zone
     */
    public Rect getNavBarGestureZone() {
        // Gesture zone height from the bottom
        int gestureZoneHeight = mContext.getResources().getDimensionPixelSize(
                com.android.internal.R.dimen.navigation_bar_gesture_height);
        Rect screen = getScreenRect();
        return new Rect(
                screen.left,
                screen.bottom - gestureZoneHeight,
                screen.right,
                screen.bottom);
    }
}
Loading