本篇文章接着上一篇,上一篇主要分析了滑动锁屏加载流程,这篇我们分析密码锁屏也叫Bouncer的加载,看看系统如何加载不同密码锁屏以及如何动态添加的
我们知道密码锁屏有三种,pin/password/pattern,还有两种SIM卡的bouncer,SIM PIN/SIM PUK,要显示密码锁屏,首先需要上滑滑动锁屏,我们就以上滑滑动锁屏为入口分析密码锁屏的出现
通过上一篇文章我们知道滑动锁屏是一个id为notification_panel的自定义ViewGroup,对应类NotificationPanelView,继承PanelView,我们需要找到滑动锁屏的TouchEvent事件分发,当用户在滑动锁屏上滑的速度和距离满足一定条件之后,滑动锁屏就会解锁,这时候如果有密码锁屏就会显示,没有就直接解锁,我们首先来看看PanelView的isFalseTouch方法,这个方法定义了当前是否为误触的条件,isFalseTouch返回true代表是误触
private boolean isFalseTouch(float x, float y) {
if (!mStatusBar.isFalsingThresholdNeeded()) {
return false;
}
if (mFalsingManager.isClassiferEnabled()) {
return mFalsingManager.isFalseTouch();
}
if (!mTouchAboveFalsingThreshold) {
return true;
}
if (mUpwardsWhenTresholdReached) {
return false;
}
return !isDirectionUpwards(x, y);
}
此方法有几个判断条件:
- isFalsingThresholdNeeded方法,此方法定义在StatusBar中,代码很简单"mStatusBarStateController.getState() == StatusBarState.KEYGUARD",返回当前是否是锁屏状态,肯定为true的
- 通过一系列算法来判断的,结合了P-sersor,也就是近距离传感器,
实际应用就是口袋模式防误触,当手机顶部sersor被遮挡了,那不管怎么滑都会被判定为误触 - mTouchAboveFalsingThreshold这个条件判定的就是滑动阙值,后面onTouchEvent方法中再分析
- mUpwardsWhenTresholdReached判定的是滑动方向是否向上且与水平方向成45度角以上,如果是则mUpwardsWhenTresholdReached为true
- !isDirectionUpwards(x, y),其实mUpwardsWhenTresholdReached的值就是调用isDirectionUpwards的返回值,!isDirectionUpwards == !mUpwardsWhenTresholdReached
锁屏误触的条件我们已经清楚了,我们来看PanelView的onTouchEvent方法,主要看Move事件就行了
PanelView.onTouchEvent
@Override
public boolean onTouchEvent(MotionEvent event) {
...
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
...
break;
case MotionEvent.ACTION_MOVE:
//滑动Y轴的距离
float h = y - mInitialTouchY;
if (Math.abs(h) > mTouchSlop
&& (Math.abs(h) > Math.abs(x - mInitialTouchX)
|| mIgnoreXTouchSlop)) {
mTouchSlopExceeded = true;
...
//设置mTracking为true,代表滑动锁屏开始...
onTrackingStarted();
}
...
//阙值判断,getFalsingThreshold获取
//R.dimen.unlock_falsing_threshold(80dp)的值,然后
//乘上一个系数,1或者1.5
if (-h >= getFalsingThreshold()) {
//阙值满足之后将mTouchAboveFalsingThreshold置为true
mTouchAboveFalsingThreshold = true;
//并且获取滑动方向和角度
mUpwardsWhenTresholdReached = isDirectionUpwards(x, y);
}
if (!mJustPeeked && (!mGestureWaitForTouchSlop || mTracking) &&
!isTrackingBlocked()) {
//这里会传递一个newHeight最终给到NotificationPanelView
//根据这个值,锁屏上的时钟,通知等透明度会变化
setExpandedHeightInternal(newHeight);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
addMovement(event);
endMotionEvent(event, x, y, false /* forceCancel */);
break;
}
}
我们只看Move事件里做的事情:
h等于touch事件Y轴的距离,因为是向上滑,所以这里h是一个负值,需要用绝对值,mTouchSlop等于R.dimen.hint_move_distance等于75dp,所以首先锁屏上Y轴滑动的距离大于75dp就会调用onTrackingStarted,并设置mTracking为true,代表当前正在滑动锁屏中…
当再次满足一个阙值getFalsingThreshold()之后会将mTouchAboveFalsingThreshold置为true,并且得到当前滑动的方向和角度是否满足要求,这两个条件就是我们前面分析isFalseTouch中误触的条件
通过setExpandedHeightInternal,调用NotificationPanelView中的onHeightUpdated方法根据传递的newHeight的值更新锁屏上时钟,通知等的透明度,newHeight这个值开始为手机屏幕高度,随着手指上滑逐渐变小,最终变为0代表滑动锁屏完全退出
Move事件结束后在UP事件中调用了endMotionEvent方法
endMotionEvent
private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) {
......
//expand这个值是解锁的关键,false代表成功,true代表失败
boolean expand = flingExpands(vel, vectorVel, x, y)
|| event.getActionMasked() == MotionEvent.ACTION_CANCEL
|| forceCancel;
......
fling(vel, expand, isFalseTouch(x, y));
onTrackingStopped(expand);
...
} else if (mPanelClosedOnDown && !mHeadsUpManager.hasPinnedHeadsUp() && !mTracking
&& !mStatusBar.isBouncerShowing() && !mKeyguardMonitor.isKeyguardFadingAway()) {
...
}
} else if (!mStatusBar.isBouncerShowing()) {
...
}
}
先看看给expand赋值方法flingExpands
flingExpands
protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
//如果解锁被禁用则返回true,这里不太清楚如何禁用
if (mFalsingManager.isUnlockingDisabled()) {
return true;
}
//是否为误触
if (isFalseTouch(x, y)) {
return true;
}
//对滑动速度的判断
if (Math.abs(vectorVel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
return getExpandedFraction() > 0.5f;
} else {
return vel > 0;
}
}
flingExpands主要对是否误触和滑动速度的判断,即使通过了误触检测,还会检测滑动的速度,是否误触我们前面已经分析过,这里的速度我自己测试了下,稍微快点的速度都能满足条件,所以flingExpands的返回结果就决定了是否能够滑动解锁,expand等于true代表为误触,为false代表成功滑动解锁
到这里我们可以总结滑动解锁的检测条件了:(1)对P-senor的近距离检测,(2)对滑动的阙值检测,(3)对滑动的方向和角度检测,(4)对滑动速度的检测
fling这个方法主要是对滑动锁屏的一些动画设置和处理,就不具体看了,我们接着分析onTrackingStopped方法,此方法代表手指滑动锁屏结束
因为子类NotificationPanelView覆盖了此方法,所以会调用NotificationPanelView的onTrackingStopped方法
NotificationPanelView.onTrackingStopped
@Override
protected void onTrackingStopped(boolean expand) {
...
super.onTrackingStopped(expand);
...
}
子类NotificationPanelView中做了一些工作之后通过super再调用了父类的此方法
onTrackingStopped
protected void onTrackingStopped(boolean expand) {
mTracking = false;
mBar.onTrackingStopped(expand);
notifyBarPanelExpansionChanged();
}
接着调用了PanelBar的onTrackingStopped方法,又会调用到PanelBar的子类PhoneStatusBarView覆盖的此方法
PhoneStatusBarView.onTrackingStopped
@Override
public void onTrackingStopped(boolean expand) {
super.onTrackingStopped(expand);
mBar.onTrackingStopped(expand);
}
这里先通过super调用了父类的onTrackingStopped方法,父类中就一行代码” mTracking = false“,接着调到了StatusBar的onTrackingStopped方法
StatusBar.onTrackingStopped
public void onTrackingStopped(boolean expand) {
//当前设备状态为KEYGUARD或者SHADE_LOCKED
if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
//expand就是flingExpands返回值,一直传递过来,为false
//有密码锁屏时canSkipBouncer为false,没有则为true
if (!expand && !mUnlockMethodCache.canSkipBouncer()) {
showBouncer(false /* scrimmed */);
}
}
}
接着调用showBouncer
showBouncer
@Override
public void showBouncer(boolean scrimmed) {
mStatusBarKeyguardViewManager.showBouncer(scrimmed);
}
StatusBarKeyguardViewManager.showBouncer
public void showBouncer(boolean scrimmed) {
//Keyguard已经已经show并且Bouncer还没有show
if (mShowing && !mBouncer.isShowing()) {
mBouncer.show(false /* resetSecuritySelection */, scrimmed);
}
//更新当前锁屏的各种状态值
updateStates();
}
KeyguardBouncer.show
public void show(boolean resetSecuritySelection, boolean isScrimmed) {
...
//ensureView我们前一篇文章分析过,里面会调用inflateView加载
//id为keyguard_bouncer和keyguard_host_view的bouncer布局文件
//并通过addView添加到SystemUI顶层View中
ensureView();
...
//resetSecuritySelection传递过来为false,代表不更新当前bouncer类型
//什么情况下会更新呢?插入带PIN或者PUK的sim卡时
if (resetSecuritySelection) {
showPrimarySecurityScreen();
}
//如果当前bouncer已经可见或者mShowingSoon为true则return
//mShowingSoon是个标志位,避免重复调用,代表即将显示bouncer
if (mRoot.getVisibility() == View.VISIBLE || mShowingSoon) {
return;
}
final int activeUserId = KeyguardUpdateMonitor.getCurrentUser();
final boolean isSystemUser =
UserManager.isSplitSystemUser() && activeUserId == UserHandle.USER_SYSTEM;
final boolean allowDismissKeyguard = !isSystemUser && activeUserId == keyguardUserId;
//allowDismissKeyguard一般为true,mKeyguardView.dismiss
//决定是否显示bouncer,显示哪种bouncer,返回true代表没有bouncer
if (allowDismissKeyguard && mKeyguardView.dismiss(activeUserId)) {
return;
}
...
//标志位
mShowingSoon = true;
DejankUtils.removeCallbacks(mResetRunnable);
if (mKeyguardUpdateMonitor.isFaceDetectionRunning()) {
mHandler.postDelayed(mShowRunnable, BOUNCER_FACE_DELAY);
} else {
DejankUtils.postAfterTraversal(mShowRunnable);
}
mCallback.onBouncerVisiblityChanged(true /* shown */);
mExpansionCallback.onStartingToShow();
}
我们先看看决定是否显示bouncer,显示何种类型bouncer(pin/password/pattern)的核心方法KeyguardHostView.dismiss
KeyguardHostView.dismiss
public boolean dismiss(int targetUserId) {
...
return dismiss(false, targetUserId);
}
@Override
public boolean dismiss(boolean authenticated, int targetUserId) {
return mSecurityContainer.showNextSecurityScreenOrFinish(authenticated, targetUserId);
}
mSecurityContainer是一个id为keyguard_security_container,类型为KeyguardSecurityContainer的自定义FrameLayout,被包含在keyguard_host_view.xml中,它是(pin/password/pattern)主要容器
KeyguardSecurityContainer.showNextSecurityScreenOrFinish
boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId) {
//finish作为此方法的返回值,代表了是否显示bouncer界面
boolean finish = false;
boolean strongAuth = false;
int eventSubtype = -1;
//是否可以信任设备,google有一个smart lock的功能,用户设置可信任
//方式则可以跳过密码锁屏
if (mUpdateMonitor.getUserHasTrust(targetUserId)) {
finish = true;
...
//如果使用了生物识别解锁也可以跳过密码锁屏
} else if (mUpdateMonitor.getUserUnlockedWithBiometric(targetUserId)) {
finish = true;
...
//如果当前的密码锁屏方式为None
} else if (SecurityMode.None == mCurrentSecuritySelection) {
SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
if (SecurityMode.None == securityMode) {
finish = true;
eventSubtype = BOUNCER_DISMISS_NONE_SECURITY;
} else {
showSecurityScreen(securityMode);
}
} else if (authenticated) {//authenticated传递过来为false
switch (mCurrentSecuritySelection) {
case Pattern:
case Password:
case PIN:
strongAuth = true;
finish = true;
eventSubtype = BOUNCER_DISMISS_PASSWORD;
break;
case SimPin:
case SimPuk:
SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
if (securityMode == SecurityMode.None || mLockPatternUtils.isLockScreenDisabled(
KeyguardUpdateMonitor.getCurrentUser())) {
finish = true;
eventSubtype = BOUNCER_DISMISS_SIM;
} else {
showSecurityScreen(securityMode);
}
break;
default:
Log.v(TAG, "Bad security screen " + mCurrentSecuritySelection + ", fail safe");
showPrimarySecurityScreen(false);
break;
}
}
...
return finish;
}
通过手指上滑锁屏显示bouncer不会走此方法里的任何分支,finish为是否显示密码锁屏,true代表不显示,false代表显示,所以当我们设置了(pin/password/pattern)则finish不会进任何分支,直接返回false
那我们的锁屏类型是哪里获取的呢?
我们再回到KeyguardBouncer中的inflateView方法,再来深入分析一下此方法相关的代码
inflateView
protected void inflateView() {
...
mRoot = (ViewGroup) LayoutInflater.from(mContext).inflate(R.layout.keyguard_bouncer, null);
mKeyguardView = mRoot.findViewById(R.id.keyguard_host_view);
...
mContainer.addView(mRoot, mContainer.getChildCount());
...
}
我们来看下,首先加载keyguard_bouncer的布局,然后加载keyguard_host_view的布局:
keyguard_host_view.xml
<com.android.keyguard.KeyguardHostView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/res-auto"
android:id="@+id/keyguard_host_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:importantForAccessibility="yes"> <!-- Needed because TYPE_WINDOW_STATE_CHANGED is sent
from this view when bouncer is shown -->
<com.android.keyguard.KeyguardSecurityContainer
android:id="@+id/keyguard_security_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
androidprv:layout_maxHeight="@dimen/keyguard_security_max_height"
android:clipChildren="false"
android:clipToPadding="false"
android:padding="0dp"
android:fitsSystemWindows="true"
android:layout_gravity="center">
<com.android.keyguard.KeyguardSecurityViewFlipper
android:id="@+id/view_flipper"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingTop="@dimen/keyguard_security_view_top_margin"
android:paddingStart="@dimen/keyguard_security_view_lateral_margin"
android:paddingEnd="@dimen/keyguard_security_view_lateral_margin"
android:gravity="center">
</com.android.keyguard.KeyguardSecurityViewFlipper>
</com.android.keyguard.KeyguardSecurityContainer>
</com.android.keyguard.KeyguardHostView>
我们可以看到keyguard_host_view是KeyguardHostView的布局文件,所以我们来看看KeyguardHostView加载完成后做的事情
KeyguardHostView.onFinishInflate
@Override
protected void onFinishInflate() {
mSecurityContainer =
findViewById(R.id.keyguard_security_container);
...
mSecurityContainer.showPrimarySecurityScreen(false);
}
keyguard_security_container我们前面讲过,是一个被包含在keyguard_host_view
中的KeyguardSecurityContainer,我们主要看调用它的showPrimarySecurityScreen方法
showPrimarySecurityScreen
void showPrimarySecurityScreen(boolean turningOff) {
//获取当前的bouncer类型
SecurityMode securityMode = mSecurityModel.getSecurityMode(
KeyguardUpdateMonitor.getCurrentUser());
showSecurityScreen(securityMode);
}
mSecurityModel.getSecurityMode
SecurityMode getSecurityMode(int userId) {
KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
//如果当前有插入SIM卡,并且SIM卡的状态是PUK_REQUIRED
//则bouncer类型为SimPuk
if (mIsPukScreenAvailable && SubscriptionManager.isValidSubscriptionId(
monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED))) {
return SecurityMode.SimPuk;
}
//如果当前有插入SIM卡,并且SIM卡的状态是PIN_REQUIRED
//则bouncer类型为SimPin
if (SubscriptionManager.isValidSubscriptionId(
monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED))) {
return SecurityMode.SimPin;
}
//否则就通过mLockPatternUtils获取当前的bouncer,
//当Settings中设置好了bouncer类型,就会保存到mLockPatternUtils中
final int security = mLockPatternUtils.getActivePasswordQuality(userId);
switch (security) {
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
return SecurityMode.PIN;
case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
case DevicePolicyManager.PASSWORD_QUALITY_MANAGED:
return SecurityMode.Password;
case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
return SecurityMode.Pattern;
case DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED:
return SecurityMode.None;
default:
throw new IllegalStateException("Unknown security quality:" + security);
}
}
上面代码就是获取当前的锁屏类型,包含如下的类型
public enum SecurityMode {
Invalid, // NULL state
None, // No security enabled
Pattern, // Unlock by drawing a pattern.
Password, // Unlock by entering an alphanumeric password
PIN, // Strictly numeric password
SimPin, // Unlock by entering a sim pin.
SimPuk // Unlock by entering a sim puk
}
bouncer类型有了之后调用showSecurityScreen方法,此方法就是为了更新当前的锁屏类型,以便需要显示的时候直接显示出来
showSecurityScreen
private void showSecurityScreen(SecurityMode securityMode) {
//如果bouncer的类型并没有发生变化,就不需要更新
if (securityMode == mCurrentSecuritySelection) return;
//getSecurityView方法会根据的bouncer类型,得到具体的bouncer自定义View
KeyguardSecurityView oldView = getSecurityView(mCurrentSecuritySelection);
KeyguardSecurityView newView = getSecurityView(securityMode);
//待getSecurityView分析完之后再贴出后面代码分析
......
}
getSecurityView
private KeyguardSecurityView getSecurityView(SecurityMode securityMode) {
//根据bouncer类型获取具体的bouncer的控件id
final int securityViewIdForMode = getSecurityViewIdForMode(securityMode);
KeyguardSecurityView view = null;
//获取mSecurityViewFlipper中的bouncer个数,如果有的话
final int children = mSecurityViewFlipper.getChildCount();
for (int child = 0; child < children; child++) {
if (mSecurityViewFlipper.getChildAt(child).getId() == securityViewIdForMode) {
//找到bouncer返回
view = ((KeyguardSecurityView)mSecurityViewFlipper.getChildAt(child));
break;
}
}
//根据bouncer类型获取具体的bouncer的布局文件id
int layoutId = getLayoutIdFor(securityMode);
//如果mSecurityViewFlipper还没有bouncer并且能够获取到bouncer的layout
if (view == null && layoutId != 0) {
final LayoutInflater inflater = LayoutInflater.from(mContext);
//通过layoutId加载具体的bouncer View
View v = mInjectionInflationController.injectable(inflater)
.inflate(layoutId, mSecurityViewFlipper, false);
//并且将bouncer View添加到mSecurityViewFlipper中
mSecurityViewFlipper.addView(v);
//给bouncer设置回调和mLockPatternUtils
updateSecurityView(v);
view = (KeyguardSecurityView)v;
view.reset();
}
return view;
}
我们先看下上面代码中获取bouncer控件id(getSecurityViewIdForMode)和bouncer布局文件id(getLayoutIdFor),我们发现这五种bouncer类型各自有自己的布局文件
private int getSecurityViewIdForMode(SecurityMode securityMode) {
switch (securityMode) {
case Pattern: return R.id.keyguard_pattern_view;
case PIN: return R.id.keyguard_pin_view;
case Password: return R.id.keyguard_password_view;
case SimPin: return R.id.keyguard_sim_pin_view;
case SimPuk: return R.id.keyguard_sim_puk_view;
}
return 0;
}
@VisibleForTesting
public int getLayoutIdFor(SecurityMode securityMode) {
switch (securityMode) {
case Pattern: return R.layout.keyguard_pattern_view;
case PIN: return R.layout.keyguard_pin_view;
case Password: return R.layout.keyguard_password_view;
case SimPin: return R.layout.keyguard_sim_pin_view;
case SimPuk: return R.layout.keyguard_sim_puk_view;
default:
return 0;
}
}
好了我们总结一下getSecurityView方法:
- 根据当前bouncer类型获取bouncer控件id
- 遍历mSecurityViewFlipper,获取已经添加的bouncer
- 根据当前bouncer类型获取bouncer布局文件id
- 如果没有在mSecurityViewFlipper获取到bouncer,则通过LayoutInflater加载布局得到具体的bouncer View
- 最后将bouncer添加到mSecurityViewFlipper中
我们发现其实最终bouncer的直接父容器是KeyguardSecurityViewFlipper,我们再回头看看keyguard_host_view布局:
KeyguardSecurityViewFlipper是此布局最里面的容器,各种bouncer就是这样动态添加到了keyguard_host_view中
<com.android.keyguard.KeyguardHostView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/res-auto"
android:id="@+id/keyguard_host_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:importantForAccessibility="yes"> <!-- Needed because TYPE_WINDOW_STATE_CHANGED is sent
from this view when bouncer is shown -->
<com.android.keyguard.KeyguardSecurityContainer
android:id="@+id/keyguard_security_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
androidprv:layout_maxHeight="@dimen/keyguard_security_max_height"
android:clipChildren="false"
android:clipToPadding="false"
android:padding="0dp"
android:fitsSystemWindows="true"
android:layout_gravity="center">
<com.android.keyguard.KeyguardSecurityViewFlipper
android:id="@+id/view_flipper"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingTop="@dimen/keyguard_security_view_top_margin"
android:paddingStart="@dimen/keyguard_security_view_lateral_margin"
android:paddingEnd="@dimen/keyguard_security_view_lateral_margin"
android:gravity="center">
</com.android.keyguard.KeyguardSecurityViewFlipper>
</com.android.keyguard.KeyguardSecurityContainer>
</com.android.keyguard.KeyguardHostView>
另外我们需要知道所有的bouncer都实现了KeyguardSecurityView接口,到此bouncer的获取和创建以及动态添加我们就分析完毕了
接着回到showSecurityScreen后半部分继续分析:
private void showSecurityScreen(SecurityMode securityMode) {
//如果bouncer的类型并没有发生变化,就不需要更新
if (securityMode == mCurrentSecuritySelection) return;
//getSecurityView方法会根据的bouncer类型,得到具体的bouncer自定义View
//如果需要更新boucer的情况,先获取老的bouncer类型
KeyguardSecurityView oldView = getSecurityView(mCurrentSecuritySelection);
//再获取最新改变的bouncer类型
KeyguardSecurityView newView = getSecurityView(securityMode);
//如果之前的bouncer不为空则调用onPause隐藏并设置空回调
if (oldView != null) {
oldView.onPause();
oldView.setKeyguardCallback(mNullCallback);
}
//如果当前设置的bouncer不为None,则调用其onResume激活,并设置回调接口
if (securityMode != SecurityMode.None) {
newView.onResume(KeyguardSecurityView.VIEW_REVEALED);
newView.setKeyguardCallback(mCallback);
}
//获取bouncer的数量
final int childCount = mSecurityViewFlipper.getChildCount();
final int securityViewIdForMode = getSecurityViewIdForMode(securityMode);
//遍历mSecurityViewFlipper的bouncer,并根据当前的bouncer类型
//调用setDisplayedChild切换到此bouncer
for (int i = 0; i < childCount; i++) {
if (mSecurityViewFlipper.getChildAt(i).getId() == securityViewIdForMode) {
mSecurityViewFlipper.setDisplayedChild(i);
break;
}
}
//将当前的bouncer类型保存到mCurrentSecuritySelection
mCurrentSecuritySelection = securityMode;
//将当前的bouncer View保存到mCurrentSecurityView
mCurrentSecurityView = newView;
//通知bouncer类型发生改变
mSecurityCallback.onSecurityModeChanged(securityMode,
securityMode != SecurityMode.None && newView.needsInput());
}
到此showSecurityScreen方法我们已经全部分析完毕,此方法主要目的就是更新当前系统的bouncer类型并添加到mSecurityViewFlipper,此时bouncer并不可见
我们可以回到KeyguardBouncer.show方法中去了,我们之前分析到mKeyguardView.dismiss方法,此方法返回false代表需要显示bouncer,返回true代表可以跳过,继续看代码:
KeyguardBouncer.show
public void show(boolean resetSecuritySelection, boolean isScrimmed) {
...
//ensureView我们前一篇文章分析过,里面会调用inflateView加载
//id为keyguard_bouncer和keyguard_host_view的bouncer布局文件
//并通过addView添加到SystemUI顶层View中
ensureView();
...
//resetSecuritySelection传递过来为false,代表不更新当前bouncer类型
//什么情况下会更新呢?插入带PIN或者PUK的sim卡时
if (resetSecuritySelection) {
showPrimarySecurityScreen();
}
//如果当前bouncer已经可见或者mShowingSoon为true则return
//mShowingSoon是个标志位,避免重复调用,代表即将显示bouncer
if (mRoot.getVisibility() == View.VISIBLE || mShowingSoon) {
return;
}
final int activeUserId = KeyguardUpdateMonitor.getCurrentUser();
final boolean isSystemUser =
UserManager.isSplitSystemUser() && activeUserId == UserHandle.USER_SYSTEM;
final boolean allowDismissKeyguard = !isSystemUser && activeUserId == keyguardUserId;
//allowDismissKeyguard一般为true,mKeyguardView.dismiss
//决定是否显示bouncer,显示哪种bouncer,返回true代表没有bouncer
if (allowDismissKeyguard && mKeyguardView.dismiss(activeUserId)) {
return;
}
...
//标志位
mShowingSoon = true;
if (mKeyguardUpdateMonitor.isFaceDetectionRunning()) {
mHandler.postDelayed(mShowRunnable, BOUNCER_FACE_DELAY);
} else {
DejankUtils.postAfterTraversal(mShowRunnable);
}
//通知bouncer的可见性发生了变化
mCallback.onBouncerVisiblityChanged(true /* shown */);
//通知bouncer开始显示,里面会更新LockIcon图标的变化
mExpansionCallback.onStartingToShow();
}
最后来看看mShowRunnable,
private final Runnable mShowRunnable = new Runnable() {
@Override
public void run() {
//首先就是让mRoot显示出来
mRoot.setVisibility(View.VISIBLE);
...
if (mKeyguardView.getHeight() != 0 && mKeyguardView.getHeight() != mStatusBarHeight) {
mKeyguardView.startAppearAnimation();
} else {
mKeyguardView.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
mKeyguardView.getViewTreeObserver().removeOnPreDrawListener(this);
mKeyguardView.startAppearAnimation();
return true;
}
});
mKeyguardView.requestLayout();
}
//标志位置为false
mShowingSoon = false;
if (mExpansion == EXPANSION_VISIBLE) {
mKeyguardView.onResume();
mKeyguardView.resetSecurityContainer();
}
...
}
};
这个Runnable做的事情其实就是将bouncer显示出来,不管哪种类型的bouncer在显示的时候都会调用startAppearAnimation方法,开始显示的动画效果
到此滑动锁屏上滑显示密码锁屏已经全部分析完毕,总结一下:
- 我们分析了滑动锁屏上滑的判定策略,系统提供了几种误触判定的方式,包括P-seneor近距离检测,上滑距离检测,速度检测和方向检测
- 我们分析了bouncer的创建,bouncer类型的获取,bouncer的添加,所有bouncer都是添加到KeyguardSecurityViewFlipper中的,以动态添加的方式
- bouncer的显示,不管何种类型的bouncer显示时都会调用startAppearAnimation方法
上篇和这篇全面总结了锁屏的流程,以后遇到和锁屏相关的问题,我们只需要顺着这个流程查找问题,一定会很容易的