Loading packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +12 −3 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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, Loading Loading @@ -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; } } Loading packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java +55 −3 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; } } /** /** Loading Loading @@ -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. */ */ Loading Loading @@ -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); Loading @@ -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); Loading packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java +18 −0 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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); } } } } Loading Loading @@ -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; Loading packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +81 −0 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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; Loading Loading @@ -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; Loading packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java +41 −0 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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
packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +12 −3 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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, Loading Loading @@ -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; } } Loading
packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java +55 −3 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; } } /** /** Loading Loading @@ -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. */ */ Loading Loading @@ -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); Loading @@ -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); Loading
packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java +18 −0 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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); } } } } Loading Loading @@ -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; Loading
packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +81 −0 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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; Loading Loading @@ -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; Loading
packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java +41 −0 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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); } } }