├── .gitignore ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── androidessence │ │ └── fingerprintdialogsample │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── androidessence │ │ │ └── fingerprintdialogsample │ │ │ ├── FingerprintController.kt │ │ │ ├── FingerprintDialog.kt │ │ │ └── MainActivity.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_check_white_24dp.xml │ │ ├── ic_error_white_24dp.xml │ │ ├── ic_fingerprint_white_24dp.xml │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── content_main.xml │ │ └── dialog_fingerprint.xml │ │ ├── menu │ │ └── menu_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── androidessence │ └── fingerprintdialogsample │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .idea/ 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion 26 9 | defaultConfig { 10 | applicationId "com.androidessence.fingerprintdialogsample" 11 | minSdkVersion 23 12 | targetSdkVersion 26 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(dir: 'libs', include: ['*.jar']) 27 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" 28 | implementation 'com.android.support:appcompat-v7:26.1.0' 29 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 30 | implementation 'com.android.support:design:26.1.0' 31 | testImplementation 'junit:junit:4.12' 32 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 33 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 34 | } 35 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/androidessence/fingerprintdialogsample/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.androidessence.fingerprintdialogsample 2 | 3 | import android.support.test.InstrumentationRegistry 4 | import android.support.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("com.androidessence.fingerprintdialogsample", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 14 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/androidessence/fingerprintdialogsample/FingerprintController.kt: -------------------------------------------------------------------------------- 1 | package com.androidessence.fingerprintdialogsample 2 | 3 | import android.content.Context 4 | import android.support.v4.content.ContextCompat 5 | import android.support.v4.hardware.fingerprint.FingerprintManagerCompat 6 | import android.support.v4.os.CancellationSignal 7 | import android.widget.ImageView 8 | import android.widget.TextView 9 | 10 | /** 11 | * Defines the behavior required by any view used for fingerprint authentication. 12 | * 13 | * @property[fingerprintManager] The system manager for fingerprint hardware functionality. 14 | * @param[callback] A callback to the view when authentication fails or succeeds. 15 | * @param[title] The TextView that displays the title for the authentication flow. 16 | * @param[subtitle] The TextView that displays the subtitle for the authentication flow. 17 | * @param[errorText] The TextView that displays the error text for the authentication flow. 18 | * @param[icon] The ImageView that displays the icon for the authentication flow. 19 | */ 20 | class FingerprintController( 21 | private val fingerprintManager: FingerprintManagerCompat, 22 | private val callback: Callback, 23 | private val title: TextView, 24 | private val subtitle: TextView, 25 | private val errorText: TextView, 26 | private val icon: ImageView) : FingerprintManagerCompat.AuthenticationCallback() { 27 | 28 | /** 29 | * The signal that gets called if the fingerprint authentication is cancelled. 30 | */ 31 | private var cancellationSignal: CancellationSignal? = null 32 | 33 | /** 34 | * Boolean flag for whether or not authentication was cancelled by this controller or something else. 35 | */ 36 | private var selfCancelled = false 37 | 38 | /** 39 | * Determines whether or not this device can support fingerprint authentication. 40 | */ 41 | private val isFingerprintAuthAvailable: Boolean 42 | get() = fingerprintManager.isHardwareDetected && fingerprintManager.hasEnrolledFingerprints() 43 | 44 | /** 45 | * Helper variable to get the context from one of the views. The view used is arbitrary. 46 | */ 47 | private val context: Context 48 | get() = errorText.context 49 | 50 | /** 51 | * Runnable that resets the icon and error text as necessary. 52 | */ 53 | private val resetErrorTextRunnable: Runnable = Runnable { 54 | errorText.setTextColor(ContextCompat.getColor(context, R.color.hint_color)) 55 | errorText.text = context.getString(R.string.touch_sensor) 56 | icon.setImageResource(R.drawable.ic_fingerprint_white_24dp) 57 | } 58 | 59 | init { 60 | errorText.post(resetErrorTextRunnable) 61 | } 62 | 63 | /** 64 | * Begins listening for fingerprint authentication on the device. 65 | */ 66 | fun startListening(cryptoObject: FingerprintManagerCompat.CryptoObject) { 67 | if (!isFingerprintAuthAvailable) return 68 | 69 | cancellationSignal = CancellationSignal() 70 | selfCancelled = false 71 | fingerprintManager.authenticate(cryptoObject, 0, cancellationSignal, this, null) 72 | } 73 | 74 | /** 75 | * Cancels listening for fingerprint authentication. This should be done anytime your activity is killed, so that another app in the system can begin to check for the fingerprint. 76 | */ 77 | fun stopListening() { 78 | cancellationSignal?.let { 79 | selfCancelled = true 80 | it.cancel() 81 | cancellationSignal = null 82 | } 83 | } 84 | 85 | /** 86 | * Displays an error to the user if there was a problem with authentication. 87 | * 88 | * @param[text] The error message to show. 89 | */ 90 | private fun showError(text: CharSequence?) { 91 | icon.setImageResource(R.drawable.ic_error_white_24dp) 92 | errorText.text = text 93 | errorText.setTextColor(ContextCompat.getColor(errorText.context, R.color.warning_color)) 94 | errorText.removeCallbacks(resetErrorTextRunnable) 95 | errorText.postDelayed(resetErrorTextRunnable, ERROR_TIMEOUT_MILLIS) 96 | } 97 | 98 | override fun onAuthenticationError(errMsgId: Int, errString: CharSequence?) { 99 | if (!selfCancelled) { 100 | showError(errString) 101 | icon.postDelayed({ 102 | callback.onError() 103 | }, ERROR_TIMEOUT_MILLIS) 104 | } 105 | } 106 | 107 | override fun onAuthenticationSucceeded(result: FingerprintManagerCompat.AuthenticationResult?) { 108 | errorText.removeCallbacks(resetErrorTextRunnable) 109 | icon.setImageResource(R.drawable.ic_check_white_24dp) 110 | errorText.setTextColor(ContextCompat.getColor(errorText.context, R.color.success_color)) 111 | errorText.text = errorText.context.getString(R.string.fingerprint_recognized) 112 | icon.postDelayed({ 113 | callback.onAuthenticated() 114 | }, SUCCESS_DELAY_MILLIS) 115 | } 116 | 117 | override fun onAuthenticationHelp(helpMsgId: Int, helpString: CharSequence?) { 118 | showError(helpString) 119 | } 120 | 121 | override fun onAuthenticationFailed() { 122 | showError(errorText.context.getString(R.string.fingerprint_not_recognized)) 123 | } 124 | 125 | /** 126 | * Displays a title for the fingerprint authentication flow. 127 | * 128 | * @param[title] The text to be displayed. 129 | */ 130 | fun setTitle(title: CharSequence) { 131 | this.title.text = title 132 | } 133 | 134 | /** 135 | * Displays a subtitle for the fingerprint authentication flow. 136 | * 137 | * @param[subtitle] The text to be displayed. 138 | */ 139 | fun setSubtitle(subtitle: CharSequence) { 140 | this.subtitle.text = subtitle 141 | } 142 | 143 | companion object { 144 | /** 145 | * The amount of time that we should delay before showing the error message to the user. 146 | */ 147 | private val ERROR_TIMEOUT_MILLIS = 1600L 148 | 149 | /** 150 | * The amount of time that we should delay before showing the success message to the user. 151 | */ 152 | private val SUCCESS_DELAY_MILLIS = 1300L 153 | } 154 | 155 | /** 156 | * A callback that allows a class to be updated when fingerprint authentication is complete. 157 | */ 158 | interface Callback { 159 | /** 160 | * Callback method used for a successful fingerprint authentication. 161 | */ 162 | fun onAuthenticated() 163 | 164 | /** 165 | * Callback method used if there is any error authenticating the fingerprint. 166 | */ 167 | fun onError() 168 | } 169 | } -------------------------------------------------------------------------------- /app/src/main/java/com/androidessence/fingerprintdialogsample/FingerprintDialog.kt: -------------------------------------------------------------------------------- 1 | package com.androidessence.fingerprintdialogsample 2 | 3 | import android.os.Build 4 | import android.os.Bundle 5 | import android.security.keystore.KeyGenParameterSpec 6 | import android.security.keystore.KeyPermanentlyInvalidatedException 7 | import android.security.keystore.KeyProperties 8 | import android.support.v4.app.DialogFragment 9 | import android.support.v4.hardware.fingerprint.FingerprintManagerCompat 10 | import android.view.LayoutInflater 11 | import android.view.View 12 | import android.view.ViewGroup 13 | import kotlinx.android.synthetic.main.dialog_fingerprint.* 14 | import java.io.IOException 15 | import java.security.* 16 | import java.security.cert.CertificateException 17 | import javax.crypto.Cipher 18 | import javax.crypto.KeyGenerator 19 | import javax.crypto.NoSuchPaddingException 20 | import javax.crypto.SecretKey 21 | 22 | 23 | /** 24 | * DialogFragment that prompts the user to authenticate their fingerprint. 25 | */ 26 | class FingerprintDialog : DialogFragment(), FingerprintController.Callback { 27 | 28 | private val controller: FingerprintController by lazy { 29 | FingerprintController( 30 | FingerprintManagerCompat.from(context), 31 | this, 32 | titleTextView, 33 | subtitleTextView, 34 | errorTextView, 35 | iconFAB 36 | ) 37 | } 38 | 39 | /** 40 | * CryptoObject is a wrapper class for any cryptography required by the FingerprintManager. 41 | * https://developer.android.com/reference/android/support/v4/hardware/fingerprint/FingerprintManagerCompat.CryptoObject.html 42 | */ 43 | private var cryptoObject: FingerprintManagerCompat.CryptoObject? = null 44 | 45 | /** 46 | * KeyStore is the device's storage for any cryptographic keys and certificates. We use this to get a key for the fingerprint manager. 47 | * https://developer.android.com/reference/java/security/KeyStore.html 48 | */ 49 | private var keyStore: KeyStore? = null 50 | 51 | /** 52 | * This class is used to generate the keys that were reference from the [keyStore]. 53 | * https://developer.android.com/reference/javax/crypto/KeyGenerator.html 54 | */ 55 | private var keyGenerator: KeyGenerator? = null 56 | 57 | override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? = 58 | inflater?.inflate(R.layout.dialog_fingerprint, container, false) 59 | 60 | 61 | override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { 62 | super.onViewCreated(view, savedInstanceState) 63 | 64 | controller.setTitle(arguments.getString(ARG_TITLE)) 65 | controller.setSubtitle(arguments.getString(ARG_SUBTITLE)) 66 | } 67 | 68 | override fun onCreate(savedInstanceState: Bundle?) { 69 | super.onCreate(savedInstanceState) 70 | 71 | try { 72 | keyStore = KeyStore.getInstance("AndroidKeyStore") 73 | } catch (e: KeyStoreException) { 74 | throw RuntimeException("Failed to get an instance of KeyStore", e) 75 | } 76 | 77 | try { 78 | keyGenerator = KeyGenerator 79 | .getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore") 80 | } catch (e: NoSuchAlgorithmException) { 81 | throw RuntimeException("Failed to get an instance of KeyGenerator", e) 82 | } catch (e: NoSuchProviderException) { 83 | throw RuntimeException("Failed to get an instance of KeyGenerator", e) 84 | } 85 | 86 | createKey(DEFAULT_KEY_NAME, false) 87 | 88 | val defaultCipher: Cipher 89 | try { 90 | defaultCipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" 91 | + KeyProperties.BLOCK_MODE_CBC + "/" 92 | + KeyProperties.ENCRYPTION_PADDING_PKCS7) 93 | } catch (e: NoSuchAlgorithmException) { 94 | throw RuntimeException("Failed to get an instance of Cipher", e) 95 | } catch (e: NoSuchPaddingException) { 96 | throw RuntimeException("Failed to get an instance of Cipher", e) 97 | } 98 | 99 | if (initCipher(defaultCipher, DEFAULT_KEY_NAME)) { 100 | cryptoObject = FingerprintManagerCompat.CryptoObject(defaultCipher) 101 | } 102 | } 103 | 104 | override fun onResume() { 105 | super.onResume() 106 | 107 | dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) 108 | cryptoObject?.let { 109 | controller.startListening(it) 110 | } 111 | } 112 | 113 | override fun onPause() { 114 | super.onPause() 115 | controller.stopListening() 116 | } 117 | 118 | override fun onAuthenticated() { 119 | //TODO: 120 | } 121 | 122 | override fun onError() { 123 | //TODO: 124 | } 125 | 126 | /** 127 | * Lifted code from the Google samples - https://github.com/googlesamples/android-FingerprintDialog/blob/master/kotlinApp/app/src/main/java/com/example/android/fingerprintdialog/MainActivity.kt 128 | * 129 | * Initialize the [Cipher] instance with the created key in the 130 | * [.createKey] method. 131 | * 132 | * @param keyName the key name to init the cipher 133 | * @return `true` if initialization is successful, `false` if the lock screen has 134 | * been disabled or reset after the key was generated, or if a fingerprint got enrolled after 135 | * the key was generated. 136 | */ 137 | private fun initCipher(cipher: Cipher, keyName: String): Boolean { 138 | try { 139 | keyStore?.load(null) 140 | val key = keyStore?.getKey(keyName, null) as SecretKey 141 | cipher.init(Cipher.ENCRYPT_MODE, key) 142 | return true 143 | } catch (e: KeyPermanentlyInvalidatedException) { 144 | return false 145 | } catch (e: KeyStoreException) { 146 | throw RuntimeException("Failed to init Cipher", e) 147 | } catch (e: CertificateException) { 148 | throw RuntimeException("Failed to init Cipher", e) 149 | } catch (e: UnrecoverableKeyException) { 150 | throw RuntimeException("Failed to init Cipher", e) 151 | } catch (e: IOException) { 152 | throw RuntimeException("Failed to init Cipher", e) 153 | } catch (e: NoSuchAlgorithmException) { 154 | throw RuntimeException("Failed to init Cipher", e) 155 | } catch (e: InvalidKeyException) { 156 | throw RuntimeException("Failed to init Cipher", e) 157 | } 158 | } 159 | 160 | /** 161 | * Lifted code from the Google Samples - https://github.com/googlesamples/android-FingerprintDialog/blob/master/kotlinApp/app/src/main/java/com/example/android/fingerprintdialog/MainActivity.kt 162 | * 163 | * Creates a symmetric key in the Android Key Store which can only be used after the user has 164 | * authenticated with fingerprint. 165 | * 166 | * @param keyName the name of the key to be created 167 | * @param invalidatedByBiometricEnrollment if `false` is passed, the created key will not 168 | * be invalidated even if a new fingerprint is enrolled. 169 | * The default value is `true`, so passing 170 | * `true` doesn't change the behavior 171 | * (the key will be invalidated if a new fingerprint is 172 | * enrolled.). Note that this parameter is only valid if 173 | * the app works on Android N developer preview. 174 | */ 175 | private fun createKey(keyName: String, invalidatedByBiometricEnrollment: Boolean) { 176 | // The enrolling flow for fingerprint. This is where you ask the user to set up fingerprint 177 | // for your flow. Use of keys is necessary if you need to know if the set of 178 | // enrolled fingerprints has changed. 179 | try { 180 | keyStore?.load(null) 181 | // Set the alias of the entry in Android KeyStore where the key will appear 182 | // and the constrains (purposes) in the constructor of the Builder 183 | 184 | val builder = KeyGenParameterSpec.Builder(keyName, 185 | KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT) 186 | .setBlockModes(KeyProperties.BLOCK_MODE_CBC) 187 | // Require the user to authenticate with a fingerprint to authorize every use 188 | // of the key 189 | .setUserAuthenticationRequired(true) 190 | .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) 191 | 192 | // This is a workaround to avoid crashes on devices whose API level is < 24 193 | // because KeyGenParameterSpec.Builder#setInvalidatedByBiometricEnrollment is only 194 | // visible on API level +24. 195 | // Ideally there should be a compat library for KeyGenParameterSpec.Builder but 196 | // which isn't available yet. 197 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 198 | builder.setInvalidatedByBiometricEnrollment(invalidatedByBiometricEnrollment) 199 | } 200 | keyGenerator?.init(builder.build()) 201 | keyGenerator?.generateKey() 202 | } catch (e: NoSuchAlgorithmException) { 203 | throw RuntimeException(e) 204 | } catch (e: InvalidAlgorithmParameterException) { 205 | throw RuntimeException(e) 206 | } catch (e: CertificateException) { 207 | throw RuntimeException(e) 208 | } catch (e: IOException) { 209 | throw RuntimeException(e) 210 | } 211 | 212 | } 213 | 214 | companion object { 215 | /** 216 | * Fragment tag that is used when this dialog is shown. 217 | */ 218 | val FRAGMENT_TAG: String = FingerprintDialog::class.java.simpleName 219 | 220 | // Bundle keys for each of the arguments of the newInstance method. 221 | private val ARG_TITLE = "ArgTitle" 222 | private val ARG_SUBTITLE = "ArgSubtitle" 223 | 224 | private val DEFAULT_KEY_NAME = "default_key" 225 | 226 | /** 227 | * Creates a new FingerprintDialog instance with initial text setup. 228 | * 229 | * @param[title] The title of this FingerprintDialog. 230 | * @param[subtitle] The subtitle or description of the dialog. 231 | */ 232 | fun newInstance(title: String, subtitle: String): FingerprintDialog { 233 | val args = Bundle() 234 | args.putString(ARG_TITLE, title) 235 | args.putString(ARG_SUBTITLE, subtitle) 236 | 237 | val fragment = FingerprintDialog() 238 | fragment.arguments = args 239 | 240 | return fragment 241 | } 242 | } 243 | } -------------------------------------------------------------------------------- /app/src/main/java/com/androidessence/fingerprintdialogsample/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.androidessence.fingerprintdialogsample 2 | 3 | import android.os.Bundle 4 | import android.support.design.widget.Snackbar 5 | import android.support.v4.hardware.fingerprint.FingerprintManagerCompat 6 | import android.support.v7.app.AppCompatActivity 7 | 8 | import kotlinx.android.synthetic.main.activity_main.* 9 | 10 | class MainActivity : AppCompatActivity() { 11 | 12 | override fun onCreate(savedInstanceState: Bundle?) { 13 | super.onCreate(savedInstanceState) 14 | setContentView(R.layout.activity_main) 15 | setSupportActionBar(toolbar) 16 | 17 | fab.setOnClickListener { view -> 18 | val manager = FingerprintManagerCompat.from(this) 19 | 20 | if (manager.isHardwareDetected && manager.hasEnrolledFingerprints()) { 21 | showFingerprintAuth() 22 | } else { 23 | Snackbar.make(view, "Fingerprint authentication is not supported.", Snackbar.LENGTH_SHORT).show() 24 | } 25 | } 26 | } 27 | 28 | private fun showFingerprintAuth() { 29 | val dialog = FingerprintDialog.newInstance( 30 | "Sign In", 31 | "Confirm fingerprint to continue." 32 | ) 33 | dialog.show(supportFragmentManager, FingerprintDialog.FRAGMENT_TAG) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_check_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_error_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fingerprint_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 20 | 21 | 22 | 23 | 24 | 25 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_fingerprint.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 21 | 22 | 31 | 32 | 40 | 41 | 52 | 53 | 65 | 66 |