Merge "Notification Shade Animation for Back Gesture (udc-dev)" into udc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
773f6bd4e0
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user