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

This commit is contained in:
Rahul Banerjee
2023-02-28 05:08:43 +00:00
committed by Android (Google) Code Review
10 changed files with 400 additions and 29 deletions

View File

@@ -23,6 +23,7 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import android.annotation.IntDef;
import android.content.Context;
import android.content.res.Resources;
import android.os.SystemProperties;
import android.view.ViewConfiguration;
import android.view.WindowManagerPolicyConstants;
@@ -115,6 +116,9 @@ public class QuickStepContract {
public static final int SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE = 1 << 26;
// Device dreaming state
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)
@IntDef({SYSUI_STATE_SCREEN_PINNING,
@@ -243,9 +247,14 @@ public class QuickStepContract {
sysuiStateFlags &= ~SYSUI_STATE_NAV_BAR_HIDDEN;
}
// Disable when in immersive, or the notifications are interactive
int disableFlags = SYSUI_STATE_NAV_BAR_HIDDEN
| SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
int disableFlags = SYSUI_STATE_NAV_BAR_HIDDEN | 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;
}

View File

@@ -27,6 +27,7 @@ import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Xfermode;
import android.graphics.drawable.Drawable;
import android.view.animation.DecelerateInterpolator;
@@ -41,7 +42,11 @@ import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
public class ScrimDrawable extends Drawable {
private static final String TAG = "ScrimDrawable";
private boolean mShouldUseLargeScreenSize;
private final Paint mPaint;
private final Path mPath = new Path();
private final RectF mBoundsRectF = new RectF();
private int mAlpha = 255;
private int mMainColor;
private ValueAnimator mColorAnimation;
@@ -49,11 +54,13 @@ public class ScrimDrawable extends Drawable {
private float mCornerRadius;
private ConcaveInfo mConcaveInfo;
private int mBottomEdgePosition;
private float mBottomEdgeRadius = -1;
private boolean mCornerRadiusEnabled;
public ScrimDrawable() {
mPaint = new Paint();
mPaint.setStyle(Paint.Style.FILL);
mShouldUseLargeScreenSize = false;
}
/**
@@ -133,6 +140,10 @@ public class ScrimDrawable extends Drawable {
return PixelFormat.TRANSLUCENT;
}
public void setShouldUseLargeScreenSize(boolean v) {
mShouldUseLargeScreenSize = v;
}
/**
* Corner radius used by either concave or convex corners.
*/
@@ -191,6 +202,10 @@ public class ScrimDrawable extends Drawable {
invalidateSelf();
}
public void setBottomEdgeRadius(float radius) {
mBottomEdgeRadius = radius;
}
@Override
public void draw(@NonNull Canvas canvas) {
mPaint.setColor(mMainColor);
@@ -198,9 +213,46 @@ public class ScrimDrawable extends Drawable {
if (mConcaveInfo != null) {
drawConcave(canvas);
} else if (mCornerRadiusEnabled && mCornerRadius > 0) {
canvas.drawRoundRect(getBounds().left, getBounds().top, getBounds().right,
getBounds().bottom,
/* x radius*/ mCornerRadius, /* y radius*/ mCornerRadius, mPaint);
float topEdgeRadius = mCornerRadius;
float bottomEdgeRadius = mBottomEdgeRadius == -1.0 ? mCornerRadius : mBottomEdgeRadius;
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 {
canvas.drawRect(getBounds().left, getBounds().top, getBounds().right,
getBounds().bottom, mPaint);

View File

@@ -20,6 +20,7 @@ import static java.lang.Float.isNaN;
import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PorterDuff;
@@ -37,6 +38,7 @@ import androidx.core.graphics.ColorUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.systemui.util.LargeScreenUtils;
import java.util.concurrent.Executor;
@@ -102,6 +104,13 @@ public class ScrimView extends View {
@Override
protected void onDraw(Canvas canvas) {
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);
}
}
@@ -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
Drawable getDrawable() {
return mDrawable;

View File

@@ -293,7 +293,19 @@ public final class NotificationPanelViewController implements Dumpable {
* custom clock animation is in use.
*/
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 Resources mResources;
private final KeyguardStateController mKeyguardStateController;
@@ -361,6 +373,8 @@ public final class NotificationPanelViewController implements Dumpable {
private CentralSurfaces mCentralSurfaces;
private HeadsUpManagerPhone mHeadsUpManager;
private float mExpandedHeight = 0;
/** The current squish amount for the predictive back animation */
private float mCurrentBackProgress = 0.0f;
private boolean mTracking;
private boolean mHintAnimationRunning;
private KeyguardBottomAreaView mKeyguardBottomArea;
@@ -815,6 +829,7 @@ public final class NotificationPanelViewController implements Dumpable {
mShadeHeaderController = shadeHeaderController;
mLayoutInflater = layoutInflater;
mFeatureFlags = featureFlags;
mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE);
mFalsingCollector = falsingCollector;
mPowerManager = powerManager;
mWakeUpCoordinator = coordinator;
@@ -1951,6 +1966,14 @@ public final class NotificationPanelViewController implements Dumpable {
if (mFixedDuration != NO_FIXED_DURATION) {
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() {
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() {
// mTransitioningToFullShadeProgress > 0 means we're doing regular lockscreen to shade
@@ -4851,6 +4921,11 @@ public final class NotificationPanelViewController implements Dumpable {
switch (event.getActionMasked()) {
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");
startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
mMinExpandHeight = 0.0f;
@@ -4900,6 +4975,12 @@ public final class NotificationPanelViewController implements Dumpable {
}
break;
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 panel is fully collapsed, reset haptic effect before adding movement.
mHasVibratedOnOpen = false;

View File

@@ -20,6 +20,7 @@ import android.app.Fragment;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.WindowInsets;
@@ -55,6 +56,13 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout
private QS mQs;
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
private Consumer<Configuration> mConfigurationChangedListener;
@@ -172,4 +180,37 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout
public void applyConstraints(ConstraintSet constraintSet) {
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);
}
}

View File

@@ -31,6 +31,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.app.Fragment;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.Region;
import android.util.Log;
@@ -40,6 +41,9 @@ import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
@@ -63,6 +67,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.shade.transition.ShadeTransitionController;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -83,10 +88,10 @@ import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.LargeScreenUtils;
import javax.inject.Inject;
import dagger.Lazy;
import javax.inject.Inject;
/** Handles QuickSettings touch handling, expansion and animation state
* TODO (b/264460656) make this dumpable
*/
@@ -222,6 +227,13 @@ public class QuickSettingsController {
*/
private boolean mAnimatorExpand;
/**
* The gesture inset currently in effect -- used to decide whether a back gesture should
* receive a horizontal swipe inwards from the left/right vertical edge of the screen.
* We cache this on ACTION_DOWN, and query it during both ACTION_DOWN and ACTION_MOVE events.
*/
private Insets mCachedGestureInsets;
/**
* The amount of progress we are currently in if we're transitioning to the full shade.
* 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
@@ -406,6 +418,7 @@ public class QuickSettingsController {
mQuickQsHeaderHeight = mLargeScreenShadeHeaderHeight;
mEnableClipping = mResources.getBoolean(R.bool.qs_enable_clipping);
updateGestureInsetsCache();
}
// TODO (b/265054088): move this and others to a CoreStartable
@@ -469,6 +482,26 @@ public class QuickSettingsController {
|| touchX > mQsFrame.getX() + mQsFrame.getWidth();
}
/**
* Computes (and caches) the gesture insets for the current window. Intended to be called
* on ACTION_DOWN, and safely queried repeatedly thereafter during ACTION_MOVE events.
*/
public void updateGestureInsetsCache() {
WindowManager wm = this.mPanelView.getContext().getSystemService(WindowManager.class);
WindowMetrics windowMetrics = wm.getCurrentWindowMetrics();
mCachedGestureInsets = windowMetrics.getWindowInsets().getInsets(
WindowInsets.Type.systemGestures());
}
/**
* Returns whether x coordinate lies in the vertical edges of the screen
* (the only place where a back gesture can be initiated).
*/
public boolean shouldBackBypassQuickSettings(float touchX) {
return (touchX < mCachedGestureInsets.left)
|| (touchX > mKeyguardStatusBar.getWidth() - mCachedGestureInsets.right);
}
/** Returns whether touch is within QS area */
private boolean isTouchInQsArea(float x, float y) {
if (isSplitShadeAndTouchXOutsideQs(x)) {
@@ -926,6 +959,10 @@ public class QuickSettingsController {
getHeaderTranslation(),
squishiness
);
if (QuickStepContract.ALLOW_BACK_GESTURE_IN_SHADE
&& mPanelViewControllerLazy.get().mAnimateBack) {
mPanelViewControllerLazy.get().adjustBackAnimationScale(adjustedExpansionFraction);
}
mMediaHierarchyManager.setQsExpansion(qsExpansionFraction);
int qsPanelBottomY = calculateBottomPosition(qsExpansionFraction);
mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY);
@@ -1113,6 +1150,7 @@ public class QuickSettingsController {
float screenCornerRadius = mRecordingController.isRecording() ? 0 : mScreenCornerRadius;
radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius,
Math.min(top / (float) mScrimCornerRadius, 1f));
mScrimController.setNotificationBottomRadius(radius);
}
if (isQsFragmentCreated()) {
float qsTranslation = 0;
@@ -1505,18 +1543,31 @@ public class QuickSettingsController {
}
private void handleDown(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN
&& shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) {
mFalsingCollector.onQsDown();
mShadeLog.logMotionEvent(event, "handleQsDown: down action, QS tracking enabled");
mTracking = true;
onExpansionStarted();
mInitialHeightOnTouch = mExpansionHeight;
mInitialTouchY = event.getY();
mInitialTouchX = event.getX();
// TODO (b/265193930): remove dependency on NPVC
// If we interrupt an expansion gesture here, make sure to update the state correctly.
mPanelViewControllerLazy.get().notifyExpandingFinished();
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
// When the shade is fully-expanded, an inward swipe from the L/R edge should first
// allow the back gesture's animation to preview the shade animation (if enabled).
// (swipes starting closer to the center of the screen will not be affected)
if (QuickStepContract.ALLOW_BACK_GESTURE_IN_SHADE
&& mPanelViewControllerLazy.get().mAnimateBack) {
updateGestureInsetsCache();
if (shouldBackBypassQuickSettings(event.getX())) {
return;
}
}
if (shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) {
mFalsingCollector.onQsDown();
mShadeLog.logMotionEvent(event,
"handleQsDown: down action, QS tracking enabled");
mTracking = true;
onExpansionStarted();
mInitialHeightOnTouch = mExpansionHeight;
mInitialTouchY = event.getY();
mInitialTouchX = event.getX();
// TODO (b/265193930): remove dependency on NPVC
// If we interrupt an expansion gesture here, make sure to update the state
// correctly.
mPanelViewControllerLazy.get().notifyExpandingFinished();
}
}
}

View File

@@ -104,6 +104,8 @@ import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
import android.widget.DateTimeView;
import android.window.BackEvent;
import android.window.OnBackAnimationCallback;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
@@ -508,6 +510,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
private final BrightnessSliderController.Factory mBrightnessSliderFactory;
private final FeatureFlags mFeatureFlags;
private final boolean mAnimateBack;
private final FragmentService mFragmentService;
private final ScreenOffAnimationController mScreenOffAnimationController;
private final WallpaperController mWallpaperController;
@@ -654,6 +657,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private final InteractionJankMonitor mJankMonitor;
/** Existing callback that handles back gesture invoked for the Shade. */
private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
if (DEBUG) {
Log.d(TAG, "mOnBackInvokedCallback() called");
@@ -661,6 +665,33 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
onBackPressed();
};
private boolean shouldBackBeHandled() {
return (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED
&& !isBouncerShowingOverDream());
}
/**
* New callback that handles back gesture invoked, cancel, progress
* and provides feedback via Shade animation.
* (enabled via the WM_SHADE_ANIMATE_BACK_GESTURE flag)
*/
private final OnBackAnimationCallback mOnBackAnimationCallback = new OnBackAnimationCallback() {
@Override
public void onBackInvoked() {
onBackPressed();
}
@Override
public void onBackProgressed(BackEvent event) {
if (shouldBackBeHandled()) {
if (mNotificationPanelViewController.canPanelBeCollapsed()) {
float fraction = event.getProgress();
mNotificationPanelViewController.onBackProgressed(fraction);
}
}
}
};
/**
* Public constructor for CentralSurfaces.
*
@@ -882,6 +913,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI)) {
mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true);
}
// Based on teamfood flag, enable predictive back animation for the Shade.
mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE);
}
private void initBubbles(Bubbles bubbles) {
@@ -2706,7 +2739,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
if (viewRootImpl != null) {
viewRootImpl.getOnBackInvokedDispatcher()
.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT,
mOnBackInvokedCallback);
mAnimateBack ? mOnBackAnimationCallback
: mOnBackInvokedCallback);
mIsBackCallbackRegistered = true;
if (DEBUG) Log.d(TAG, "is now VISIBLE to user AND callback registered");
}
@@ -2721,7 +2755,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
ViewRootImpl viewRootImpl = getViewRootImpl();
if (viewRootImpl != null) {
viewRootImpl.getOnBackInvokedDispatcher()
.unregisterOnBackInvokedCallback(mOnBackInvokedCallback);
.unregisterOnBackInvokedCallback(
mAnimateBack ? mOnBackAnimationCallback
: mOnBackInvokedCallback);
mIsBackCallbackRegistered = false;
if (DEBUG) Log.d(TAG, "is NOT VISIBLE to user, AND callback unregistered");
}
@@ -3253,9 +3289,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
if (mNotificationPanelViewController.closeUserSwitcherIfOpen()) {
return true;
}
if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED
&& !isBouncerShowingOverDream()) {
if (shouldBackBeHandled()) {
if (mNotificationPanelViewController.canPanelBeCollapsed()) {
// this is the Shade dismiss animation, so make sure QQS closes when it ends.
mNotificationPanelViewController.onBackPressed();
mShadeController.animateCollapseShade();
}
return true;

View File

@@ -345,9 +345,16 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
}
}
/**
* Sets corner radius of scrims.
*/
// TODO(b/270984686) recompute scrim height accurately, based on shade contents.
/** Set corner radius of the bottom edge of the Notification scrim. */
public void setNotificationBottomRadius(float radius) {
if (mNotificationsScrim == null) {
return;
}
mNotificationsScrim.setBottomEdgeRadius(radius);
}
/** Sets corner radius of scrims. */
public void setScrimCornerRadius(int radius) {
if (mScrimBehind == null || mNotificationsScrim == null) {
return;
@@ -511,6 +518,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
scheduleUpdate();
}
/** This is used by the predictive back gesture animation to scale the Shade. */
public void applyBackScaling(float scale) {
mNotificationsScrim.setScaleX(scale);
mNotificationsScrim.setScaleY(scale);
}
public void onTrackingStarted() {
mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
if (!mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) {

View File

@@ -78,6 +78,25 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
DejankUtils.setImmediate(true);
}
/**
* When the Back gesture starts (progress 0%), the scrim will stay at 100% scale (1.0f).
*/
@Test
public void testBackGesture_min_scrimAtMaxScale() {
mNotificationPanelViewController.onBackProgressed(0.0f);
verify(mScrimController).applyBackScaling(1.0f);
}
/**
* When the Back gesture is at max (progress 100%), the scrim will be scaled to its minimum.
*/
@Test
public void testBackGesture_max_scrimAtMinScale() {
mNotificationPanelViewController.onBackProgressed(1.0f);
verify(mScrimController).applyBackScaling(
NotificationPanelViewController.SHADE_BACK_ANIM_MIN_SCALE);
}
@Test
public void onNotificationHeightChangeWhileOnKeyguardWillComputeMaxKeyguardNotifications() {
mStatusBarStateController.setState(KEYGUARD);

View File

@@ -77,6 +77,8 @@ import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.window.BackEvent;
import android.window.OnBackAnimationCallback;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
import android.window.WindowOnBackInvokedDispatcher;
@@ -182,6 +184,8 @@ import com.android.systemui.volume.VolumeComponent;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.startingsurface.StartingSurface;
import dagger.Lazy;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -194,8 +198,6 @@ import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.util.Optional;
import dagger.Lazy;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
@@ -333,6 +335,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI, false);
// Set default value to avoid IllegalStateException.
mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, false);
// For the Shade to respond to Back gesture, we must enable the event routing
mFeatureFlags.set(Flags.WM_SHADE_ALLOW_BACK_GESTURE, true);
// For the Shade to animate during the Back gesture, we must enable the animation flag.
mFeatureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true);
IThermalService thermalService = mock(IThermalService.class);
mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService,
@@ -855,6 +861,50 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
verify(mShadeController).animateCollapseShade();
}
/**
* When back progress is at 100%, the onBackProgressed animation driver inside
* NotificationPanelViewController should be invoked appropriately (with 1.0f passed in).
*/
@Test
public void testPredictiveBackAnimation_progressMaxScalesPanel() {
mCentralSurfaces.setNotificationShadeWindowViewController(
mNotificationShadeWindowViewController);
mCentralSurfaces.handleVisibleToUserChanged(true);
verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
mOnBackInvokedCallback.capture());
OnBackAnimationCallback onBackAnimationCallback =
(OnBackAnimationCallback) (mOnBackInvokedCallback.getValue());
when(mNotificationPanelViewController.canPanelBeCollapsed()).thenReturn(true);
BackEvent fakeSwipeInFromLeftEdge = new BackEvent(20.0f, 100.0f, 1.0f, BackEvent.EDGE_LEFT);
onBackAnimationCallback.onBackProgressed(fakeSwipeInFromLeftEdge);
verify(mNotificationPanelViewController).onBackProgressed(eq(1.0f));
}
/**
* When back progress is at 0%, the onBackProgressed animation driver inside
* NotificationPanelViewController should be invoked appropriately (with 0.0f passed in).
*/
@Test
public void testPredictiveBackAnimation_progressMinScalesPanel() {
mCentralSurfaces.setNotificationShadeWindowViewController(
mNotificationShadeWindowViewController);
mCentralSurfaces.handleVisibleToUserChanged(true);
verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
mOnBackInvokedCallback.capture());
OnBackAnimationCallback onBackAnimationCallback =
(OnBackAnimationCallback) (mOnBackInvokedCallback.getValue());
when(mNotificationPanelViewController.canPanelBeCollapsed()).thenReturn(true);
BackEvent fakeSwipeInFromLeftEdge = new BackEvent(20.0f, 10.0f, 0.0f, BackEvent.EDGE_LEFT);
onBackAnimationCallback.onBackProgressed(fakeSwipeInFromLeftEdge);
verify(mNotificationPanelViewController).onBackProgressed(eq(0.0f));
}
@Test
public void testPanelOpenForHeadsUp() {
when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);