diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt index a9d4fdeae3970..e132209c35f5d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt @@ -25,6 +25,7 @@ import android.os.PowerManager import android.os.PowerManager.WAKE_REASON_GESTURE import android.os.SystemClock import android.view.MotionEvent +import android.view.VelocityTracker import android.view.ViewConfiguration import com.android.systemui.Gefingerpoken @@ -36,6 +37,7 @@ import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout +import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.ShadeController import javax.inject.Inject @@ -48,22 +50,31 @@ import kotlin.math.max @Singleton class PulseExpansionHandler @Inject constructor(context: Context, - private val mWakeUpCoordinator: NotificationWakeUpCoordinator) : Gefingerpoken { + private val wakeUpCoordinator: NotificationWakeUpCoordinator, + private val bypassController: KeyguardBypassController) : Gefingerpoken { companion object { private val RUBBERBAND_FACTOR_STATIC = 0.25f private val SPRING_BACK_ANIMATION_LENGTH_MS = 375 } private val mPowerManager: PowerManager? - private var mShadeController: ShadeController? = null + private lateinit var shadeController: ShadeController private val mMinDragDistance: Int private var mInitialTouchX: Float = 0.0f private var mInitialTouchY: Float = 0.0f - var isExpanding: Boolean = false - private set + private var isExpanding: Boolean = false + private set(value) { + val changed = field != value + field = value + bypassController.isPulseExpanding = value + if (changed && !value && !leavingLockscreen) { + bypassController.maybePerformPendingUnlock() + } + } + private var leavingLockscreen: Boolean = false private val mTouchSlop: Float - private var mExpansionCallback: ExpansionCallback? = null - private lateinit var mStackScroller: NotificationStackScrollLayout + private lateinit var expansionCallback: ExpansionCallback + private lateinit var stackScroller: NotificationStackScrollLayout private val mTemp2 = IntArray(2) private var mDraggedFarEnough: Boolean = false private var mStartingChild: ExpandableView? = null @@ -74,6 +85,7 @@ constructor(context: Context, private var mEmptyDragAmount: Float = 0.0f private var mWakeUpHeight: Float = 0.0f private var mReachedWakeUpHeight: Boolean = false + private var velocityTracker: VelocityTracker? = null private val isFalseTouch: Boolean get() = mFalsingManager.isFalseTouch @@ -91,9 +103,13 @@ constructor(context: Context, } private fun maybeStartExpansion(event: MotionEvent): Boolean { - if (!mPulsing) { + if (!wakeUpCoordinator.canShowPulsingHuns) { return false } + if (velocityTracker == null) { + velocityTracker = VelocityTracker.obtain() + } + velocityTracker!!.addMovement(event) val x = event.x val y = event.y @@ -101,6 +117,7 @@ constructor(context: Context, MotionEvent.ACTION_DOWN -> { mDraggedFarEnough = false isExpanding = false + leavingLockscreen = false mStartingChild = null mInitialTouchY = y mInitialTouchX = x @@ -114,29 +131,52 @@ constructor(context: Context, captureStartingChild(mInitialTouchX, mInitialTouchY) mInitialTouchY = y mInitialTouchX = x - mWakeUpHeight = mWakeUpCoordinator.getWakeUpHeight() + mWakeUpHeight = wakeUpCoordinator.getWakeUpHeight() mReachedWakeUpHeight = false return true } } + + MotionEvent.ACTION_UP -> { + recycleVelocityTracker(); + } + + MotionEvent.ACTION_CANCEL -> { + recycleVelocityTracker(); + } } return false } + private fun recycleVelocityTracker() { + velocityTracker?.recycle(); + velocityTracker = null + } + override fun onTouchEvent(event: MotionEvent): Boolean { if (!isExpanding) { return maybeStartExpansion(event) } + velocityTracker!!.addMovement(event) val y = event.y + val moveDistance = y - mInitialTouchY when (event.actionMasked) { - MotionEvent.ACTION_MOVE -> updateExpansionHeight(y - mInitialTouchY) - MotionEvent.ACTION_UP -> if (!mFalsingManager.isUnlockingDisabled && !isFalseTouch) { - finishExpansion() - } else { - cancelExpansion() + MotionEvent.ACTION_MOVE -> updateExpansionHeight(moveDistance) + MotionEvent.ACTION_UP -> { + velocityTracker!!.computeCurrentVelocity(1000 /* units */) + val canExpand = moveDistance > 0 && velocityTracker!!.getYVelocity() > -1000 + if (!mFalsingManager.isUnlockingDisabled && !isFalseTouch && canExpand) { + finishExpansion() + } else { + cancelExpansion() + } + recycleVelocityTracker() + } + MotionEvent.ACTION_CANCEL -> { + cancelExpansion() + recycleVelocityTracker() } - MotionEvent.ACTION_CANCEL -> cancelExpansion() } return isExpanding } @@ -147,12 +187,15 @@ constructor(context: Context, setUserLocked(mStartingChild!!, false) mStartingChild = null } + if (shadeController.isDozing) { + isWakingToShadeLocked = true + wakeUpCoordinator.willWakeUp = true + mPowerManager!!.wakeUp(SystemClock.uptimeMillis(), WAKE_REASON_GESTURE, + "com.android.systemui:PULSEDRAG") + } + shadeController.goToLockedShade(mStartingChild) + leavingLockscreen = true; isExpanding = false - isWakingToShadeLocked = true - mWakeUpCoordinator.willWakeUp = true - mPowerManager!!.wakeUp(SystemClock.uptimeMillis(), WAKE_REASON_GESTURE, - "com.android.systemui:PULSEDRAG") - mShadeController!!.goToLockedShade(mStartingChild) if (mStartingChild is ExpandableNotificationRow) { val row = mStartingChild as ExpandableNotificationRow? row!!.onExpandedByGesture(true /* userExpanded */) @@ -172,12 +215,12 @@ constructor(context: Context, expansionHeight = max(newHeight.toFloat(), expansionHeight) } else { val target = if (mReachedWakeUpHeight) mWakeUpHeight else 0.0f - mWakeUpCoordinator.setNotificationsVisibleForExpansion(height > target, + wakeUpCoordinator.setNotificationsVisibleForExpansion(height > target, true /* animate */, true /* increaseSpeed */) expansionHeight = max(mWakeUpHeight, expansionHeight) } - val emptyDragAmount = mWakeUpCoordinator.setPulseHeight(expansionHeight) + val emptyDragAmount = wakeUpCoordinator.setPulseHeight(expansionHeight) setEmptyDragAmount(emptyDragAmount * RUBBERBAND_FACTOR_STATIC) } @@ -192,7 +235,7 @@ constructor(context: Context, private fun setEmptyDragAmount(amount: Float) { mEmptyDragAmount = amount - mExpansionCallback!!.setEmptyDragAmount(amount) + expansionCallback.setEmptyDragAmount(amount) } private fun reset(child: ExpandableView) { @@ -234,7 +277,7 @@ constructor(context: Context, } else { resetClock() } - mWakeUpCoordinator.setNotificationsVisibleForExpansion(false /* visible */, + wakeUpCoordinator.setNotificationsVisibleForExpansion(false /* visible */, true /* animate */, false /* increaseSpeed */) isExpanding = false @@ -243,21 +286,21 @@ constructor(context: Context, private fun findView(x: Float, y: Float): ExpandableView? { var totalX = x var totalY = y - mStackScroller.getLocationOnScreen(mTemp2) + stackScroller.getLocationOnScreen(mTemp2) totalX += mTemp2[0].toFloat() totalY += mTemp2[1].toFloat() - val childAtRawPosition = mStackScroller.getChildAtRawPosition(totalX, totalY) + val childAtRawPosition = stackScroller.getChildAtRawPosition(totalX, totalY) return if (childAtRawPosition != null && childAtRawPosition.isContentExpandable) { childAtRawPosition } else null } - fun setUp(notificationStackScroller: NotificationStackScrollLayout, - expansionCallback: ExpansionCallback, - shadeController: ShadeController) { - mExpansionCallback = expansionCallback - mShadeController = shadeController - mStackScroller = notificationStackScroller + fun setUp(stackScroller: NotificationStackScrollLayout, + expansionCallback: ExpansionCallback, + shadeController: ShadeController) { + this.expansionCallback = expansionCallback + this.shadeController = shadeController + this.stackScroller = stackScroller } fun setPulsing(pulsing: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt index bf25c455531bc..1f3675a37c750 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt @@ -89,6 +89,21 @@ class NotificationWakeUpCoordinator @Inject constructor( } } + /** + * True if we can show pulsing heads up notifications + */ + var canShowPulsingHuns: Boolean = false + private set + get() { + var canShow = pulsing + if (bypassController.bypassEnabled) { + // We also allow pulsing on the lock screen! + canShow = canShow || (mWakingUp || willWakeUp || fullyAwake) + && mStatusBarStateController.state == StatusBarState.KEYGUARD + } + return canShow + } + init { mHeadsUpManagerPhone.addListener(this) @@ -120,13 +135,7 @@ class NotificationWakeUpCoordinator @Inject constructor( private fun updateNotificationVisibility(animate: Boolean, increaseSpeed: Boolean) { // TODO: handle Lockscreen wakeup for bypass when we're not pulsing anymore var visible = mNotificationsVisibleForExpansion || mHeadsUpManagerPhone.hasNotifications() - var canShow = pulsing - if (bypassController.bypassEnabled) { - // We also allow pulsing on the lock screen! - canShow = canShow || (mWakingUp || willWakeUp || fullyAwake) - && mStatusBarStateController.state == StatusBarState.KEYGUARD - } - visible = visible && canShow + visible = visible && canShowPulsingHuns if (!visible && mNotificationsVisible && (mWakingUp || willWakeUp) && mDozeAmount != 0.0f) { // let's not make notifications invisible while waking up, otherwise the animation diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 35df7d920e29d..3f6144eb6f815 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -242,7 +242,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback { } } - private void startWakeAndUnlock(BiometricSourceType biometricSourceType) { + public void startWakeAndUnlock(BiometricSourceType biometricSourceType) { startWakeAndUnlock(calculateMode(biometricSourceType)); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt index 9bbe4bed7c0ee..56b64dfff7ac8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt @@ -33,20 +33,32 @@ class KeyguardBypassController { private val unlockMethodCache: UnlockMethodCache private val statusBarStateController: StatusBarStateController - /** - * If face unlock dismisses the lock screen or keeps user on keyguard for the current user. - */ + lateinit var unlockController: BiometricUnlockController + var isPulseExpanding = false + + /** + * If face unlock dismisses the lock screen or keeps user on keyguard for the current user. + */ var bypassEnabled: Boolean = false get() = field && unlockMethodCache.isUnlockingWithFacePossible private set - - lateinit var unlockController: BiometricUnlockController + /** + * The pending unlock type which is set if the bypass was blocked when it happened. + */ + private var pendingUnlockType: BiometricSourceType? = null @Inject constructor(context: Context, tunerService: TunerService, statusBarStateController: StatusBarStateController) { unlockMethodCache = UnlockMethodCache.getInstance(context) this.statusBarStateController = statusBarStateController + statusBarStateController.addCallback(object : StatusBarStateController.StateListener { + override fun onStateChanged(newState: Int) { + if (newState != StatusBarState.KEYGUARD) { + pendingUnlockType = null; + } + } + }) val faceManager = context.getSystemService(FaceManager::class.java) if (faceManager?.isHardwareDetected != true) { return @@ -72,11 +84,30 @@ class KeyguardBypassController { * @return false if we can not wake and unlock right now */ fun onBiometricAuthenticated(biometricSourceType: BiometricSourceType): Boolean { - if (bypassEnabled && statusBarStateController.state != StatusBarState.KEYGUARD) { - // We're bypassing but not actually on the lockscreen, the user should decide when - // to unlock - return false + if (bypassEnabled) { + if (statusBarStateController.state != StatusBarState.KEYGUARD) { + // We're bypassing but not actually on the lockscreen, the user should decide when + // to unlock + return false + } + if (isPulseExpanding) { + pendingUnlockType = biometricSourceType + return false + } } return true } + + fun maybePerformPendingUnlock() { + if (pendingUnlockType != null) { + if (onBiometricAuthenticated(pendingUnlockType!!)) { + unlockController.startWakeAndUnlock(pendingUnlockType) + pendingUnlockType = null + } + } + } + + fun onStartedGoingToSleep() { + pendingUnlockType = null + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 56a61546fabad..626e62ecba681 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -213,6 +213,8 @@ public class NotificationPanelView extends PanelView implements private int mNotificationsHeaderCollideDistance; private int mUnlockMoveDistance; private float mEmptyDragAmount; + private float mDownX; + private float mDownY; private final KeyguardClockPositionAlgorithm mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm(); @@ -902,7 +904,8 @@ public class NotificationPanelView extends PanelView implements MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1); return true; } - if (mPulseExpansionHandler.onInterceptTouchEvent(event)) { + if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0) + && mPulseExpansionHandler.onInterceptTouchEvent(event)) { return true; } @@ -1003,6 +1006,8 @@ public class NotificationPanelView extends PanelView implements mOnlyAffordanceInThisMotion = false; mQsTouchAboveFalsingThreshold = mQsFullyExpanded; mDozingOnDown = isDozing(); + mDownX = event.getX(); + mDownY = event.getY(); mCollapsedOnDown = isFullyCollapsed(); mListenForHeadsUp = mCollapsedOnDown && mHeadsUpManager.hasPinnedHeadsUp(); } @@ -1076,7 +1081,8 @@ public class NotificationPanelView extends PanelView implements || event.getAction() == MotionEvent.ACTION_CANCEL) { mBlockingExpansionForCurrentTouch = false; } - if (!mIsExpanding && mPulseExpansionHandler.onTouchEvent(event)) { + if (!mIsExpanding && !shouldQuickSettingsIntercept(mDownX, mDownY, 0) + && mPulseExpansionHandler.onTouchEvent(event)) { // We're expanding all the other ones shouldn't get this anymore return true; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 43e7a7b809075..09024842a586b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -786,6 +786,7 @@ public class StatusBar extends SystemUI implements DemoMode, inflateStatusBarWindow(context); mStatusBarWindow.setService(this); + mStatusBarWindow.setBypassController(mKeyguardBypassController); mStatusBarWindow.setOnTouchListener(getStatusBarWindowTouchListener()); // TODO: Deal with the ugliness that comes from having some of the statusbar broken out @@ -3622,6 +3623,7 @@ public class StatusBar extends SystemUI implements DemoMode, notifyHeadsUpGoingToSleep(); dismissVolumeDialog(); mWakeUpCoordinator.setFullyAwake(false); + mKeyguardBypassController.onStartedGoingToSleep(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java index de266592533d4..9417295b35214 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java @@ -149,6 +149,7 @@ public class StatusBarWindowView extends FrameLayout { * events manually as it's outside of the regular view bounds. */ private boolean mExpandingBelowNotch; + private KeyguardBypassController mBypassController; public StatusBarWindowView(Context context, AttributeSet attrs) { super(context, attrs); @@ -417,6 +418,7 @@ public class StatusBarWindowView extends FrameLayout { if (mNotificationPanel.isFullyExpanded() && mStatusBarStateController.getState() == StatusBarState.KEYGUARD && !mService.isBouncerShowing() + && !mBypassController.getBypassEnabled() && !mService.isDozing()) { intercept = mDragDownHelper.onInterceptTouchEvent(ev); } @@ -439,7 +441,8 @@ public class StatusBarWindowView extends FrameLayout { if (mService.isDozing()) { handled = !mService.isPulsing(); } - if ((mStatusBarStateController.getState() == StatusBarState.KEYGUARD && !handled) + if ((mStatusBarStateController.getState() == StatusBarState.KEYGUARD && !handled + && !mBypassController.getBypassEnabled()) || mDragDownHelper.isDraggingDown()) { // we still want to finish our drag down gesture when locking the screen handled = mDragDownHelper.onTouchEvent(ev); @@ -518,6 +521,10 @@ public class StatusBarWindowView extends FrameLayout { } } + public void setBypassController(KeyguardBypassController bypassController) { + mBypassController = bypassController; + } + public class LayoutParams extends FrameLayout.LayoutParams { public boolean ignoreRightInset; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java index 747411adedcee..7742023a23b85 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java @@ -118,7 +118,8 @@ public class NotificationPanelViewTest extends SysuiTestCase { mock(HeadsUpManagerPhone.class), new StatusBarStateControllerImpl(), bypassController); - PulseExpansionHandler expansionHandler = new PulseExpansionHandler(mContext, coordinator); + PulseExpansionHandler expansionHandler = new PulseExpansionHandler(mContext, coordinator, + bypassController); mNotificationPanelView = new TestableNotificationPanelView(coordinator, expansionHandler, bypassController); mNotificationPanelView.setHeadsUpManager(mHeadsUpManager);