1. 程式人生 > >Android8.1 SystemUI Keyguard之滑動解鎖流程

Android8.1 SystemUI Keyguard之滑動解鎖流程

我們理解Keyguard的解鎖流程主要從鎖屏的介面Layout結構、touchEvent事件分發、解鎖動作邏輯幾個方面進行原始碼的分析

鎖屏的介面Layout結構分析

StatusbarWindowView

整個鎖屏介面的頂級View就是mStatusBarWindow
src/com/android/systemui/statusbar/phone/StatusBar.java

    public void createAndAddWindows() {
        addStatusBarWindow();
    }

    private void addStatusBarWindow() {
        makeStatusBarView();
        mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
        mRemoteInputController = new RemoteInputController(mHeadsUpManager);
        mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
        // add by hai.qin for story lock
        if (mLockScreenManager != null) {
            mLockScreenManager.setStatusBarWindowManager(mStatusBarWindowManager);
        }
        //
    }

src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java

    /**
     * Adds the status bar view to the window manager.
     *
     * @param statusBarView The view to add.
     * @param barHeight The height of the status bar in collapsed state.
     */
    public void add(View statusBarView, int barHeight) {

        // Now that the status bar window encompasses the sliding panel and its
        // translucent backdrop, the entire thing is made TRANSLUCENT and is
        // hardware-accelerated.
        mLp = new WindowManager.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                barHeight,
                WindowManager.LayoutParams.TYPE_STATUS_BAR,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                        | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
                PixelFormat.TRANSLUCENT);
        mLp.token = new Binder();
        mLp.gravity = Gravity.TOP;
        mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
        mLp.setTitle("StatusBar");
        mLp.packageName = mContext.getPackageName();
        mStatusBarView = statusBarView;
        mBarHeight = barHeight;
        mWindowManager.addView(mStatusBarView, mLp);
        mLpChanged = new WindowManager.LayoutParams();
        mLpChanged.copyFrom(mLp);
    }

mStatusBarWindow是在StatusBar的create流程中呼叫WindowManager.addView()新增到視窗上的, type為WindowManager.LayoutParams.TYPE_STATUS_BAR

Layout結構

鎖屏介面的Layout結構可以簡單概括為以下結構:
mStatusBarWindow--> R.layout.super_status_bar
notification_panel--> R.layout.status_bar_expanded
keyguardBouncer-->R.layout.keyguard_bouncer

mStatusBarWindow-->notification_panel-->notification_container_parent-->keyguard_header(鎖屏狀態列)
                |                    |
                |                    -->keyguard_bottom_area (lock_icon和充電狀態等)
                |                    |
                |                    -->keyguard_status_view (鎖屏時鐘日期)
                |                    |
                |                    -->keyguard_up_slide (箭頭提示動畫)
                |
                -->keyguardBouncer(安全鎖介面)

上劃後顯示的安全鎖介面是KeyguardBouncer,但keyguardbouncer並沒有寫在super_status_bar的layout檔案裡面,那麼他是在什麼時候新增的呢?

KeyguarBouncer何時建立

src/com/android/systemui/statusbar/phone/StatusBar.java
-->start()-->startKeyguard()

 protected void startKeyguard() {
 ...
        mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this,
                getBouncerContainer(), mScrimController,
                mFingerprintUnlockController);
 ...
 }

src/com/android/systemui/keyguard/KeyguardViewMediator.java
-->registerStatusBar()
src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
-->registerStatusBar()

    public void registerStatusBar(StatusBar statusBar,
            ViewGroup container,
            ScrimController scrimController,
            FingerprintUnlockController fingerprintUnlockController,
            DismissCallbackRegistry dismissCallbackRegistry) {
        mStatusBar = statusBar;
        mContainer = container;
        mScrimController = scrimController;
        mFingerprintUnlockController = fingerprintUnlockController;
        mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
                mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry);
    }

那麼這裡的container是什麼?

    protected ViewGroup getBouncerContainer() {
        return mStatusBarWindow;
    }

是什麼時候把keyguard_host_view加入到mStatuBarWindow的?

src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
-->show()-->ensureView()-->inflateView()

    protected void inflateView() {
        removeView();
        mHandler.removeCallbacks(mRemoveViewRunnable);
        mRoot = (ViewGroup) LayoutInflater.from(mContext).inflate(R.layout.keyguard_bouncer, null);
        mKeyguardView = mRoot.findViewById(R.id.keyguard_host_view);
        mKeyguardView.setLockPatternUtils(mLockPatternUtils);
        mKeyguardView.setViewMediatorCallback(mCallback);
        mContainer.addView(mRoot, mContainer.getChildCount());
        mRoot.setVisibility(View.INVISIBLE);

        final WindowInsets rootInsets = mRoot.getRootWindowInsets();
        if (rootInsets != null) {
            mRoot.dispatchApplyWindowInsets(rootInsets);
        }
    }

