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

Commit 773f6bd4 authored by Rahul Banerjee's avatar Rahul Banerjee Committed by Android (Google) Code Review
Browse files

Merge "Notification Shade Animation for Back Gesture (udc-dev)" into udc-dev

parents 5ddb41c0 d3aa5b8e
Loading
Loading
Loading
Loading
+12 −3
Original line number Original line Diff line number Diff line
@@ -23,6 +23,7 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import android.annotation.IntDef;
import android.annotation.IntDef;
import android.content.Context;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources;
import android.os.SystemProperties;
import android.view.ViewConfiguration;
import android.view.ViewConfiguration;
import android.view.WindowManagerPolicyConstants;
import android.view.WindowManagerPolicyConstants;


@@ -115,6 +116,9 @@ public class QuickStepContract {
    public static final int SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE = 1 << 26;
    public static final int SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE = 1 << 26;
    // Device dreaming state
    // Device dreaming state
    public static final int SYSUI_STATE_DEVICE_DREAMING = 1 << 27;
    public static final int SYSUI_STATE_DEVICE_DREAMING = 1 << 27;
    // Whether the back gesture is allowed (or ignored) by the Shade
    public static final boolean ALLOW_BACK_GESTURE_IN_SHADE = SystemProperties.getBoolean(
            "persist.wm.debug.shade_allow_back_gesture", false);


    @Retention(RetentionPolicy.SOURCE)
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({SYSUI_STATE_SCREEN_PINNING,
    @IntDef({SYSUI_STATE_SCREEN_PINNING,
@@ -243,9 +247,14 @@ public class QuickStepContract {
            sysuiStateFlags &= ~SYSUI_STATE_NAV_BAR_HIDDEN;
            sysuiStateFlags &= ~SYSUI_STATE_NAV_BAR_HIDDEN;
        }
        }
        // Disable when in immersive, or the notifications are interactive
        // Disable when in immersive, or the notifications are interactive
        int disableFlags = SYSUI_STATE_NAV_BAR_HIDDEN
        int disableFlags = SYSUI_STATE_NAV_BAR_HIDDEN | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
                | SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED

                | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
        // EdgeBackGestureHandler ignores Back gesture when SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED.
        // To allow Shade to respond to Back, we're bypassing this check (behind a flag).
        if (!ALLOW_BACK_GESTURE_IN_SHADE) {
            disableFlags |= SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
        }

        return (sysuiStateFlags & disableFlags) != 0;
        return (sysuiStateFlags & disableFlags) != 0;
    }
    }


+55 −3
Original line number Original line Diff line number Diff line
@@ -27,6 +27,7 @@ import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Xfermode;
import android.graphics.Xfermode;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
@@ -41,7 +42,11 @@ import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
public class ScrimDrawable extends Drawable {
public class ScrimDrawable extends Drawable {
    private static final String TAG = "ScrimDrawable";
    private static final String TAG = "ScrimDrawable";


    private boolean mShouldUseLargeScreenSize;
    private final Paint mPaint;
    private final Paint mPaint;
    private final Path mPath = new Path();
    private final RectF mBoundsRectF = new RectF();

    private int mAlpha = 255;
    private int mAlpha = 255;
    private int mMainColor;
    private int mMainColor;
    private ValueAnimator mColorAnimation;
    private ValueAnimator mColorAnimation;
@@ -49,11 +54,13 @@ public class ScrimDrawable extends Drawable {
    private float mCornerRadius;
    private float mCornerRadius;
    private ConcaveInfo mConcaveInfo;
    private ConcaveInfo mConcaveInfo;
    private int mBottomEdgePosition;
    private int mBottomEdgePosition;
    private float mBottomEdgeRadius = -1;
    private boolean mCornerRadiusEnabled;
    private boolean mCornerRadiusEnabled;


    public ScrimDrawable() {
    public ScrimDrawable() {
        mPaint = new Paint();
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setStyle(Paint.Style.FILL);
        mShouldUseLargeScreenSize = false;
    }
    }


    /**
    /**
@@ -133,6 +140,10 @@ public class ScrimDrawable extends Drawable {
        return PixelFormat.TRANSLUCENT;
        return PixelFormat.TRANSLUCENT;
    }
    }


    public void setShouldUseLargeScreenSize(boolean v) {
        mShouldUseLargeScreenSize = v;
    }

    /**
    /**
     * Corner radius used by either concave or convex corners.
     * Corner radius used by either concave or convex corners.
     */
     */
@@ -191,6 +202,10 @@ public class ScrimDrawable extends Drawable {
        invalidateSelf();
        invalidateSelf();
    }
    }


    public void setBottomEdgeRadius(float radius) {
        mBottomEdgeRadius = radius;
    }

    @Override
    @Override
    public void draw(@NonNull Canvas canvas) {
    public void draw(@NonNull Canvas canvas) {
        mPaint.setColor(mMainColor);
        mPaint.setColor(mMainColor);
@@ -198,9 +213,46 @@ public class ScrimDrawable extends Drawable {
        if (mConcaveInfo != null) {
        if (mConcaveInfo != null) {
            drawConcave(canvas);
            drawConcave(canvas);
        } else if (mCornerRadiusEnabled && mCornerRadius > 0) {
        } else if (mCornerRadiusEnabled && mCornerRadius > 0) {
            canvas.drawRoundRect(getBounds().left, getBounds().top, getBounds().right,
            float topEdgeRadius = mCornerRadius;
                    getBounds().bottom,
            float bottomEdgeRadius = mBottomEdgeRadius == -1.0 ? mCornerRadius : mBottomEdgeRadius;
                    /* x radius*/ mCornerRadius, /* y radius*/ mCornerRadius, mPaint);

            mBoundsRectF.set(getBounds());

            // When the back gesture causes the notification scrim to be scaled down,
            // this offset "reveals" the rounded bottom edge as it "pulls away".
            // We must *not* make this adjustment on largescreen shades (where the corner is sharp).
            if (!mShouldUseLargeScreenSize && mBottomEdgeRadius != -1) {
                mBoundsRectF.bottom -= bottomEdgeRadius;
            }

            // We need a box with rounded corners but its lower corners are not rounded on large
            // screen devices in "portrait" orientation.
            // Thus, we cannot draw a symmetric rounded rectangle via canvas.drawRoundRect()
            // and must build a box with different corner radii at the top and at the bottom.
            // Additionally, when the scrim is pushed to the very bottom of the screen, do not draw
            // anything (drawing a rounded box with these specifications is not possible).
            // TODO(b/271030611) perhaps this could be accomplished via Path.addRoundRect instead?
            if (mBoundsRectF.bottom - mBoundsRectF.top > bottomEdgeRadius) {
                mPath.reset();
                mPath.moveTo(mBoundsRectF.right, mBoundsRectF.top + topEdgeRadius);
                mPath.cubicTo(mBoundsRectF.right, mBoundsRectF.top + topEdgeRadius,
                        mBoundsRectF.right, mBoundsRectF.top,
                        mBoundsRectF.right - topEdgeRadius, mBoundsRectF.top);
                mPath.lineTo(mBoundsRectF.left + topEdgeRadius, mBoundsRectF.top);
                mPath.cubicTo(mBoundsRectF.left + topEdgeRadius, mBoundsRectF.top,
                        mBoundsRectF.left, mBoundsRectF.top,
                        mBoundsRectF.left, mBoundsRectF.top + topEdgeRadius);
                mPath.lineTo(mBoundsRectF.left, mBoundsRectF.bottom - bottomEdgeRadius);
                mPath.cubicTo(mBoundsRectF.left, mBoundsRectF.bottom - bottomEdgeRadius,
                        mBoundsRectF.left, mBoundsRectF.bottom,
                        mBoundsRectF.left + bottomEdgeRadius, mBoundsRectF.bottom);
                mPath.lineTo(mBoundsRectF.right - bottomEdgeRadius, mBoundsRectF.bottom);
                mPath.cubicTo(mBoundsRectF.right - bottomEdgeRadius, mBoundsRectF.bottom,
                        mBoundsRectF.right, mBoundsRectF.bottom,
                        mBoundsRectF.right, mBoundsRectF.bottom - bottomEdgeRadius);
                mPath.close();
                canvas.drawPath(mPath, mPaint);
            }
        } else {
        } else {
            canvas.drawRect(getBounds().left, getBounds().top, getBounds().right,
            canvas.drawRect(getBounds().left, getBounds().top, getBounds().right,
                    getBounds().bottom, mPaint);
                    getBounds().bottom, mPaint);
+18 −0
Original line number Original line Diff line number Diff line
@@ -20,6 +20,7 @@ import static java.lang.Float.isNaN;


import android.annotation.NonNull;
import android.annotation.NonNull;
import android.content.Context;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.PorterDuff;
@@ -37,6 +38,7 @@ import androidx.core.graphics.ColorUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.systemui.util.LargeScreenUtils;


import java.util.concurrent.Executor;
import java.util.concurrent.Executor;


@@ -102,6 +104,13 @@ public class ScrimView extends View {
    @Override
    @Override
    protected void onDraw(Canvas canvas) {
    protected void onDraw(Canvas canvas) {
        if (mDrawable.getAlpha() > 0) {
        if (mDrawable.getAlpha() > 0) {
            Resources res = getResources();
            // Scrim behind notification shade has sharp (not rounded) corners on large screens
            // which scrim itself cannot know, so we set it here.
            if (mDrawable instanceof ScrimDrawable) {
                ((ScrimDrawable) mDrawable).setShouldUseLargeScreenSize(
                        LargeScreenUtils.shouldUseLargeScreenShadeHeader(res));
            }
            mDrawable.draw(canvas);
            mDrawable.draw(canvas);
        }
        }
    }
    }
@@ -170,6 +179,15 @@ public class ScrimView extends View {
        });
        });
    }
    }


    /**
     * Set corner radius of the bottom edge of the Notification scrim.
     */
    public void setBottomEdgeRadius(float radius) {
        if (mDrawable instanceof ScrimDrawable) {
            ((ScrimDrawable) mDrawable).setBottomEdgeRadius(radius);
        }
    }

    @VisibleForTesting
    @VisibleForTesting
    Drawable getDrawable() {
    Drawable getDrawable() {
        return mDrawable;
        return mDrawable;
+81 −0
Original line number Original line Diff line number Diff line
@@ -293,7 +293,19 @@ public final class NotificationPanelViewController implements Dumpable {
     * custom clock animation is in use.
     * custom clock animation is in use.
     */
     */
    private static final int KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION = 1000;
    private static final int KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION = 1000;
    /**
     * Whether the Shade should animate to reflect Back gesture progress.
     * To minimize latency at runtime, we cache this, else we'd be reading it every time
     * updateQsExpansion() is called... and it's called very often.
     *
     * Whenever we change this flag, SysUI is restarted, so it's never going to be "stale".
     */


    public final boolean mAnimateBack;
    /**
     * The minimum scale to "squish" the Shade and associated elements down to, for Back gesture
     */
    public static final float SHADE_BACK_ANIM_MIN_SCALE = 0.9f;
    private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
    private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
    private final Resources mResources;
    private final Resources mResources;
    private final KeyguardStateController mKeyguardStateController;
    private final KeyguardStateController mKeyguardStateController;
@@ -361,6 +373,8 @@ public final class NotificationPanelViewController implements Dumpable {
    private CentralSurfaces mCentralSurfaces;
    private CentralSurfaces mCentralSurfaces;
    private HeadsUpManagerPhone mHeadsUpManager;
    private HeadsUpManagerPhone mHeadsUpManager;
    private float mExpandedHeight = 0;
    private float mExpandedHeight = 0;
    /** The current squish amount for the predictive back animation */
    private float mCurrentBackProgress = 0.0f;
    private boolean mTracking;
    private boolean mTracking;
    private boolean mHintAnimationRunning;
    private boolean mHintAnimationRunning;
    private KeyguardBottomAreaView mKeyguardBottomArea;
    private KeyguardBottomAreaView mKeyguardBottomArea;
@@ -815,6 +829,7 @@ public final class NotificationPanelViewController implements Dumpable {
        mShadeHeaderController = shadeHeaderController;
        mShadeHeaderController = shadeHeaderController;
        mLayoutInflater = layoutInflater;
        mLayoutInflater = layoutInflater;
        mFeatureFlags = featureFlags;
        mFeatureFlags = featureFlags;
        mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE);
        mFalsingCollector = falsingCollector;
        mFalsingCollector = falsingCollector;
        mPowerManager = powerManager;
        mPowerManager = powerManager;
        mWakeUpCoordinator = coordinator;
        mWakeUpCoordinator = coordinator;
@@ -1951,6 +1966,14 @@ public final class NotificationPanelViewController implements Dumpable {
            if (mFixedDuration != NO_FIXED_DURATION) {
            if (mFixedDuration != NO_FIXED_DURATION) {
                animator.setDuration(mFixedDuration);
                animator.setDuration(mFixedDuration);
            }
            }

            // Reset Predictive Back animation's transform after Shade is completely hidden.
            animator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    resetBackTransformation();
                }
            });
        }
        }
        animator.addListener(new AnimatorListenerAdapter() {
        animator.addListener(new AnimatorListenerAdapter() {
            private boolean mCancelled;
            private boolean mCancelled;
@@ -2175,6 +2198,53 @@ public final class NotificationPanelViewController implements Dumpable {
        }
        }
    }
    }


    /**
     * When the back gesture triggers a fully-expanded shade --> QQS shade collapse transition,
     * the expansionFraction goes down from 1.0 --> 0.0 (collapsing), so the current "squish" amount
     * (mCurrentBackProgress) must be un-applied from various UI elements in tandem, such that,
     * as the shade ends up in its half-expanded state (with QQS above), it is back at 100% scale.
     * Without this, the shade would collapse, and stay squished.
     */
    public void adjustBackAnimationScale(float expansionFraction) {
        if (expansionFraction > 0.0f) { // collapsing
            float animatedFraction = expansionFraction * mCurrentBackProgress;
            applyBackScaling(animatedFraction);
        } else {
            // collapsed! reset, so that if we re-expand shade, it won't start off "squished"
            mCurrentBackProgress = 0;
        }
    }

    //TODO(b/270981268): allow cancelling back animation mid-flight
    /** Called when Back gesture has been committed (i.e. a back event has definitely occurred) */
    public void onBackPressed() {
        closeQsIfPossible();
    }
    /** Sets back progress. */
    public void onBackProgressed(float progressFraction) {
        // TODO: non-linearly transform progress fraction into squish amount (ease-in, linear out)
        mCurrentBackProgress = progressFraction;
        applyBackScaling(progressFraction);
    }

    /** Resets back progress. */
    public void resetBackTransformation() {
        mCurrentBackProgress = 0.0f;
        applyBackScaling(0.0f);
    }

    /** Scales multiple elements in tandem to achieve the illusion of the QS+Shade shrinking
     *  as a single visual element (used by the Predictive Back Gesture preview animation).
     *  fraction = 0 implies "no scaling", and 1 means "scale down to minimum size (90%)".
     */
    public void applyBackScaling(float fraction) {
        if (mNotificationContainerParent == null) {
            return;
        }
        float scale = MathUtils.lerp(1.0f, SHADE_BACK_ANIM_MIN_SCALE, fraction);
        mNotificationContainerParent.applyBackScaling(scale, mSplitShadeEnabled);
        mScrimController.applyBackScaling(scale);
    }
    /** */
    /** */
    public float getLockscreenShadeDragProgress() {
    public float getLockscreenShadeDragProgress() {
        // mTransitioningToFullShadeProgress > 0 means we're doing regular lockscreen to shade
        // mTransitioningToFullShadeProgress > 0 means we're doing regular lockscreen to shade
@@ -4851,6 +4921,11 @@ public final class NotificationPanelViewController implements Dumpable {


            switch (event.getActionMasked()) {
            switch (event.getActionMasked()) {
                case MotionEvent.ACTION_DOWN:
                case MotionEvent.ACTION_DOWN:
                    if (QuickStepContract.ALLOW_BACK_GESTURE_IN_SHADE && mAnimateBack) {
                        // Cache the gesture insets now, so we can quickly query them during
                        // ACTION_MOVE and decide whether to intercept events for back gesture anim.
                        mQsController.updateGestureInsetsCache();
                    }
                    mShadeLog.logMotionEvent(event, "onTouch: down action");
                    mShadeLog.logMotionEvent(event, "onTouch: down action");
                    startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
                    startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
                    mMinExpandHeight = 0.0f;
                    mMinExpandHeight = 0.0f;
@@ -4900,6 +4975,12 @@ public final class NotificationPanelViewController implements Dumpable {
                    }
                    }
                    break;
                    break;
                case MotionEvent.ACTION_MOVE:
                case MotionEvent.ACTION_MOVE:
                    // If the shade is half-collapsed, a horizontal swipe inwards from L/R edge
                    // must be routed to the back gesture (which shows a preview animation).
                    if (QuickStepContract.ALLOW_BACK_GESTURE_IN_SHADE && mAnimateBack
                            && mQsController.shouldBackBypassQuickSettings(x)) {
                        return false;
                    }
                    if (isFullyCollapsed()) {
                    if (isFullyCollapsed()) {
                        // If panel is fully collapsed, reset haptic effect before adding movement.
                        // If panel is fully collapsed, reset haptic effect before adding movement.
                        mHasVibratedOnOpen = false;
                        mHasVibratedOnOpen = false;
+41 −0
Original line number Original line Diff line number Diff line
@@ -20,6 +20,7 @@ import android.app.Fragment;
import android.content.Context;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.AttributeSet;
import android.view.View;
import android.view.View;
import android.view.WindowInsets;
import android.view.WindowInsets;
@@ -55,6 +56,13 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout
    private QS mQs;
    private QS mQs;
    private View mQSContainer;
    private View mQSContainer;


    /**
     *  These are used to compute the bounding box containing the shade and the notification scrim,
     *  which is then used to drive the Back gesture animation.
     */
    private final Rect mUpperRect = new Rect();
    private final Rect mBoundingBoxRect = new Rect();

    @Nullable
    @Nullable
    private Consumer<Configuration> mConfigurationChangedListener;
    private Consumer<Configuration> mConfigurationChangedListener;


@@ -172,4 +180,37 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout
    public void applyConstraints(ConstraintSet constraintSet) {
    public void applyConstraints(ConstraintSet constraintSet) {
        constraintSet.applyTo(this);
        constraintSet.applyTo(this);
    }
    }

    /**
     *  Scale multiple elements in tandem, for the predictive back animation.
     *  This is how the Shade responds to the Back gesture (by scaling).
     *  Without the common center, individual elements will scale about their respective centers.
     *  Scaling the entire NotificationsQuickSettingsContainer will also resize the shade header
     *  (which we don't want).
     */
    public void applyBackScaling(float scale, boolean usingSplitShade) {
        if (mStackScroller == null || mQSContainer == null) {
            return;
        }

        mQSContainer.getBoundsOnScreen(mUpperRect);
        mStackScroller.getBoundsOnScreen(mBoundingBoxRect);
        mBoundingBoxRect.union(mUpperRect);

        float cx = mBoundingBoxRect.centerX();
        float cy = mBoundingBoxRect.centerY();

        mQSContainer.setPivotX(cx);
        mQSContainer.setPivotY(cy);
        mQSContainer.setScaleX(scale);
        mQSContainer.setScaleY(scale);

        // When in large-screen split-shade mode, the notification stack scroller scales correctly
        // only if the pivot point is at the left edge of the screen (because of its dimensions).
        // When not in large-screen split-shade mode, we can scale correctly via the (cx,cy) above.
        mStackScroller.setPivotX(usingSplitShade ? 0.0f : cx);
        mStackScroller.setPivotY(cy);
        mStackScroller.setScaleX(scale);
        mStackScroller.setScaleY(scale);
    }
}
}
Loading