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

Commit 750ba7b3 authored by Joshua Tsuji's avatar Joshua Tsuji
Browse files

ActivityView animations!

Expand/collapse animations work by applying a matrix to the expanded view container - this is a) fast b) allows for pivot scale animation c) works around some weirdness with "actually" scaling the view, since the matrix transform is applied after the AV draws.

Switch animations work by snapshotting the current bubble's surface into graphics memory, rendering that into a SurfaceView, and animating the SurfaceView out. Memory profiler indicates this does not use additional memory (since those pixels were already in graphic memory anyway, and released as soon as the animation ends).

Test: lots and lots of manual testing
Fixes: 123306815
Fixes: 135137761
Change-Id: I0b01dab4bb0c82873afc55d054bafc672bacc8bf
parent 67a1b20b
Loading
Loading
Loading
Loading
+0 −4
Original line number Diff line number Diff line
@@ -1223,10 +1223,6 @@
    <dimen name="bubble_dismiss_slop">16dp</dimen>
    <!-- Height of button allowing users to adjust settings for bubbles. -->
    <dimen name="bubble_manage_button_height">48dp</dimen>
    <!-- How far, horizontally, to animate the expanded view over when animating in/out. -->
    <dimen name="bubble_expanded_animate_x_distance">100dp</dimen>
    <!-- How far, vertically, to animate the expanded view over when animating in/out. -->
    <dimen name="bubble_expanded_animate_y_distance">500dp</dimen>
    <!-- Max width of the message bubble-->
    <dimen name="bubble_message_max_width">144dp</dimen>
    <!-- Min width of the message bubble -->
+103 −10
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.graphics.PixelFormat.TRANSPARENT;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
import static android.view.ViewRootImpl.sNewInsetsMode;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
@@ -33,7 +34,6 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPAND
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;

import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
@@ -46,6 +46,7 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Outline;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
@@ -55,12 +56,19 @@ import android.os.RemoteException;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.SurfaceControl;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.LinearLayout;

import androidx.annotation.Nullable;