不同型別的安全鎖是怎麼放入keyguard_host_view的?

src/com/android/keyguard/KeyguardSecurityContainer.java

    private KeyguardSecurityView getSecurityView(SecurityMode securityMode) {
        final int securityViewIdForMode = getSecurityViewIdForMode(securityMode);
        KeyguardSecurityView view = null;
        final int children = mSecurityViewFlipper.getChildCount();
        for (int child = 0; child < children; child++) {
            if (mSecurityViewFlipper.getChildAt(child).getId() == securityViewIdForMode) {
                view = ((KeyguardSecurityView)mSecurityViewFlipper.getChildAt(child));
                break;
            }
        }
        int layoutId = getLayoutIdFor(securityMode);
        if (view == null && layoutId != 0) {
            final LayoutInflater inflater = LayoutInflater.from(mContext);
            if (DEBUG) Log.v(TAG, "inflating id = " + layoutId);
            View v = inflater.inflate(layoutId, mSecurityViewFlipper, false);
            mSecurityViewFlipper.addView(v);
            updateSecurityView(v);
            view = (KeyguardSecurityView)v;
        }

        return view;
    }

每次獲取securityview的時候 先判斷是否在viewflippter裡存在該id
沒有的話 inflate一個新的同時addview到viewflippter裡面

在顯示的時候呼叫showSecurityScreen(SecurityMode securityMode)

    private void showSecurityScreen(SecurityMode securityMode) {
    ...

        // Find and show this child.
        final int childCount = mSecurityViewFlipper.getChildCount();

        final int securityViewIdForMode = getSecurityViewIdForMode(securityMode);
        for (int i = 0; i < childCount; i++) {
            if (mSecurityViewFlipper.getChildAt(i).getId() == securityViewIdForMode) {
                mSecurityViewFlipper.setDisplayedChild(i);
                break;
            }
        }
    ...
    }

會根據securitymode選擇viewflipper中對應的child進行顯示

touchEvent事件分發

我們這裡分析上滑解鎖過程中的touchEvent事件分發
讓我們來先回顧一個android中的事件分發概念:事件序列

事件序列

在Android系統中,一個單獨的事件基本上是沒什麼作用的,只有一個事件序列,才有意義。一個事件序列正常情況下,定義為 DOWN、MOVE(0或者多個)、UP/CANCEL。事件序列以DOWN事件開始,中間會有0或者多個MOVE事件,最後以UP事件或者CANCEL事件結束。

DOWN事件作為序列的開始,有一個很重要的職責,就是尋找事件序列的接受者,怎麼理解呢?framework 在DOWN事件的傳遞過程中,需要根據View事件處理方法(onTouchEvent)的返回值來確定事件序列的接受者。如果一個View的onTouchEvent事件,在處理DOWN事件的時候返回true,說明它願意接受並處理該事件序列。

上滑解鎖

當用戶移動手指時,產生touch down事件,
最外層view StatusBarWindowView會執行onInterceptTouchEvent,看是否需要攔截touch事件
再一級級往子View傳遞,都沒有被攔截,之後執行OnTouchEvent從子View開始一級級往父View傳遞,到PanelView這裡當手指移動的距離達到一定的閾值會呼叫onTrackingStarted從而設定mTracking的值為true,onTouchEvent返回true,接收此touch move事件,之後的touch事件直接傳到此View。
在使用者滑動過程會呼叫setExpandedHeightInternal,進而呼叫NotificationPanelView的onHeightUpdated進行鎖屏上的時間和通知View根據手指的移動距離進行縮小、變透明處理。
當用戶擡起手指時,產生touch up事件,PanelView接收到這個事件後會呼叫endMotionEvent,如果手指從down到up之間移動的距離達到一定閾值會呼叫onTrackingStopped

