AndroidQ SystemUI之锁屏加载(下)密码锁屏

本篇文章接着上一篇,上一篇主要分析了滑动锁屏加载流程,这篇我们分析密码锁屏也叫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);
    }

此方法有几个判断条件:

  1. isFalsingThresholdNeeded方法,此方法定义在StatusBar中,代码很简单"mStatusBarStateController.getState() == StatusBarState.KEYGUARD",返回当前是否是锁屏状态,肯定为true的
  2. 通过一系列算法来判断的,结合了P-sersor,也就是近距离传感器,
    实际应用就是口袋模式防误触,当手机顶部sersor被遮挡了,那不管怎么滑都会被判定为误触
  3. mTouchAboveFalsingThreshold这个条件判定的就是滑动阙值,后面onTouchEvent方法中再分析
  4. mUpwardsWhenTresholdReached判定的是滑动方向是否向上且与水平方向成45度角以上,如果是则mUpwardsWhenTresholdReached为true
  5. !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方法:

  1. 根据当前bouncer类型获取bouncer控件id
  2. 遍历mSecurityViewFlipper,获取已经添加的bouncer
  3. 根据当前bouncer类型获取bouncer布局文件id
  4. 如果没有在mSecurityViewFlipper获取到bouncer,则通过LayoutInflater加载布局得到具体的bouncer View
  5. 最后将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方法,开始显示的动画效果

到此滑动锁屏上滑显示密码锁屏已经全部分析完毕,总结一下:

  1. 我们分析了滑动锁屏上滑的判定策略,系统提供了几种误触判定的方式,包括P-seneor近距离检测,上滑距离检测,速度检测和方向检测
  2. 我们分析了bouncer的创建,bouncer类型的获取,bouncer的添加,所有bouncer都是添加到KeyguardSecurityViewFlipper中的,以动态添加的方式
  3. bouncer的显示,不管何种类型的bouncer显示时都会调用startAppearAnimation方法

上篇和这篇全面总结了锁屏的流程,以后遇到和锁屏相关的问题,我们只需要顺着这个流程查找问题,一定会很容易的

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值