2 上滑触摸事件
2.1 Touch down事件
2.2 Touch move事件
2.3 Touch up事件
用户抬起手指,产生touch up事件,PanelView接收到这个事件后会调用endMotionEvent,如果手指从down到up之间移动的距离达到一定阈值会调用onTrackingStopped,从而调出解锁界面;
//PanelViewController
private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) {
final boolean onKeyguard =
mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
final boolean expand;
if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
// If we get a cancel, put the shade back to the state it was in when the gesture
// started
if (onKeyguard) {
expand = true;
} else {
expand = !mPanelClosedOnDown;
}
} else {
expand = flingExpands(vel, vectorVel, x, y);
}
...
fling(vel, expand, isFalseTouch(x, y, interactionType));
onTrackingStopped(expand);
mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
if (mUpdateFlingOnLayout) {
mUpdateFlingVelocity = vel;
}
...
}
这里复位mTracking,并将onTrackingStopped回调给
protected void onTrackingStopped(boolean expand) {
mTracking = false;
mBar.onTrackingStopped(expand);
notifyBarPanelExpansionChanged();
}
//PanelBar
public void onTrackingStopped(boolean expand) {
mTracking = false;
}
3 上滑解锁流程
3.1 fling动画更新panelView高度
上滑解锁锁屏,PanelView的expansion fraction从 1f 变成0f,锁屏时钟,通知等从上部慢慢消失。
在endMotionEvent事件里面,调用了PanelViewController的fling动画
//PanelViewController
protected void fling(float vel, boolean expand, float collapseSpeedUpFactor,
boolean expandBecauseOfFalsing) {
cancelPeek();
float target = expand ? getMaxPanelHeight() : 0;
if (!expand) {
mClosing = true;
}
flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
}
然后调用flingToHeight,走到createHeightAnimator里面
http://aospxref.com/android-13.0.0_r3/xref/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java#setExpandedHeightInternal
/**
* Create an animator that can also overshoot
*
* @param targetHeight the target height
* @param overshootAmount the amount of overshoot desired
*/
private ValueAnimator createHeightAnimator(float targetHeight, float overshootAmount) {
float startExpansion = mOverExpansion;
ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight);
animator.addUpdateListener(
animation -> {
if (overshootAmount > 0.0f
// Also remove the overExpansion when collapsing
|| (targetHeight == 0.0f && startExpansion != 0)) {
final float expansion = MathUtils.lerp(
startExpansion,
mPanelFlingOvershootAmount * overshootAmount,
Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
animator.getAnimatedFraction()));
setOverExpansionInternal(expansion, false /* isFromGesture */);
}
setExpandedHeightInternal((float) animation.getAnimatedValue());
});
return animator;
}
在fling动画走onUpdateAnimator里面调用到setExpandedHeightInternal,
public void setExpandedHeightInternal(float h) {
if (isNaN(h)) {
Log.wtf(TAG, "ExpandedHeight set to NaN");
}
mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
if (mExpandLatencyTracking && h != 0f) {
DejankUtils.postAfterTraversal(
() -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
mExpandLatencyTracking = false;
}
float maxPanelHeight = getMaxPanelHeight();
if (mHeightAnimator == null) {
// Split shade has its own overscroll logic
if (mTracking && !mInSplitShade) {
float overExpansionPixels = Math.max(0, h - maxPanelHeight);
setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */);
}
mExpandedHeight = Math.min(h, maxPanelHeight);
} else {
mExpandedHeight = h;
}
// If we are closing the panel and we are almost there due to a slow decelerating
// interpolator, abort the animation.
if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) {
mExpandedHeight = 0f;
if (mHeightAnimator != null) {
mHeightAnimator.end();
}
}
mExpansionDragDownAmountPx = h;
mExpandedFraction = Math.min(1f,
maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight);
onHeightUpdated(mExpandedHeight);
updatePanelExpansionAndVisibility();
});
}
最后调用updatePanelExpansionAndVisibility()
/**
* Updates the panel expansion and {@link PanelView} visibility if necessary.
*
* TODO(b/200063118): Could public calls to this method be replaced with calls to
* {@link #updateVisibility()}? That would allow us to make this method private.
*/
public void updatePanelExpansionAndVisibility() {
mPanelExpansionStateManager.onPanelExpansionChanged(
mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
updateVisibility();
}
updatePanelExpansionAndVisibility里面首先回调onPanelExpansionChanged,然后更新PanelView的可见性。
/** Update the visibility of {@link PanelView} if necessary. */
public void updateVisibility() {
mView.setVisibility(shouldPanelBeVisible() ? VISIBLE : INVISIBLE);
}
3.2 判断是否要走KeyguardDismiss流程
CentralSurfacesImpl收到onPanelExpansionChanged
http://aospxref.com/android-13.0.0_r3/xref/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java#1412
private void onPanelExpansionChanged(PanelExpansionChangeEvent event) {
1413 float fraction = event.getFraction();
1414 boolean tracking = event.getTracking();
1415 dispatchPanelExpansionForKeyguardDismiss(fraction, tracking);
1416
1417 if (fraction == 0 || fraction == 1) {
1418 if (getNavigationBarView() != null) {
1419 getNavigationBarView().onStatusBarPanelStateChanged();
1420 }
1421 if (getNotificationPanelViewController() != null) {
1422 getNotificationPanelViewController().updateSystemUiStateFlags();
1423 }
1424 }
1425 }
之后调用dispatchPanelExpansionForKeyguardDismiss走KeyguardDismiss流程
/**
* When swiping up to dismiss the lock screen, the panel expansion fraction goes from 1f to 0f.
* This results in the clock/notifications/other content disappearing off the top of the screen.
*
* We also use the expansion fraction to animate in the app/launcher surface from the bottom of
* the screen, 'pushing' off the notifications and other content. To do this, we dispatch the
* expansion fraction to the KeyguardViewMediator if we're in the process of dismissing the
* keyguard.
*/
private void dispatchPanelExpansionForKeyguardDismiss(float fraction, boolean trackingTouch) {
// Things that mean we're not swiping to dismiss the keyguard, and should ignore this
// expansion:
// - Keyguard isn't even visible.
// - Keyguard is occluded. Expansion changes here are the shade being expanded over the
// occluding activity.
// - Keyguard is visible, but can't be dismissed (swiping up will show PIN/password prompt).
// - The SIM is locked, you can't swipe to unlock. If the SIM is locked but there is no
// device lock set, canDismissLockScreen returns true even though you should not be able
// to dismiss the lock screen until entering the SIM PIN.
// - QS is expanded and we're swiping - swiping up now will hide QS, not dismiss the
// keyguard.
if (!isKeyguardShowing()
|| isOccluded()
|| !mKeyguardStateController.canDismissLockScreen()
|| mKeyguardViewMediator.isAnySimPinSecure()
|| (mNotificationPanelViewController.isQsExpanded() && trackingTouch)) {
return;
}
// Otherwise, we should let the keyguard know about this if we're tracking touch, or if we
// are already animating the keyguard dismiss (since we will need to either finish or cancel
// the animation).
if (trackingTouch
|| mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe()
|| mKeyguardUnlockAnimationController.isUnlockingWithSmartSpaceTransition()) {
mKeyguardStateController.notifyKeyguardDismissAmountChanged(
1f - fraction, trackingTouch);
}
}
这里走mKeyguardStateController.notifyKeyguardDismissAmountChanged(1f - fraction, trackingTouch)里面,然后走到KeyguardStateControllerImpl的notifyKeyguardDismissAmountChanged。
http://aospxref.com/android-13.0.0_r3/xref/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java#366
public void notifyKeyguardDismissAmountChanged(float dismissAmount,
boolean dismissingFromTouch) {
mDismissAmount = dismissAmount;
mDismissingFromTouch = dismissingFromTouch;
new ArrayList<>(mCallbacks).forEach(Callback::onKeyguardDismissAmountChanged);
}
之后回调onKeyguardDismissAmountChanged。这里回调到KeyguardUnlockAnimationController的
http://aospxref.com/android-13.0.0_r3/xref/frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt#591
override fun onKeyguardDismissAmountChanged() {
if (!willHandleUnlockAnimation()) {
return
}
if (keyguardViewController.isShowing && !playingCannedUnlockAnimation) {
showOrHideSurfaceIfDismissAmountThresholdsReached()
// If the surface is visible or it's about to be, start updating its appearance to
// reflect the new dismiss amount.
if ((keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
keyguardViewMediator.get()
.isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe) &&
!playingCannedUnlockAnimation) {
updateSurfaceBehindAppearAmount()
}
}
}
然后走到showOrHideSurfaceIfDismissAmountThresholdsReached,
/**
* Lets the KeyguardViewMediator know if the dismiss amount has crossed a threshold of interest,
* such as reaching the point in the dismiss swipe where we need to make the surface behind the
* keyguard visible.
*/
private fun showOrHideSurfaceIfDismissAmountThresholdsReached() {
if (!featureFlags.isEnabled(Flags.NEW_UNLOCK_SWIPE_ANIMATION)) {
return
}
// If we are playing the canned unlock animation, we flung away the keyguard to hide it and
// started a canned animation to show the surface behind the keyguard. The fling will cause
// panel height/dismiss amount updates, but we should ignore those updates here since the
// surface behind is already visible and animating.
if (playingCannedUnlockAnimation) {
return
}
if (!keyguardStateController.isShowing) {
return
}
val dismissAmount = keyguardStateController.dismissAmount
if (dismissAmount >= DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
!keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) {
keyguardViewMediator.get().showSurfaceBehindKeyguard()
} else if (dismissAmount < DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) {
// We're no longer past the threshold but we are showing the surface. Animate it
// out.
keyguardViewMediator.get().hideSurfaceBehindKeyguard()
fadeOutSurfaceBehind()
}
finishKeyguardExitRemoteAnimationIfReachThreshold()
}
走到最后keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(false /* cancelled */)
/**
* Called when we're done running the keyguard exit animation.
*
* This will call {@link #mSurfaceBehindRemoteAnimationFinishedCallback} to let WM know that
* we're done with the RemoteAnimation, actually hide the keyguard, and clean up state related
* to the keyguard exit animation.
*
* @param cancelled {@code true} if the animation was cancelled before it finishes.
*/
public void onKeyguardExitRemoteAnimationFinished(boolean cancelled) {
if (!mSurfaceBehindRemoteAnimationRunning && !mSurfaceBehindRemoteAnimationRequested) {
return;
}
// Block the panel from expanding, in case we were doing a swipe to dismiss gesture.
mKeyguardViewControllerLazy.get().blockPanelExpansionFromCurrentTouch();
final boolean wasShowing = mShowing;
InteractionJankMonitor.getInstance().end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
// Post layout changes to the next frame, so we don't hang at the end of the animation.
DejankUtils.postAfterTraversal(() -> {
onKeyguardExitFinished();
if (mKeyguardStateController.isDismissingFromSwipe() || wasShowing) {
mKeyguardUnlockAnimationControllerLazy.get().hideKeyguardViewAfterRemoteAnimation();
}
finishSurfaceBehindRemoteAnimation(cancelled);
mSurfaceBehindRemoteAnimationRequested = false;
// The remote animation is over, so we're not going away anymore.
mKeyguardStateController.notifyKeyguardGoingAway(false);
// Dispatch the callback on animation finishes.
mUpdateMonitor.dispatchKeyguardDismissAnimationFinished();
});
mKeyguardUnlockAnimationControllerLazy.get().notifyFinishedKeyguardExitAnimation(
cancelled);
}
private void onKeyguardExitFinished() {
// only play "unlock" noises if not on a call (since the incall UI
// disables the keyguard)
if (TelephonyManager.EXTRA_STATE_IDLE.equals(mPhoneState)) {
playSounds(false);
}
setShowingLocked(false);
mWakeAndUnlocking = false;
mDismissCallbackRegistry.notifyDismissSucceeded();
resetKeyguardDonePendingLocked();
mHideAnimationRun = false;
adjustStatusBarLocked();
sendUserPresentBroadcast();
}
在onKeyguardExitFinished中会播放解锁声音,然后reset一些变量。
private void onKeyguardExitFinished() {
// only play "unlock" noises if not on a call (since the incall UI
// disables the keyguard)
if (TelephonyManager.EXTRA_STATE_IDLE.equals(mPhoneState)) {
playSounds(false);
}
setShowingLocked(false);
mWakeAndUnlocking = false;
mDismissCallbackRegistry.notifyDismissSucceeded();
resetKeyguardDonePendingLocked();
mHideAnimationRun = false;
adjustStatusBarLocked();
sendUserPresentBroadcast();
}
然后调用KeyguardUnlockAnimationController对的hideKeyguardViewAfterRemoteAnimation走keyguard的hide流程。
/**
* Asks the keyguard view to hide, using the start time from the beginning of the remote
* animation.
*/
fun hideKeyguardViewAfterRemoteAnimation() {
if (keyguardViewController.isShowing) {
// Hide the keyguard, with no fade out since we animated it away during the unlock.
keyguardViewController.hide(
surfaceBehindRemoteAnimationStartTime,
0 /* fadeOutDuration */
)
} else {
Log.e(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
"showing. Ignoring...")
}
}
3.3 keyguard的hide流程
StatusBarKeyguardViewManager的hide里面,然后是mCentralSurfaces.hideKeyguard();
@Override
public boolean hideKeyguard() {
mStatusBarStateController.setKeyguardRequested(false);
return updateIsKeyguard();
}
@Override
public boolean updateIsKeyguard() {
return updateIsKeyguard(false /* forceStateChange */);
}
@Override
public boolean updateIsKeyguard(boolean forceStateChange) {
if (shouldBeKeyguard) {
if (mScreenOffAnimationController.isKeyguardShowDelayed()
|| (isGoingToSleep()
&& mScreenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_TURNING_OFF)) {
// Delay showing the keyguard until screen turned off.
} else {
showKeyguardImpl();
}
} else {
// During folding a foldable device this might be called as a result of
// 'onScreenTurnedOff' call for the inner display.
// In this case:
// * When phone is locked on folding: it doesn't make sense to hide keyguard as it
// will be immediately locked again
// * When phone is unlocked: we still don't want to execute hiding of the keyguard
// as the animation could prepare 'fake AOD' interface (without actually
// transitioning to keyguard state) and this might reset the view states
if (!mScreenOffAnimationController.isKeyguardHideDelayed()) {
return hideKeyguardImpl(forceStateChange);
}
}
}
这里走的是hideKeyguardImpl。
/**
* @return true if we would like to stay in the shade, false if it should go away entirely
*/
@Override
public boolean hideKeyguardImpl(boolean forceStateChange) {
}
hideKeyguardImpl里面首先会调用mStatusBarStateController.setState(StatusBarState.SHADE, forceStateChange)设置StatusBarState为SHADE,然后调用instantCollapseNotificationPanel
@Override
public void instantCollapseNotificationPanel() {
mNotificationPanelViewController.instantCollapse();
mShadeController.runPostCollapseRunnables();
}
这次又回到了PanelViewController,调用了instantCollapse,
public void instantCollapse() {
abortAnimations();
setExpandedFraction(0f);
if (mExpanding) {
notifyExpandingFinished();
}
if (mInstantExpanding) {
mInstantExpanding = false;
updatePanelExpansionAndVisibility();
}
}
然后走到setExpandedFraction(0f);,此时设置ExpandedFraction为0f了。然后再次回到了setExpandedHeightInternal
然后是updatePanelExpansionAndVisibility更新PanelView的可见性
/**
* Updates the panel expansion and {@link PanelView} visibility if necessary.
*
* TODO(b/200063118): Could public calls to this method be replaced with calls to
* {@link #updateVisibility()}? That would allow us to make this method private.
*/
public void updatePanelExpansionAndVisibility() {
mPanelExpansionStateManager.onPanelExpansionChanged(
mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
updateVisibility();
}
3.4 通知keyguard可见性改变
在onPanelExpansionChanged中,会走到StatusBarKeyguardViewManager的onPanelExpansionChanged里。
然后走到KeyguardBouncer的mBouncer.setExpansion。
/**
* Current notification panel expansion
* @param fraction 0 when notification panel is collapsed and 1 when expanded.
* @see StatusBarKeyguardViewManager#onPanelExpansionChanged
*/
public void setExpansion(float fraction) {
float oldExpansion = mExpansion;
mExpansion = fraction;
if (mKeyguardView != null && !mIsAnimatingAway) {
float alpha = MathUtils.map(ALPHA_EXPANSION_THRESHOLD, 1, 1, 0, fraction);
mKeyguardView.setAlpha(MathUtils.constrain(alpha, 0f, 1f));
mKeyguardView.setTranslationY(fraction * mKeyguardView.getHeight());
}
if (fraction == EXPANSION_VISIBLE && oldExpansion != EXPANSION_VISIBLE) {
onFullyShown();
mExpansionCallback.onFullyShown();
} else if (fraction == EXPANSION_HIDDEN && oldExpansion != EXPANSION_HIDDEN) {
onFullyHidden();
mExpansionCallback.onFullyHidden();
} else if (fraction != EXPANSION_VISIBLE && oldExpansion == EXPANSION_VISIBLE) {
mExpansionCallback.onStartingToHide();
if (mKeyguardView != null) {
mKeyguardView.onStartingToHide();
}
}
}
然后来到StatusBarKeyguardViewManager的onFullyShown
@Override
public void onFullyShown() {
mBouncerAnimating = false;
updateStates();
mCentralSurfaces.wakeUpIfDozing(SystemClock.uptimeMillis(),
mCentralSurfaces.getBouncerContainer(), "BOUNCER_VISIBLE");
}
之后再updateStates里面就会调用onKeyguardVisibilityChanged通知Keyguard可见性为false。