import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.systemui.Dependency;
import com.android.systemui.R;
@@ -88,6 +96,7 @@ public class BubbleExpandedView extends LinearLayout {
    // The triangle pointing to the expanded view
    private View mPointerView;
    private int mPointerMargin;
    @Nullable private int[] mExpandedViewContainerLocation;

    private AlphaOptimizedButton mSettingsIcon;

@@ -121,6 +130,16 @@ public class BubbleExpandedView extends LinearLayout {
    private View mVirtualImeView;
    private WindowManager mVirtualDisplayWindowManager;
    private boolean mImeShowing = false;
    private float mCornerRadius = 0f;

    /**
     * Container for the ActivityView that has a solid, round-rect background that shows if the
     * ActivityView hasn't loaded.
     */
    private FrameLayout mActivityViewContainer = new FrameLayout(getContext());

    /** The SurfaceView that the ActivityView draws to. */
    @Nullable private SurfaceView mActivitySurface;

    private ActivityView.StateCallback mStateCallback = new ActivityView.StateCallback() {
        @Override
@@ -269,7 +288,28 @@ public class BubbleExpandedView extends LinearLayout {

        // Set ActivityView's alpha value as zero, since there is no view content to be shown.
        setContentVisibility(false);
        addView(mActivityView);

        mActivityViewContainer.setBackgroundColor(Color.WHITE);
        mActivityViewContainer.setOutlineProvider(new ViewOutlineProvider() {
            @Override
            public void getOutline(View view, Outline outline) {
                outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius);
            }
        });
        mActivityViewContainer.setClipToOutline(true);
        mActivityViewContainer.addView(mActivityView);
        mActivityViewContainer.setLayoutParams(
                new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
        addView(mActivityViewContainer);

        if (mActivityView != null
                && mActivityView.getChildCount() > 0
                && mActivityView.getChildAt(0) instanceof SurfaceView) {
            // Retrieve the surface from the ActivityView so we can screenshot it and change its
            // z-ordering. This should always be possible, since ActivityView's constructor adds the
            // SurfaceView as its first child.
            mActivitySurface = (SurfaceView) mActivityView.getChildAt(0);
        }

        // Expanded stack layout, top to bottom:
        // Expanded view container
@@ -327,6 +367,40 @@ public class BubbleExpandedView extends LinearLayout {
        return mBubble != null ? mBubble.getKey() : "null";
    }

    /**
     * Asks the ActivityView's surface to draw on top of all other views in the window. This is
     * useful for ordering surfaces during animations, but should otherwise be set to false so that
     * bubbles and menus can draw over the ActivityView.
     */
    void setSurfaceZOrderedOnTop(boolean onTop) {
        if (mActivitySurface == null) {
            return;
        }

        mActivitySurface.setZOrderedOnTop(onTop, true);
    }

    /** Return a GraphicBuffer with the contents of the ActivityView's underlying surface. */
    @Nullable
    SurfaceControl.ScreenshotHardwareBuffer snapshotActivitySurface() {
        if (mActivitySurface == null) {
            return null;
        }

        return SurfaceControl.captureLayers(
                mActivitySurface.getSurfaceControl(),
                new Rect(0, 0, mActivityView.getWidth(), mActivityView.getHeight()),
                1 /* scale */);
    }

    int[] getActivityViewLocationOnScreen() {
        if (mActivityView != null) {
            return mActivityView.getLocationOnScreen();
        } else {
            return new int[]{0, 0};
        }
    }

    void setManageClickListener(OnClickListener manageClickListener) {
        findViewById(R.id.settings_button).setOnClickListener(manageClickListener);
    }
@@ -345,12 +419,12 @@ public class BubbleExpandedView extends LinearLayout {
    void applyThemeAttrs() {
        final TypedArray ta = mContext.obtainStyledAttributes(
                new int[] {android.R.attr.dialogCornerRadius});
        float cornerRadius = ta.getDimensionPixelSize(0, 0);
        mCornerRadius = ta.getDimensionPixelSize(0, 0);
        ta.recycle();

        if (mActivityView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
                mContext.getResources())) {
            mActivityView.setCornerRadius(cornerRadius);
            mActivityView.setCornerRadius(mCornerRadius);
        }
    }

@@ -398,6 +472,7 @@ public class BubbleExpandedView extends LinearLayout {
        mPointerView.setAlpha(alpha);
        if (mActivityView != null) {
            mActivityView.setAlpha(alpha);
            mActivityView.bringToFront();
        }
    }

@@ -557,6 +632,11 @@ public class BubbleExpandedView extends LinearLayout {
        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
            Log.d(TAG, "updateHeight: bubble=" + getBubbleKey());
        }

        if (mExpandedViewContainerLocation == null) {
            return;
        }

        if (usingActivityView()) {
            float desiredHeight = mOverflowHeight;
            if (!mIsOverflow) {
@@ -564,7 +644,7 @@ public class BubbleExpandedView extends LinearLayout {
            }
            float height = Math.min(desiredHeight, getMaxExpandedHeight());
            height = Math.max(height, mIsOverflow? mOverflowHeight : mMinHeight);
            LayoutParams lp = (LayoutParams) mActivityView.getLayoutParams();
            ViewGroup.LayoutParams lp = mActivityView.getLayoutParams();
            mNeedsNewHeight = lp.height != height;
            if (!mKeyboardVisible) {
                // If the keyboard is visible... don't adjust the height because that will cause
@@ -574,7 +654,8 @@ public class BubbleExpandedView extends LinearLayout {
                mNeedsNewHeight = false;
            }
            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
                Log.d(TAG, "updateHeight: bubble=" + getBubbleKey() + " height=" + height
                Log.d(TAG, "updateHeight: bubble=" + getBubbleKey()
                        + " height=" + height
                        + " mNeedsNewHeight=" + mNeedsNewHeight);
            }
        }
@@ -582,29 +663,41 @@ public class BubbleExpandedView extends LinearLayout {

    private int getMaxExpandedHeight() {
        mWindowManager.getDefaultDisplay().getRealSize(mDisplaySize);
        int[] windowLocation = mActivityView.getLocationOnScreen();
        int bottomInset = getRootWindowInsets() != null
                ? getRootWindowInsets().getStableInsetBottom()
                : 0;
        return mDisplaySize.y - windowLocation[1] - mSettingsIconHeight - mPointerHeight

        return mDisplaySize.y
                - mExpandedViewContainerLocation[1]
                - getPaddingTop()
                - getPaddingBottom()
                - mSettingsIconHeight
                - mPointerHeight
                - mPointerMargin - bottomInset;
    }

    /**
     * Update appearance of the expanded view being displayed.
     *
     * @param containerLocationOnScreen The location on-screen of the container the expanded view is
     *                                  added to. This allows us to calculate max height without
     *                                  waiting for layout.
     */
    public void updateView() {
    public void updateView(int[] containerLocationOnScreen) {
        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
            Log.d(TAG, "updateView: bubble="
                    + getBubbleKey());
        }

        mExpandedViewContainerLocation = containerLocationOnScreen;

        if (usingActivityView()
                && mActivityView.getVisibility() == VISIBLE
                && mActivityView.isAttachedToWindow()) {
            mActivityView.onLocationChanged();
        }
            updateHeight();
        }
    }

    /**
     * Set the x position that the tip of the triangle should point to.
+372 −97

File changed.

Preview size limit exceeded, changes collapsed.

+137 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.systemui.bubbles.animation;

import android.graphics.Matrix;

import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.FloatPropertyCompat;

/**
 * Matrix whose scale properties can be animated using physics animations, via the {@link #SCALE_X}
 * and {@link #SCALE_Y} FloatProperties.
 *
 * This is useful when you need to perform a scale animation with a pivot point, since pivot points
 * are not supported by standard View scale operations but are supported by matrices.
 *
 * NOTE: DynamicAnimation assumes that all custom properties are denominated in pixels, and thus
 * considers 1 to be the smallest user-visible change for custom properties. This means that if you
 * animate {@link #SCALE_X} and {@link #SCALE_Y} to 3f, for example, the animation would have only
 * three frames.
 *
 * To work around this, whenever animating to a desired scale value, animate to the value returned
 * by {@link #getAnimatableValueForScaleFactor} instead. The SCALE_X and SCALE_Y properties will
 * convert that (larger) value into the appropriate scale factor when scaling the matrix.
 */
public class AnimatableScaleMatrix extends Matrix {

    /**
     * The X value of the scale.
     *
     * NOTE: This must be set or animated to the value returned by
     * {@link #getAnimatableValueForScaleFactor}, not the desired scale factor itself.
     */
    public static final FloatPropertyCompat<AnimatableScaleMatrix> SCALE_X =
            new FloatPropertyCompat<AnimatableScaleMatrix>("matrixScaleX") {
        @Override
        public float getValue(AnimatableScaleMatrix object) {
            return getAnimatableValueForScaleFactor(object.mScaleX);
        }

        @Override
        public void setValue(AnimatableScaleMatrix object, float value) {
            object.setScaleX(value * DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE);
        }
    };

    /**
     * The Y value of the scale.
     *
     * NOTE: This must be set or animated to the value returned by
     * {@link #getAnimatableValueForScaleFactor}, not the desired scale factor itself.
     */
    public static final FloatPropertyCompat<AnimatableScaleMatrix> SCALE_Y =
            new FloatPropertyCompat<AnimatableScaleMatrix>("matrixScaleY") {
                @Override
                public float getValue(AnimatableScaleMatrix object) {
                    return getAnimatableValueForScaleFactor(object.mScaleY);
                }

                @Override
                public void setValue(AnimatableScaleMatrix object, float value) {
                    object.setScaleY(value * DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE);
                }
            };

    private float mScaleX = 1f;
    private float mScaleY = 1f;

    private float mPivotX = 0f;
    private float mPivotY = 0f;

    /**
     * Return the value to animate SCALE_X or SCALE_Y to in order to achieve the desired scale
     * factor.
     */
    public static float getAnimatableValueForScaleFactor(float scale) {
        return scale * (1f / DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE);
    }

    @Override
    public void setScale(float sx, float sy, float px, float py) {
        mScaleX = sx;
        mScaleY = sy;
        mPivotX = px;
        mPivotY = py;
        super.setScale(mScaleX, mScaleY, mPivotX, mPivotY);
    }

    public void setScaleX(float scaleX) {
        mScaleX = scaleX;
        super.setScale(mScaleX, mScaleY, mPivotX, mPivotY);
    }

    public void setScaleY(float scaleY) {
        mScaleY = scaleY;
        super.setScale(mScaleX, mScaleY, mPivotX, mPivotY);
    }

    public void setPivotX(float pivotX) {
        mPivotX = pivotX;
        super.setScale(mScaleX, mScaleY, mPivotX, mPivotY);
    }

    public void setPivotY(float pivotY) {
        mPivotY = pivotY;
        super.setScale(mScaleX, mScaleY, mPivotX, mPivotY);
    }

    public float getScaleX() {
        return mScaleX;
    }

    public float getScaleY() {
        return mScaleY;
    }

    public float getPivotX() {
        return mPivotX;
    }

    public float getPivotY() {
        return mPivotY;
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -56,7 +56,7 @@ public class ExpandedAnimationController
    private static final int ANIMATE_TRANSLATION_FACTOR = 4;

    /** Duration of the expand/collapse target path animation. */
    private static final int EXPAND_COLLAPSE_TARGET_ANIM_DURATION = 175;
    public static final int EXPAND_COLLAPSE_TARGET_ANIM_DURATION = 175;

    /** Stiffness for the expand/collapse path-following animation. */
    private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 1000;
Loading