在上滑過程中,不斷呼叫PanelView.java的setExpandedHeightInternal()->notifyBarPanelExpansionChanged()-->PanelBar.java的notifyBarPanelExpansionChanged()
src/com/android/systemui/statusbar/phone/PanelBar.java

    public void panelExpansionChanged(float frac, boolean expanded) {
        Log.d("WANG", "panelExpansionChanged frac=" + frac + " expaned=" + expanded );
        boolean fullyClosed = true;
        boolean fullyOpened = false;
        if (SPEW) LOG("panelExpansionChanged: start state=%d", mState);
        PanelView pv = mPanel;
        pv.setVisibility(expanded ? VISIBLE : INVISIBLE);
        // adjust any other panels that may be partially visible
        if (expanded) {
            if (mState == STATE_CLOSED) {
                go(STATE_OPENING);
                onPanelPeeked();
            }
            fullyClosed = false;
            final float thisFrac = pv.getExpandedFraction();
            if (SPEW) LOG("panelExpansionChanged:  -> %s: f=%.1f", pv.getName(), thisFrac);
            fullyOpened = thisFrac >= 1f;
        }
        if (fullyOpened && !mTracking) {
            go(STATE_OPEN);
            onPanelFullyOpened();
        } else if (fullyClosed && !mTracking && mState != STATE_CLOSED) {
            go(STATE_CLOSED);
            onPanelCollapsed();
        }

        if (SPEW) LOG("panelExpansionChanged: end state=%d [%s%s ]", mState,
                fullyOpened?" fullyOpened":"", fullyClosed?" fullyClosed":"");
    }

達到閾值時,expanded == false fullyClosed == true
呼叫onPanelCollapsed()->呼叫子類PhoneStatubarView.java的onPanelCollapsed()
src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java

 @Override
    public void onPanelCollapsed() {
        super.onPanelCollapsed();
        // Close the status bar in the next frame so we can show the end of the animation.
        post(mHideExpandedRunnable);
        mIsFullyOpenedPanel = false;
    }
    private Runnable mHideExpandedRunnable = new Runnable() {
        @Override
        public void run() {
            if (mPanelFraction == 0.0f) {
                mBar.makeExpandedInvisible();
            }
        }
    };

解鎖動作邏輯

整個解鎖過程分為兩個部分:1. 隱藏notification_panel 2. 展示keyguard_bouncer或直接解鎖

src/com/android/systemui/statusbar/phone/StatusBar.java

    void makeExpandedInvisible() {
        if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible
                + " mExpandedVisible=" + mExpandedVisible);

        if (!mExpandedVisible || mStatusBarWindow == null) {
            return;
        }

        // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
        mStatusBarView.collapsePanel(/*animate=*/ false, false /* delayed*/,
                1.0f /* speedUpFactor */);

        mNotificationPanel.closeQs();

        mExpandedVisible = false;
        visibilityChanged(false);

        // Shrink the window to the size of the status bar only
        mStatusBarWindowManager.setPanelVisible(false);
        mStatusBarWindowManager.setForceStatusBarVisible(false);

        // Close any guts that might be visible
        closeAndSaveGuts(true /* removeLeavebehind */, true /* force */, true /* removeControls */,
                -1 /* x */, -1 /* y */, true /* resetMenu */);

        runPostCollapseRunnables();
        setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
        showBouncerIfKeyguard();
        recomputeDisableFlags(mNotificationPanel.hideStatusBarIconsWhenExpanded() /* animate */);

        // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
        // the bouncer appear animation.
        if (!mStatusBarKeyguardViewManager.isShowing()) {
            WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
        }
    }

mStatusBarWindowManager.setPanelVisible(false);
呼叫WindowManager更改為StatusBarWindow的高度, 只保留狀態列高度
mStatusBarWindowManager.setForceStatusBarVisible(false);
呼叫WindowManager使狀態列不可見
showBouncerIfKeyguard()->showBouncer()最終呼叫到StatusBarKeyguardViewManager的dismiss()->showBouncer()方法

src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java

    private void showBouncer() {
        if (mShowing) {
            mBouncer.show(false /* resetSecuritySelection */);
        }
        updateStates();
    }

src/com/android/systemui/statusbar/phone/KeyguardBouncer.java

    public void show(boolean resetSecuritySelection) {
        ...
        // If allowed, try to dismiss the Keyguard. If no security auth (password/pin/pattern) is
        // set, this will dismiss the whole Keyguard. Otherwise, show the bouncer.
        if (allowDismissKeyguard && mKeyguardView.dismiss(activeUserId)) {
            return;
        }
        ...
    }

在沒有安全鎖的情況下,會回撥KeyguardHostView的finish方法

    @Override
    public void finish(boolean strongAuth, int targetUserId) {
        // If there's a pending runnable because the user interacted with a widget
        // and we're leaving keyguard, then run it.
        boolean deferKeyguardDone = false;
        if (mDismissAction != null) {
            deferKeyguardDone = mDismissAction.onDismiss();
            mDismissAction = null;
            mCancelAction = null;
        }
        if (mViewMediatorCallback != null) {
            if (deferKeyguardDone) {
                mViewMediatorCallback.keyguardDonePending(strongAuth, targetUserId);
            } else {
                mViewMediatorCallback.keyguardDone(strongAuth, targetUserId);
            }
        }
    }

