前面對於MediaPlayer的系統研究,剛剛開始,由於其他原因現在要先暫停一下。這次要看的模塊是android 4.2 系統中的Keyguard模塊。在接觸之后才發現,android4.2的keyguard模塊與之前相比,變化挺大的,最起碼名字上變化挺大的。由於對於Android系統了解不是很深入,而且知識和經驗都比較弱,在文中肯定有不恰當或者錯誤的地方,請各位路過的大神不吝指正。
在Android 4.2中,keyguard的模塊存放的目錄是在[/frameworks/base/policy/src/com/android/internal/policy/impl/keyguard/], 我也可以看到在同級目錄下有個文件夾叫做keyguard_obsolete,這里的應該是在之前使用的版本,不過我們今天的重點不是在它,而在keyguard目錄。在Android系統中,說到解鎖,最容易讓人想起的是開機之后的滑動解鎖畫面,這個也是Android系統的默認的畫面。我們可以在Android手機的Settings應用中改變解鎖方式,比如面部解鎖,圖形解鎖,PIN碼解鎖,Password解鎖等方式。在Android4.2中的解鎖畫面,又添加了新的內容,比如可以左右滑動,並且可以在解鎖的畫面上添加Calendar, Email,Clock,Message等不同的widget。下面我們就說在Android 4.2上的鎖屏機制。
先從解鎖的畫面說起。
Keyguard的窗口從哪里來?
Android系統中的畫面及用戶的交互,通常都是在Activity中進行的,不過這個Keyguard這塊有點不同。如果你在Keyguard模塊的目錄中進行搜索的話,是找不到Activity這樣的關鍵字的。從這個搜索結果可以看出,Android系統中默認的keyguard最起碼沒有使用Activity。那么,keyguard的畫面是如何呈現出來的呢?帶着這個問題,開始Keyguard的探索之旅。第一次接觸到Keyguard的時候,我不知道如何入手去看這塊代碼,我記得當時是當Power按下后,屏幕點亮時會顯示出Keyguard。上次是從這個角度去了解Keyguard的,多少有點收獲,也為再次看Keyguard模塊打下點基礎吧。之所以說起這個事情,是想告誡自己,求知不要心急,慢慢來不見得是壞事,市場回頭看一眼,會有不一樣的收獲。讓子彈飛一會,不要着急。有了上次的些微了解之后,這次是從起源開始了解Keyguard是如何繪制出來的。我們知道,Keyguard是系統啟動之后就出現的(注:Android系統在標准情況下,刷機或格式化后的首次啟動,是開始基本設置,即setupWizard),可以從這點入手。還有一點是,在Android系統中和Keyguard相關的類大多是Keyguard開頭。Android系統中的大管家SystemServer啟動后,在一切准備妥當之后,會根據需要通知不同的service.systemReady。Keyguard的啟動就是從WindowManagerService的systemReady開始的,在WindowManagerService.systemReady()中會調用PhoneWindowManager的systemReady,因為PhoneWindowManager是WindowManagerPolicy的子類。在PhoneWindowManager中會判斷KeyguarViewMediator是否已經初始化完成,其實在PhoneWindowManager的init的時候,這個對象就已經創建完畢。 這部分從SystemServer准備就緒,到WindowManagerService的systemReady,一直到PhoneWindowManager的systemReady都比較簡單,這里就不再列出代碼。我們知道在Keyguard的中已經存在了KeyguardViewManager了,為什么還要KeyguardViewMediator呢? 我們可以從KeyguardViewMediator的功能的角度看看這個原因, 先看Android的設計者對這個類的描述:
這是一個調解和keyguard相關請求的了類。它包括了查詢keyguard的狀態,電源管理相關的事件,因為電源管理事件會影響keyguard的設置或重置,回調PhoneWindowManager通知它說keyguard正在顯示,或者解鎖成功等狀態。注:keyguard畫面是在屏幕關閉的時候顯示的,所以當屏幕亮起來的時候,keyguard畫面能夠直接准備好了。比如,查詢keyguard的例子:某個事件能夠喚醒Keyguard嗎?keyguard正在顯示嗎?某個事件會被鎖屏的狀態約束而不起作用嗎?回調PhoneWindowManager的情況:鎖屏正在顯示。 導致鎖屏狀態變化的樣例:屏幕關閉,重置鎖屏,並且顯示出來以便於下次屏幕亮起是能夠直接顯示。鍵盤打開,如果keyguard是不安全的,就隱藏它。從解鎖畫面發生的事件:用戶成功解鎖,隱藏解鎖畫面,不再約束用戶的輸入事件。注:除了電源管理能夠影響keyguard的狀態外,其他的一些app或者service可能會通過方法setKeyguardEnable去關閉keyguard。比如接到電話時。這個類是在WindowManagerPolicy初始化的時候創建的,並且運行在WindowMangerPolicy所在的線程,keyguard的畫面從這個線程中創建的當keyguardViewMediator構建時。但是Keyguard相關的api可能會被其他的線程調用,比如InputManagerService和windowManagerService。因此在keyguardViewMediator的方法是同步的,並且任何一個和Keyguard畫面相關的事件都投擲到Handler中以確保在UI線程中處理。
對KeyguardViewMediator有了大概的了解之后,我們下面就直接從KeyguardViewMediator的onSystemReady開始分析Keyguard畫面顯示的過程,這個方法的代碼如下:
1 /** 2 * Let us know that the system is ready after startup. 3 */ 4 public void onSystemReady() { 5 mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); 6 synchronized (this) { 7 if (DEBUG) Log.d(TAG, "onSystemReady"); 8 mSystemReady = true;//注冊一個回調函數,用來監視系統中的用戶切換變化,手機狀態變化,sim卡狀態變化等等 9 mUpdateMonitor.registerCallback(mUpdateCallback); 10 11 // Suppress biometric unlock right after boot until things have settled if it is the 12 // selected security method, otherwise unsuppress it. It must be unsuppressed if it is 13 // not the selected security method for the following reason: if the user starts 14 // without a screen lock selected, the biometric unlock would be suppressed the first 15 // time they try to use it. 16 // 17 // Note that the biometric unlock will still not show if it is not the selected method. 18 // Calling setAlternateUnlockEnabled(true) simply says don't suppress it if it is the 19 // selected method.使用生物技術解鎖,比如聲紋解鎖解鎖技術等。不常見 20 if (mLockPatternUtils.usingBiometricWeak() 21 && mLockPatternUtils.isBiometricWeakInstalled()) { 22 if (DEBUG) Log.d(TAG, "suppressing biometric unlock during boot"); 23 mUpdateMonitor.setAlternateUnlockEnabled(false); 24 } else { 25 mUpdateMonitor.setAlternateUnlockEnabled(true); 26 } 27 28 doKeyguardLocked();//比較重要的一個方法 29 } 30 // Most services aren't available until the system reaches the ready state, so we 31 // send it here when the device first boots. 32 maybeSendUserPresentBroadcast(); 33 }
這個方法主要就是注冊了一個監聽系統中某些屬性發生變化時的回調函數,再有就是開始鎖屏。方法doKeyguardLocked的代碼如下:
1 private void doKeyguardLocked() { 2 doKeyguardLocked(null); 3 } 4 5 /** 6 * Enable the keyguard if the settings are appropriate. 7 */ 8 private void doKeyguardLocked(Bundle options) { 9 // 如果有其他的應用關閉了keyguard的話,不用顯示 10 if (!mExternallyEnabled) { 11 if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled"); 12 13 // note: we *should* set mNeedToReshowWhenReenabled=true here, but that makes 14 // for an occasional ugly flicker in this situation: 15 // 1) receive a call with the screen on (no keyguard) or make a call 16 // 2) screen times out 17 // 3) user hits key to turn screen back on 18 // instead, we reenable the keyguard when we know the screen is off and the call 19 // ends (see the broadcast receiver below) 20 // TODO: clean this up when we have better support at the window manager level 21 // for apps that wish to be on top of the keyguard 22 return; 23 } 24 25 // 如果keyguard正在顯示的話,就不用再向下執行了。 26 if (mKeyguardViewManager.isShowing()) { 27 if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing"); 28 return; 29 } 30 31 // if the setup wizard hasn't run yet, don't show 32 final boolean requireSim = !SystemProperties.getBoolean("keyguard.no_require_sim", 33 false); 34 final boolean provisioned = mUpdateMonitor.isDeviceProvisioned(); 35 final IccCardConstants.State state = mUpdateMonitor.getSimState(); 36 final boolean lockedOrMissing = state.isPinLocked() 37 || ((state == IccCardConstants.State.ABSENT 38 || state == IccCardConstants.State.PERM_DISABLED) 39 && requireSim); 40 41 if (!lockedOrMissing && !provisioned) { 42 if (DEBUG) Log.d(TAG, "doKeyguard: not showing because device isn't provisioned" 43 + " and the sim is not locked or missing"); 44 return; 45 } 46 47 if (mUserManager.getUsers(true).size() < 2 48 && mLockPatternUtils.isLockScreenDisabled() && !lockedOrMissing) { 49 if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off"); 50 return; 51 } 52 53 if (DEBUG) Log.d(TAG, "doKeyguard: showing the lock screen"); 54 showLocked(options);//開始顯示keyguard畫面,注意這里的參數是null 55 }
通過這個來決定keyguard是否應該顯示,在正常情況下(非來電,非多用戶,非首次啟動等等),決定顯示keyguard畫面,然后把這個發送消息到mHandler,不過都是在WindowManagerPolicy的線程中,使用的是同一個線程進行的loop。在顯示畫面的過程中,要一直保持設備處於亮着狀態。處理SHOW消息是由方法handleShow完成的,代碼如下:
1 private void handleShow(Bundle options) { 2 synchronized (KeyguardViewMediator.this) { 3 if (DEBUG) Log.d(TAG, "handleShow"); 4 if (!mSystemReady) return; 5 //調用KeyguardviewManager進行畫面的顯示 6 mKeyguardViewManager.show(options); 7 mShowing = true; 8 mKeyguardDonePending = false; 9 updateActivityLockScreenState();//和ActivityManagerService交互 10 adjustStatusBarLocked();//調整StatusBar中顯示的一些內容,disable一些在statusBar中進行的操作 11 userActivity(); //通知PowerManagerService有用戶事件發生,及時更新屏幕超時時間為10s 12 try { 13 ActivityManagerNative.getDefault().closeSystemDialogs("lock"); 14 } catch (RemoteException e) { 15 } 16 17 // 播放解鎖的完成的聲音。 18 playSounds(true); 19 //在發送SHOW消息的時候,申請過這個wakelock,在這里釋放 20 mShowKeyguardWakeLock.release(); 21 } 22 }
在這個方法中主要就是調用KeyguardViewManager,讓其去顯示我們所需要的UI.在完成現實之后,處理一些必要事情,不如更新KeyguardViewMediator的一些屬性,和ActivityManagerService,StatusBarService,PowerManagerService等的交互。和播放解鎖聲。看來畫面的顯示還在下一步,我們接着往下看:
[KeyguardViewManager.java]
1 public synchronized void show(Bundle options) {//此時參數為null 2 if (DEBUG) Log.d(TAG, "show(); mKeyguardView==" + mKeyguardView); 3 //在手機上返回值應該是false 4 boolean enableScreenRotation = shouldEnableScreenRotation(); 5 //這里就是創建Keyguard UI的地方。 6 maybeCreateKeyguardLocked(enableScreenRotation, false, options); 7 maybeEnableScreenRotation(enableScreenRotation); 8 9 // Disable common aspects of the system/status/navigation bars that are not appropriate or 10 // useful on any keyguard screen but can be re-shown by dialogs or SHOW_WHEN_LOCKED 11 // activities. Other disabled bits are handled by the KeyguardViewMediator talking 12 // directly to the status bar service. 13 final int visFlags = View.STATUS_BAR_DISABLE_HOME; 14 if (DEBUG) Log.v(TAG, "show:setSystemUiVisibility(" + Integer.toHexString(visFlags)+")"); 15 mKeyguardHost.setSystemUiVisibility(visFlags); 16 17 mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); 18 mKeyguardHost.setVisibility(View.VISIBLE); 19 mKeyguardView.show(); 20 mKeyguardView.requestFocus(); 21 }
這個方法的主要功能就是創建一個能夠承載KeyguardView的地方,然后把這個View給顯示出來。我們先看看創建能夠承載KeyguardView的方法maybeCreateKeyguardLocked,代碼如下:
1 private void maybeCreateKeyguardLocked(boolean enableScreenRotation, boolean force, 2 Bundle options) { 3 final boolean isActivity = (mContext instanceof Activity); // 這里的mContext是來自SystemServer的Context,因此返回值應該是false 4 5 if (mKeyguardHost != null) { 6 mKeyguardHost.saveHierarchyState(mStateContainer); 7 } 8 9 if (mKeyguardHost == null) {//這里分析的是開機之后的Keyguard的第一次顯示,mKeyguardHost應該是null。因此會進入這個判斷分支 10 if (DEBUG) Log.d(TAG, "keyguard host is null, creating it..."); 11 12 mKeyguardHost = new ViewManagerHost(mContext); 13 //創建承載Keyguard的窗口的屬性 14 int flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 15 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR 16 | WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN 17 | WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; 18 19 if (!mNeedsInput) { 20 flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 21 } 22 if (ActivityManager.isHighEndGfx()) { 23 flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; 24 } 25 26 final int stretch = ViewGroup.LayoutParams.MATCH_PARENT; 27 final int type = isActivity ? WindowManager.LayoutParams.TYPE_APPLICATION 28 : WindowManager.LayoutParams.TYPE_KEYGUARD;//Type應該是WindowManager.LayoutParams.TYPE_KEYGUARD 29 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 30 stretch, stretch, type, flags, PixelFormat.TRANSLUCENT); 31 lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; 32 lp.windowAnimations = com.android.internal.R.style.Animation_LockScreen; 33 lp.screenOrientation = enableScreenRotation ? 34 ActivityInfo.SCREEN_ORIENTATION_USER : ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; 35 36 if (ActivityManager.isHighEndGfx()) { 37 lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; 38 lp.privateFlags |= 39 WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED; 40 } 41 lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SET_NEEDS_MENU_KEY; 42 if (isActivity) { 43 lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; 44 } 45 lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY; 46 lp.setTitle(isActivity ? "KeyguardMock" : "Keyguard"); 47 mWindowLayoutParams = lp; 48 mViewManager.addView(mKeyguardHost, lp);//注意這里,如果閱讀過Activity啟動相關的代碼的話,這個地方會很熟悉 49 } 50 51 if (force || mKeyguardView == null) { 52 inflateKeyguardView(options); 53 mKeyguardView.requestFocus(); 54 } 55 updateUserActivityTimeoutInWindowLayoutParams(); 56 mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); 57 58 mKeyguardHost.restoreHierarchyState(mStateContainer); 59 }
在這個方法中,首先判斷目前使用的解鎖方式是以應用的方式提供的還是系統默認的,如果是以應用程序方式提供的解鎖,那么它的Context應該是是一個Activity。我們這里討論的是系統自帶的解鎖方式,所以isActivity的值應該是false的。這是系統啟動之后的首次開始解鎖,所以KeyguardViewManager中的屬性除了在構造函數中初始化過的,其他均應為null或未初始化。接下來因為mKeyguardHost還沒有創建,所以,接下來就是創建ViewManagerHost的實例,注意,這里是的mKeyguardHost是ViewManagerHost的實例,它是KeyguardViewManager的一個內部類,繼承自FrameLayout類。然后定義Keyguard窗口對應的一些特定的屬性,這些屬性中尤其注意type類型是WindowManager.LayoutParams.TYPE_KEYGUARD,這是一個系統級別的窗口,關於這個屬性的使用,在Activity啟動過程中圖像的描繪時會仔細討論的。把所需要的參數都定義完成之后,就開始把ViewManagerHost最終通過WindowManagerImpl添加到窗口中。添加過程不是本次的重點,這里忽略。后面的代碼不是很復雜,很容易明白,就不再一一介紹。到這里我們的目的已經達到了,回頭想一下,我們是為了尋找Keyguard的畫面是在沒有Activity的情況下如何呈現出來的,這里已經給出了答案,把ViewManagerHost添加到窗口中,這里的ViewManagerHost是繼承自FrameLayout的,相當於一個view的容器,只要把我們所需要的View布局到這個容器中,系統會幫助我們完成繪制的過程。關於View的繪制過程,網上有很多文章分析Activity的啟動過程,或者分析WindowManageService的時候都會說到這個問題,這些內容不是本次的重點,這里略過。
KeyguardView是如何布局到窗口?
我們已經擁有了View的容器了,接下來我們可以分析Keyguard的是如何把不同的解鎖方式的畫面放進這個容器的,也就是我們要開始分析Android系統各種解鎖畫面是如何布局到我們得到的容器中的。下面我們接着上面的addView之后分析。我們一直分析道這兒,在上面代碼中51line,mKeyguardView還沒有見到,在這里判斷條件是成立的,接着執行inflateKeyguardView。從這個方法的名字可以看出,這就是把view布局到窗口的函數,而他的參數是我們之前已經說明的為null。讓我們一起看看是如何布局keyguardview的,其代碼如下:
1 private void inflateKeyguardView(Bundle options) { 2 View v = mKeyguardHost.findViewById(R.id.keyguard_host_view); 3 if (v != null) {//如果已經把keyguardhost布局到ViewManagerHostview中的話,這里再把它清除掉 4 mKeyguardHost.removeView(v); 5 } 6 // TODO: Remove once b/7094175 is fixed 7 if (false) Slog.d(TAG, "inflateKeyguardView: b/7094175 mContext.config=" 8 + mContext.getResources().getConfiguration()); 9 final LayoutInflater inflater = LayoutInflater.from(mContext); 10 View view = inflater.inflate(R.layout.keyguard_host_view, mKeyguardHost, true);//把KeyguardHostView布局到ViewManagerHost中,當把View布局完成后,會調用子view的onfinishInflate方法 11 mKeyguardView = (KeyguardHostView) view.findViewById(R.id.keyguard_host_view); 12 mKeyguardView.setLockPatternUtils(mLockPatternUtils); 13 mKeyguardView.setViewMediatorCallback(mViewMediatorCallback); 14 mKeyguardView.initializeSwitchingUserState(options != null && 15 options.getBoolean(IS_SWITCHING_USER)); 16 17 // HACK 18 // The keyguard view will have set up window flags in onFinishInflate before we set 19 // the view mediator callback. Make sure it knows the correct IME state. 20 if (mViewMediatorCallback != null) {//如果這個判斷條件成立,僅僅是找到某個View而已,還沒有開始show 21 KeyguardPasswordView kpv = (KeyguardPasswordView) mKeyguardView.findViewById( 22 R.id.keyguard_password_view); 23 24 if (kpv != null) { 25 mViewMediatorCallback.setNeedsInput(kpv.needsInput()); 26 } 27 } 28 29 if (options != null) { 30 int widgetToShow = options.getInt(LockPatternUtils.KEYGUARD_SHOW_APPWIDGET, 31 AppWidgetManager.INVALID_APPWIDGET_ID); 32 if (widgetToShow != AppWidgetManager.INVALID_APPWIDGET_ID) { 33 mKeyguardView.goToWidget(widgetToShow); 34 } 35 } 36 }
首先判斷在ViewManagerHost是不是已經存在KeyguardHostView了,如果已經存在了,先把它移除了,然后重新把它布局到ViewManagerHost中。在布局完成后調用KeyguardHostView的哦你FinishInflate方法。
接着為KeyguardHostView設置毀掉函數和屬性。既然要把KeyguardHostView布局到我們得到的容器中,我們就看看KeyguardHostView的結構式怎樣的。keyguardHostView對應的布局文件在
[/frameworks/base/core/res/res/layout-port/keyguard_host_view.xml],這個布局文件對應的ViewTree如下:
這個就是我們Android系統中KeyguardHostView的view樹。我們知道在Android4.2的keyguard系統中,在鎖屏界面是可以左右滑動的。作用滑動式通過ViewFlipper實現的,這里的keyguard_widget_remove_drop_target是用來添加widget的容器,對應的畫面是帶有加號,能夠添加widget的畫面。 而在keyguard_widget_paper中可以盛放各種widget,比如時鍾,郵件等。而我們經常使用的解鎖方式是放在keyguardSecurityContainer中的。到這里,我們貌似得到還是一個View容器。只不過是把KeyguardHostView這個容器布局到ViewManagerHost這個容器中,到現在還是沒有見到我們的解鎖方式是如何呈現的。先別急,接下來我們就分析,各種view是如何取得的。之前我們是為了尋找keyguard的窗口是如何獲得的,然后我們得到了這個窗口,並且還得到了一個view容器,然后在這個容器中又布局了一個容器KeyguardHostView,現在我們一步一步的return到前面的調用的函數,一直回到keyguardViewManager.show()方法,這個方法在取得能夠盛放view的容器KeyguardHostView后開始調用KeyguardHostView的show函數,及mKeyguardView.show(),代碼如下:
1 public void show() {//為了縮減文章的長度,把代碼中一些log,注釋和一些不必要的代碼給刪除了,並不影響對keyguard的理解 2 showPrimarySecurityScreen(false); 3 } 4 void showPrimarySecurityScreen(boolean turningOff) { 5 SecurityMode securityMode = mSecurityModel.getSecurityMode();//獲取當前的鎖屏的方式,通過查找數據庫,等會重點分析此函數 6 showSecurityScreen(securityMode); 7 } 8 9 private void showSecurityScreen(SecurityMode securityMode) { 10 if (securityMode == mCurrentSecuritySelection) return; 11 //注意,這里的變量的類型,是KeyguardSecrityView,根據securityMode去獲取相應的View 12 KeyguardSecurityView oldView = getSecurityView(mCurrentSecuritySelection); 13 KeyguardSecurityView newView = getSecurityView(securityMode); 14 15 // Enter full screen mode if we're in SIM or Account screen 16 boolean fullScreenEnabled = getResources().getBoolean( 17 com.android.internal.R.bool.kg_sim_puk_account_full_screen); 18 boolean isSimOrAccount = securityMode == SecurityMode.SimPin 19 || securityMode == SecurityMode.SimPuk 20 || securityMode == SecurityMode.Account; 21 mAppWidgetContainer.setVisibility( 22 isSimOrAccount && fullScreenEnabled ? View.GONE : View.VISIBLE); 23 24 if (mSlidingChallengeLayout != null) {//mSlidingChallengeLayout對應上面ViewTree中的SlidingChallengeLayout 25 mSlidingChallengeLayout.setChallengeInteractive(!fullScreenEnabled); 26 } 27 28 if (oldView != null) { 29 oldView.onPause();//模擬聲明周期的方式來管理各個解鎖畫面 30 oldView.setKeyguardCallback(mNullCallback); // ignore requests from old view 31 } 32 newView.onResume(KeyguardSecurityView.VIEW_REVEALED); 33 newView.setKeyguardCallback(mCallback); 34 35 final boolean needsInput = newView.needsInput(); 36 if (mViewMediatorCallback != null) { 37 mViewMediatorCallback.setNeedsInput(needsInput); 38 } 39 40 // Find and show this child. 41 final int childCount = mSecurityViewContainer.getChildCount(); 42 //切換時的動畫,新的view進入的動畫方式,前一個view退出的動畫方式 43 mSecurityViewContainer.setInAnimation( 44 AnimationUtils.loadAnimation(mContext, R.anim.keyguard_security_fade_in)); 45 mSecurityViewContainer.setOutAnimation( 46 AnimationUtils.loadAnimation(mContext, R.anim.keyguard_security_fade_out)); 47 final int securityViewIdForMode = getSecurityViewIdForMode(securityMode); 48 for (int i = 0; i < childCount; i++) { 49 if (mSecurityViewContainer.getChildAt(i).getId() == securityViewIdForMode) { 50 mSecurityViewContainer.setDisplayedChild(i);//這個方法會把選擇的view設置上,並且會調用requestLayout把畫面更新出來 51 break; 52 } 53 } 54 55 if (securityMode == SecurityMode.None) { 56 // Discard current runnable if we're switching back to the selector view 57 setOnDismissAction(null); 58 } 59 if (securityMode == SecurityMode.Account && !mLockPatternUtils.isPermanentlyLocked()) { 60 // we're showing account as a backup, provide a way to get back to primary 61 setBackButtonEnabled(true); 62 } 63 mCurrentSecuritySelection = securityMode; 64 }
在選擇畫面之前,要先確定我們鎖屏的方式,即SecurityMode,這個mode就是對應各種各樣的解鎖方式。如果不做修改的話,設置之后,無論是手機是否重啟,都會保持這個模式不變,粗費自己手動在Settings應用中的鎖屏方式中做出修改。因此,我們可以確定鎖屏的方式一定是以某種方式存儲到磁盤上了,通過SecurityModel.getSecurityMode()可以讀取之前存儲的鎖屏模式,其代碼如下:
1 [SecurityMode.java] 2 SecurityMode getSecurityMode() { 3 KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext); 4 final IccCardConstants.State simState = updateMonitor.getSimState();//獲取SIM卡的狀態,查看SIM是否被鎖住 5 SecurityMode mode = SecurityMode.None;//這里的每一個SecurityMode都對應一個view, 6 if (simState == IccCardConstants.State.PIN_REQUIRED) { 7 mode = SecurityMode.SimPin; 8 } else if (simState == IccCardConstants.State.PUK_REQUIRED 9 && mLockPatternUtils.isPukUnlockScreenEnable()) { 10 mode = SecurityMode.SimPuk; 11 } else {//通過LockPatternUtils和LockSettingsService通信,打開locksettings.db數據庫,讀取保存的解鎖方式 12 final int security = mLockPatternUtils.getKeyguardStoredPasswordQuality(); 13 switch (security) { 14 case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: 15 mode = mLockPatternUtils.isLockPasswordEnabled() ? 16 SecurityMode.PIN : SecurityMode.None; 17 break; 18 case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC: 19 case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC: 20 case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX: 21 mode = mLockPatternUtils.isLockPasswordEnabled() ? 22 SecurityMode.Password : SecurityMode.None; 23 break; 24 25 case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: 26 case DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED: 27 if (mLockPatternUtils.isLockPatternEnabled()) { 28 mode = mLockPatternUtils.isPermanentlyLocked() ? 29 SecurityMode.Account : SecurityMode.Pattern; 30 } 31 break; 32 33 default: 34 throw new IllegalStateException("Unknown unlock mode:" + mode); 35 } 36 } 37 return mode; 38 } 39 [KeyguardHostView.java] 40 private int getSecurityViewIdForMode(SecurityMode securityMode) { 41 switch (securityMode) { 42 case None: return R.id.keyguard_selector_view;//這個對應的是默認的解鎖方式,即滑動的方式解鎖 43 case Pattern: return R.id.keyguard_pattern_view; 44 case PIN: return R.id.keyguard_pin_view; 45 case Password: return R.id.keyguard_password_view; 46 case Biometric: return R.id.keyguard_face_unlock_view; 47 case Account: return R.id.keyguard_account_view; 48 case SimPin: return R.id.keyguard_sim_pin_view; 49 case SimPuk: return R.id.keyguard_sim_puk_view; 50 } 51 return 0; 52 }
在通過LockPatternUtils去查找數據庫的時候,需要和LockSettingsService通信,這里就體現了鎖屏的一個重要的保護措施,對調用這個方法的UserId進行嚴格的檢查。通過這種方式獲得解鎖方式,然后找到這個解鎖方式對應的畫面,即KeyguardSecurityView。在getSecurityViewForMode中僅僅是獲取到這些view的id,在getSecurityMode方法中還要把這些view布局到KeyguardSecurityViewContainer中,實現的代碼如下:
1 private KeyguardSecurityView getSecurityView(SecurityMode securityMode) { 2 final int securityViewIdForMode = getSecurityViewIdForMode(securityMode);//根據SecurityMode獲取對應的view的Id 3 KeyguardSecurityView view = null; 4 final int children = mSecurityViewContainer.getChildCount();//在開始的時候,mSecurityViewContainer中應該是沒有任何view,這里的初始值應該為0 5 for (int child = 0; child < children; child++) { 6 if (mSecurityViewContainer.getChildAt(child).getId() == securityViewIdForMode) { 7 view = ((KeyguardSecurityView)mSecurityViewContainer.getChildAt(child)); 8 break; 9 } 10 } 11 int layoutId = getLayoutIdFor(securityMode);//根據SecurityMode獲取對應的布局文件 12 if (view == null && layoutId != 0) { 13 final LayoutInflater inflater = LayoutInflater.from(mContext); 14 if (DEBUG) Log.v(TAG, "inflating id = " + layoutId); 15 View v = inflater.inflate(layoutId, mSecurityViewContainer, false);//把布局文件布局到KeyguardSecurityViewContainer中 16 mSecurityViewContainer.addView(v);//把找到的view添加到KeyguardSecurityViewContainer中 17 updateSecurityView(v); 18 view = (KeyguardSecurityView)v; 19 } 20 21 if (view instanceof KeyguardSelectorView) { 22 KeyguardSelectorView selectorView = (KeyguardSelectorView) view; 23 View carrierText = selectorView.findViewById(R.id.keyguard_selector_fade_container); 24 selectorView.setCarrierArea(carrierText); 25 } 26 27 return view;//注意,返回值的類型是keyguardSecurityView 28
在繼續分析view是如何顯示的之前,我們還要再補充一點,關於各種keyguard的畫面和KeyguardSecurityView之間的關系。如下幾種典型的解鎖方式,及其結構關系圖:
上面這幅圖僅僅是Keyguard解鎖方式和View相關的類圖。不過圖中的類KeyguardSelector這個類從名字上不容易和我們一直的解鎖方式的畫面對應起來,其實這類中包含的是GlowPadView,就是在Android4.2開機之后默認的以滑動方式解鎖。不過呢,這樣我們從這幅圖中還是可以看到,解鎖方式一共有五大類,分別是KeyguardFaceLockedView,KeyguardAccountView,KeyguardPatternView,KeyguardAbsInputView,GlowPadView。其中所有的需要輸入數字或者字符的解鎖方式都歸為KeyguardAbsInputView。從這幅圖中,我們還可以發現特點就是,這些解鎖 畫面有兩個共同的基類,一個是KeyguardSecurityView,一個是LinearLayout。這兩個基類中,keyguardSecurityView是為了讓keyguard系統便於管理這些畫面而存在;LinearLayout是為了讓這些View能夠正常地使用View系統描繪而存在的。所以只要KeyguardViewManager持有KeyguardSecurityView這個基類,就可以指向其任一子類的實例,即不同的解鎖畫面。這是面向對象的多態性。至於要使用的那個解鎖方式,是通過LockPatternUtils和LockSettingsService通信,讀取數據庫來獲得的。
如何解鎖?
可以分為密碼類型的解鎖方式,圖案類型的解鎖方式。這部分內容,通過代碼很容易能看明白,就不再敘述