android6.0指紋識別學習 (二)--------阿冬專欄!!!
轉載自:http://blog.csdn.net/baniel01/article/details/51991764
在Android 6.0中google終於給android系統加上了指紋識別的支援,這個功能在iPhone上早就已經實現了,並且在很多廠商的定製的ROM中也都自己內部實現這個功能了,這個功能來的有點晚啊。在google全新發布的nexus裝置:nexus 5x和nexus 6p中都攜帶了一顆指紋識別晶片在裝置的背面,如下圖(圖片來自網路):
筆者手中的裝置就是圖上的那臺黑色的nexus 5x,話說這臺機器很是好看呢!手感超棒!
廢話不多說,下面我出一個指紋識別的demo app,並且詳細說明怎麼開發一個基於google api的指紋識別app。demo的原始碼在我的github上:
Android M中的指紋識別介面
這個是首先需要關注的問題,在實際動手開始寫app之前需要知道最新的平臺為我們提供了那些指紋識別的介面。所有的指紋識別介面全部在android.hardware.fingerprint這個包下,這個包中的類不是很多,如下:
api doc連結地址:
https://developer.android.com/reference/android/hardware/fingerprint/package-summary.html
大家最好FQ自己看下。
上面的圖中,我們看到這個包中總共有4個類,下面我們簡要介紹一下他們:
1.FingerprintManager:主要用來協調管理和訪問指紋識別硬體裝置
2.FingerprintManager.AuthenticationCallback這個一個callback介面,當指紋認證後系統會回撥這個介面通知app認證的結果是什麼
3.FingerprintManager.AuthenticationResult這是一個表示認證結果的類,會在回撥介面中以引數給出
4.FingerprintManager.CryptoObject這是一個加密的物件類,用來保證認證的安全性,這是一個重點,下面我們會分析。
好了,到這裡我們簡要知道了android 6.0給出的指紋識別的介面不是很多,可以說是簡短幹練。
動手開發一個指紋識別app
現在,我們要動手寫一個利用上面介面的指紋識別app,這個app介面很簡單,就一個activity,這個activity上會啟用指紋識別,然後提示使用者按下指紋,並且會將認證的結果顯示出來。
開始
在開始之前,我們需要知道使用指紋識別硬體的基本步驟:
1.在AndroidManifest.xml中申明如下許可權:
<code class="hljs xml has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">uses-permission</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:name</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"android.permission.USE_FINGERPRINT"</span>/></span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>
2.獲得FingerprintManager的物件引用
3.在執行是檢查裝置指紋識別的相容性,比如是否有指紋識別裝置等。下面我們詳細說一下上面的步驟:
申明許可權
這一步比較簡單,只要在AndroidManifest.xml中新增上面說到的許可權就可以了。
獲得FingerprintManager物件引用
這是app開發中獲得系統服務物件的常用方式,如下:
<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// Using the Android Support Library v4</span> fingerprintManager = FingerprintManagerCompat.<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">from</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>); <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// Using API level 23:</span> fingerprintManager = (FingerprintManager)getSystemService(Context.FINGERPRINT_SERVICE);</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li></ul>
上面給出兩種方式,第一種是通過V4支援包獲得相容的物件引用,這是google推行的做法;還有就是直接使用api 23 framework中的介面獲得物件引用。
檢查執行條件
要使得我們的指紋識別app能夠正常執行,有一些條件是必須滿足的。
1). API level 23
指紋識別API是在api level 23也就是android 6.0中加入的,因此我們的app必須執行在這個系統版本之上。因此google推薦使用 Android Support Library v4包來獲得FingerprintManagerCompat物件,因為在獲得的時候這個包會檢查當前系統平臺的版本。
2). 硬體
指紋識別肯定要求你的裝置上有指紋識別的硬體,因此在執行時需要檢查系統當中是不是有指紋識別的硬體:
<code class="hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">if (!fingerprintManager<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.isHardwareDetected</span>()) { // no fingerprint sensor is detected, show dialog to tell user. AlertDialog<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.Builder</span> builder = new AlertDialog<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.Builder</span>(this)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span> builder<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setTitle</span>(R<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.string</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.no</span>_sensor_dialog_title)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span> builder<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setMessage</span>(R<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.string</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.no</span>_sensor_dialog_message)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span> builder<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setIcon</span>(android<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.R</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.drawable</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.stat</span>_sys_warning)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span> builder<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setCancelable</span>(false)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span> builder<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setNegativeButton</span>(R<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.string</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.cancel</span>_btn_dialog, new DialogInterface<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.OnClickListener</span>() { @Override public void onClick(DialogInterface dialog, int which) { finish()<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span> } })<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span> // show this dialog. builder<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.create</span>()<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.show</span>()<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span> }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li></ul>
呼叫上面的介面接可以知道系統中是不是有一個這樣的硬體,如果沒有的話,那就需要做一些合適的事情,比如提示使用者當前系統中沒有指紋識別硬體等。
3). 當前裝置必須是處於安全保護中的
這個條件的意思是,你的裝置必須是使用螢幕鎖保護的,這個螢幕鎖可以是password,PIN或者圖案都行。為什麼是這樣呢?因為google原生的邏輯就是:想要使用指紋識別的話,必須首先使能螢幕鎖才行,這個和android 5.0中的smart lock邏輯是一樣的,這是因為google認為目前的指紋識別技術還是有不足之處,安全性還是不能和傳統的方式比較的。
我們可以使用下面的程式碼檢查當前裝置是不是處於安全保護中的:
<code class="hljs coffeescript has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">KeyguardManager keyguardManager =(KeyguardManager)getSystemService(Context.KEYGUARD_SERVICE); <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (keyguardManager.isKeyguardSecure()) { <span class="hljs-regexp" style="color: rgb(0, 136, 0); box-sizing: border-box;">//</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span> device <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">is</span> secure. }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li></ul>
我們使用KeyguardManager的isKeyguardSecure介面就能知道。
4). 系統中是不是有註冊的指紋
在android 6.0中,普通app要想使用指紋識別功能的話,使用者必須首先在setting中註冊至少一個指紋才行,否則是不能使用的。所以這裡我們需要檢查當前系統中是不是已經有註冊的指紋資訊了:
<code class="hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">if (!fingerprintManager<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.hasEnrolledFingerprints</span>()) { // no fingerprint image has been enrolled. AlertDialog<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.Builder</span> builder = new AlertDialog<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.Builder</span>(this)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span> builder<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setTitle</span>(R<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.string</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.no</span>_fingerprint_enrolled_dialog_title)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span> builder<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setMessage</span>(R<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.string</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.no</span>_fingerprint_enrolled_dialog_message)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span> builder<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setIcon</span>(android<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.R</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.drawable</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.stat</span>_sys_warning)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span> builder<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setCancelable</span>(false)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span> builder<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setNegativeButton</span>(R<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.string</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.cancel</span>_btn_dialog, new DialogInterface<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.OnClickListener</span>() { @Override public void onClick(DialogInterface dialog, int which) { finish()<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span> } })<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span> // show this dialog builder<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.create</span>()<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.show</span>()<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span> }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li></ul>
如果使用者還沒有註冊一個指紋的話,那麼我們的app可以提示使用者:如果想要使用指紋是功能,請再setting中註冊一個你的指紋。這裡需要囉嗦一句,如果你做過bluetooth或者其他裝置開發的話,那麼你知道你可以通過傳送一個intent來啟動bluetooth開啟的介面,只要是聲明瞭藍芽的管理許可權。但是,到目前位置google任然沒有開放讓普通app啟動指紋註冊介面的許可權,這一點我們可以從setting的AndroidManifest中看到:
<code class="hljs xml has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">activity</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:name</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">".fingerprint.FingerprintSettings"</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:exported</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"false"</span>/></span> <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">activity</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:name</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">".fingerprint.FingerprintEnrollOnboard"</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:exported</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"false"</span>/></span> <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">activity</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:name</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">".fingerprint.FingerprintEnrollFindSensor"</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:exported</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"false"</span>/></span> <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">activity</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:name</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">".fingerprint.FingerprintEnrollEnrolling"</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:exported</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"false"</span>/></span> <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">activity</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:name</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">".fingerprint.FingerprintEnrollFinish"</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:exported</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"false"</span>/></span> <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">activity</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:name</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">".fingerprint.FingerprintEnrollIntroduction"</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:exported</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"false"</span> /></span> <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">activity</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:name</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">".fingerprint.SetupFingerprintEnrollOnboard"</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:exported</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"false"</span>/></span> <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">activity</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:name</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">".fingerprint.SetupFingerprintEnrollFindSensor"</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:exported</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"false"</span>/></span> <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">activity</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:name</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">".fingerprint.SetupFingerprintEnrollEnrolling"</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:exported</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"false"</span>/></span> <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">activity</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:name</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">".fingerprint.SetupFingerprintEnrollFinish"</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:exported</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"false"</span>/></span> <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">activity</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:name</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">".fingerprint.SetupFingerprintEnrollIntroduction"</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:exported</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"true"</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:permission</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"android.permission.MANAGE_FINGERPRINT"</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:theme</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"@style/SetupWizardDisableAppStartingTheme"</span>></span> <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">intent-filter</span>></span> <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">action</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:name</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"android.settings.FINGERPRINT_SETUP"</span> /></span> <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">category</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:name</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"android.intent.category.DEFAULT"</span> /></span> <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"></<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">intent-filter</span>></span> <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"></<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">activity</span>></span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li></ul>
大部分的fingerprint設定介面都沒有exporte,只有SetupFingerprintEnrollIntroduction,但是這個介面需要android.permission.MANAGE_FINGERPRINT這個許可權,並且這個許可權只能是系統app使用,這就直接防止第三方app啟動這個介面了。(不知道日後google會不會開放這個許可權。。。。。)
一個好的app,應該在執行時都檢查一下上面的條件,防止app出現意外的錯誤。
掃描使用者按下的指紋
要開始掃描使用者按下的指紋是很簡單的,只要呼叫FingerprintManager的authenticate方法即可,那麼現在我們來看一下這個介面:
上圖是google的api文件中的描述,現在我們挨個解釋一下這些引數都是什麼:
1. crypto這是一個加密類的物件,指紋掃描器會使用這個物件來判斷認證結果的合法性。這個物件可以是null,但是這樣的話,就意味這app無條件信任認證的結果,雖然從理論上這個過程可能被攻擊,資料可以被篡改,這是app在這種情況下必須承擔的風險。因此,建議這個引數不要置為null。這個類的例項化有點麻煩,主要使用javax的security介面實現,後面我的demo程式中會給出一個helper類,這個類封裝內部實現的邏輯,開發者可以直接使用我的類簡化例項化的過程。
2. cancel 這個是CancellationSignal類的一個物件,這個物件用來在指紋識別器掃描使用者指紋的是時候取消當前的掃描操作,如果不取消的話,那麼指紋掃描器會移植掃描直到超時(一般為30s,取決於具體的廠商實現),這樣的話就會比較耗電。建議這個引數不要置為null。
3. flags 標識位,根據上圖的文件描述,這個位暫時應該為0,這個標誌位應該是保留將來使用的。
4. callback 這個是FingerprintManager.AuthenticationCallback類的物件,這個是這個介面中除了第一個引數之外最重要的引數了。當系統完成了指紋認證過程(失敗或者成功都會)後,會回撥這個物件中的介面,通知app認證的結果。這個引數不能為NULL。
5. handler 這是Handler類的物件,如果這個引數不為null的話,那麼FingerprintManager將會使用這個handler中的looper來處理來自指紋識別硬體的訊息。通常來講,開發這不用提供這個引數,可以直接置為null,因為FingerprintManager會預設使用app的main looper來處理。
取消指紋掃描
上面我們提到了取消指紋掃描的操作,這個操作是很常見的。這個時候可以使用CancellationSignal這個類的cancel方法實現:
這個方法專門用於傳送一個取消的命令給特定的監聽器,讓其取消當前操作。
因此,app可以在需要的時候呼叫cancel方法來取消指紋掃描操作。
建立CryptoObject類物件
上面我們分析FingerprintManager的authenticate方法的時候,看到這個方法的第一個引數就是CryptoObject類的物件,現在我們看一下這個物件怎麼去例項化。
我們知道,指紋識別的結果可靠性是非常重要的,我們肯定不希望認證的過程被一個第三方以某種形式攻擊,因為我們引入指紋認證的目的就是要提高安全性。但是,從理論角度來說,指紋認證的過程是可能被第三方的中介軟體惡意攻擊的,常見的攻擊的手段就是攔截和篡改指紋識別器提供的結果。這裡我們可以提供CryptoObject物件給authenticate方法來避免這種形式的攻擊。
FingerprintManager.CryptoObject是基於Java加密API的一個包裝類,並且被FingerprintManager用來保證認證結果的完整性。通常來講,用來加密指紋掃描結果的機制就是一個Javax.Crypto.Cipher物件。Cipher物件本身會使用由應用呼叫Android keystore的API產生一個key來實現上面說道的保護功能。
為了理解這些類之間是怎麼協同工作的,這裡我給出一個用於例項化CryptoObject物件的包裝類程式碼,我們先看下這個程式碼是怎麼實現的,然後再解釋一下為什麼是這樣。
<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">CryptoObjectHelper</span> {</span> <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// This can be key name you want. Should be unique for the app.</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> String KEY_NAME = <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"com.createchance.android.sample.fingerprint_authentication_key"</span>; <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// We always use this keystore on Android.</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> String KEYSTORE_NAME = <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"AndroidKeyStore"</span>; <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// Should be no need to change these values.</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> String KEY_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> String BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> String ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> String TRANSFORMATION = KEY_ALGORITHM + <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"/"</span> + BLOCK_MODE + <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"/"</span> + ENCRYPTION_PADDING; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> KeyStore _keystore; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-title" style="box-sizing: border-box;">CryptoObjectHelper</span>() <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">throws</span> Exception { _keystore = KeyStore.getInstance(KEYSTORE_NAME); _keystore.load(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>); } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> FingerprintManagerCompat.CryptoObject <span class="hljs-title" style="box-sizing: border-box;">buildCryptoObject</span>() <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">throws</span> Exception { Cipher cipher = createCipher(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>); <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> FingerprintManagerCompat.CryptoObject(cipher); } Cipher createCipher(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">boolean</span> retry) <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">throws</span> Exception { Key key = GetKey(); Cipher cipher = Cipher.getInstance(TRANSFORMATION); <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">try</span> { cipher.init(Cipher.ENCRYPT_MODE | Cipher.DECRYPT_MODE, key); } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">catch</span>(KeyPermanentlyInvalidatedException e) { _keystore.deleteEntry(KEY_NAME); <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span>(retry) { createCipher(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">false</span>); } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">throw</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> Exception(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"Could not create the cipher for fingerprint authentication."</span>, e); } } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> cipher; } Key GetKey() <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">throws</span> Exception { Key secretKey; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span>(!_keystore.isKeyEntry(KEY_NAME)) { CreateKey(); } secretKey = _keystore.getKey(KEY_NAME, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>); <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> secretKey; } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> CreateKey() <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">throws</span> Exception { KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM, KEYSTORE_NAME); KeyGenParameterSpec keyGenSpec = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(BLOCK_MODE) .setEncryptionPaddings(ENCRYPTION_PADDING) .setUserAuthenticationRequired(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>) .build(); keyGen.init(keyGenSpec); keyGen.generateKey(); } }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li><li style="box-sizing: border-box; padding: 0px 5px;">39</li><li style="box-sizing: border-box; padding: 0px 5px;">40</li><li style="box-sizing: border-box; padding: 0px 5px;">41</li><li style="box-sizing: border-box; padding: 0px 5px;">42</li><li style="box-sizing: border-box; padding: 0px 5px;">43</li><li style="box-sizing: border-box; padding: 0px 5px;">44</li><li style="box-sizing: border-box; padding: 0px 5px;">45</li><li style="box-sizing: border-box; padding: 0px 5px;">46</li><li style="box-sizing: border-box; padding: 0px 5px;">47</li><li style="box-sizing: border-box; padding: 0px 5px;">48</li><li style="box-sizing: border-box; padding: 0px 5px;">49</li><li style="box-sizing: border-box; padding: 0px 5px;">50</li><li style="box-sizing: border-box; padding: 0px 5px;">51</li><li style="box-sizing: border-box; padding: 0px 5px;">52</li><li style="box-sizing: border-box; padding: 0px 5px;">53</li><li style="box-sizing: border-box; padding: 0px 5px;">54</li><li style="box-sizing: border-box; padding: 0px 5px;">55</li><li style="box-sizing: border-box; padding: 0px 5px;">56</li><li style="box-sizing: border-box; padding: 0px 5px;">57</li><li style="box-sizing: border-box; padding: 0px 5px;">58</li><li style="box-sizing: border-box; padding: 0px 5px;">59</li><li style="box-sizing: border-box; padding: 0px 5px;">60</li><li style="box-sizing: border-box; padding: 0px 5px;">61</li><li style="box-sizing: border-box; padding: 0px 5px;">62</li><li style="box-sizing: border-box; padding: 0px 5px;">63</li><li style="box-sizing: border-box; padding: 0px 5px;">64</li><li style="box-sizing: border-box; padding: 0px 5px;">65</li><li style="box-sizing: border-box; padding: 0px 5px;">66</li><li style="box-sizing: border-box; padding: 0px 5px;">67</li><li style="box-sizing: border-box; padding: 0px 5px;">68</li><li style="box-sizing: border-box; padding: 0px 5px;">69</li><li style="box-sizing: border-box; padding: 0px 5px;">70</li><li style="box-sizing: border-box; padding: 0px 5px;">71</li><li style="box-sizing: border-box; padding: 0px 5px;">72</li><li style="box-sizing: border-box; padding: 0px 5px;">73</li><li style="box-sizing: border-box; padding: 0px 5px;">74</li><li style="box-sizing: border-box; padding: 0px 5px;">75</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0p