mViewMediatorCallback定義在KeyguardViewMediator中

  @Override
        public void keyguardDone(boolean strongAuth, int targetUserId) {
            if (targetUserId != ActivityManager.getCurrentUser()) {
                return;
            }

            tryKeyguardDone();
        }

一系列呼叫來到解鎖的核心程式碼
mKeyguardGoingAwayRunnable.run();

    private final Runnable mKeyguardGoingAwayRunnable = new Runnable() {
        @Override
        public void run() {
            Trace.beginSection("KeyguardViewMediator.mKeyGuardGoingAwayRunnable");
            if (DEBUG) Log.d(TAG, "keyguardGoingAway");
            //Modified for MYOS begin

            try {
                mStatusBarKeyguardViewManager.keyguardGoingAway();

                int flags = 0;
                if (mStatusBarKeyguardViewManager.shouldDisableWindowAnimationsForUnlock()
                        || mWakeAndUnlocking || mAniSpeedup) {
                    flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
                }
                if (mStatusBarKeyguardViewManager.isGoingToNotificationShade()) {
                    flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
                }
                if (mStatusBarKeyguardViewManager.isUnlockWithWallpaper()) {
                    flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
                }

                mUpdateMonitor.setKeyguardGoingAway(true /* goingAway */);
                // Don't actually hide the Keyguard at the moment, wait for window
                // manager until it tells us it's safe to do so with
                // startKeyguardExitAnimation.
                ActivityManager.getService().keyguardGoingAway(flags);
            } catch (RemoteException e) {
                Log.e(TAG, "Error while calling WindowManager", e);
            }

            //Modified for MYOS end
            Trace.endSection();
        }
    };

解鎖過程的核心實質上是鎖屏啟動了一個runnable,
通知AMS和WMS顯示鎖屏下方的activity元件視窗以及呼叫該activity元件的生命週期

    void keyguardGoingAway(int flags) {
        if (!mKeyguardShowing) {
            return;
        }
        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "keyguardGoingAway");
        mWindowManager.deferSurfaceLayout();
        try {
            setKeyguardGoingAway(true);
            mWindowManager.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY,
                    false /* alwaysKeepCurrent */, convertTransitFlags(flags),
                    false /* forceOverride */);
            updateKeyguardSleepToken();

            // Some stack visibility might change (e.g. docked stack)
            mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
            mStackSupervisor.addStartingWindowsForVisibleActivities(true /* taskSwitch */);
            mWindowManager.executeAppTransition();
        } finally {
            Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "keyguardGoingAway: surfaceLayout");
            mWindowManager.continueSurfaceLayout();
            Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
        }
    }

在系統準備解鎖完成後,PhoneWindowManager回撥KeyguardService的startKeyguardExitAnimation

  private void handleStartKeyguardExitAnimation(long startTime, long fadeoutDuration) {
      Trace.beginSection("KeyguardViewMediator#handleStartKeyguardExitAnimation");
      if (DEBUG) Log.d(TAG, "handleStartKeyguardExitAnimation startTime=" + startTime
              + " fadeoutDuration=" + fadeoutDuration);
      synchronized (KeyguardViewMediator.this) {

          if (!mHiding) {
              return;
          }
          mHiding = false;

          if (mWakeAndUnlocking && mDrawnCallback != null) {

              // Hack level over 9000: To speed up wake-and-unlock sequence, force it to report
              // the next draw from here so we don't have to wait for window manager to signal
              // this to our ViewRootImpl.
              mStatusBarKeyguardViewManager.getViewRootImpl().setReportNextDraw();
              notifyDrawn(mDrawnCallback);
              mDrawnCallback = null;
          }

          // 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);
          }

          mWakeAndUnlocking = false;
          setShowingLocked(false);
          mDismissCallbackRegistry.notifyDismissSucceeded();
          mStatusBarKeyguardViewManager.hide(startTime, fadeoutDuration);
          resetKeyguardDonePendingLocked();
          mHideAnimationRun = false;
          adjustStatusBarLocked();
          sendUserPresentBroadcast();
          mUpdateMonitor.setKeyguardGoingAway(false /* goingAway */);

          // ADD FOR FINGERPRINT SHOT BEGIN
          mFingerPrintManager.notifyFpService(1, null);
          // ADD FOR FINGERPRINT SHOT END
      }
      Trace.endSection();
  }

播放解鎖聲音、設定StatusBar的flag、發出ACTION_USER_PRESENT廣播、隱藏KeyguardView,解鎖流程結束

推薦文章

Android View的事件分發機制探索

解鎖的framework流程及動