1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.example.android.asymmetricfingerprintdialog;
18 
19 import com.google.common.annotations.VisibleForTesting;
20 
21 import android.hardware.fingerprint.FingerprintManager;
22 import android.os.CancellationSignal;
23 import android.widget.ImageView;
24 import android.widget.TextView;
25 
26 import javax.inject.Inject;
27 
28 /**
29  * Small helper class to manage text/icon around fingerprint authentication UI.
30  * This class assumes that the {@link android.Manifest.permission#USE_FINGERPRINT}
31  * permission has already been granted. (As of API 23 this permission is normal instead of dangerous
32  * and is granted at install time.)
33  */
34 @SuppressWarnings("MissingPermission")
35 public class FingerprintUiHelper extends FingerprintManager.AuthenticationCallback {
36 
37     @VisibleForTesting static final long ERROR_TIMEOUT_MILLIS = 1600;
38     @VisibleForTesting static final long SUCCESS_DELAY_MILLIS = 1300;
39 
40     private final FingerprintManager mFingerprintManager;
41     private final ImageView mIcon;
42     private final TextView mErrorTextView;
43     private final Callback mCallback;
44     private CancellationSignal mCancellationSignal;
45 
46     @VisibleForTesting boolean mSelfCancelled;
47 
48     /**
49      * Builder class for {@link FingerprintUiHelper} in which injected fields from Dagger
50      * holds its fields and takes other arguments in the {@link #build} method.
51      */
52     public static class FingerprintUiHelperBuilder {
53         private final FingerprintManager mFingerPrintManager;
54 
55         @Inject
FingerprintUiHelperBuilder(FingerprintManager fingerprintManager)56         public FingerprintUiHelperBuilder(FingerprintManager fingerprintManager) {
57             mFingerPrintManager = fingerprintManager;
58         }
59 
build(ImageView icon, TextView errorTextView, Callback callback)60         public FingerprintUiHelper build(ImageView icon, TextView errorTextView, Callback callback) {
61             return new FingerprintUiHelper(mFingerPrintManager, icon, errorTextView,
62                     callback);
63         }
64     }
65 
66     /**
67      * Constructor for {@link FingerprintUiHelper}. This method is expected to be called from
68      * only the {@link FingerprintUiHelperBuilder} class.
69      */
FingerprintUiHelper(FingerprintManager fingerprintManager, ImageView icon, TextView errorTextView, Callback callback)70     private FingerprintUiHelper(FingerprintManager fingerprintManager,
71             ImageView icon, TextView errorTextView, Callback callback) {
72         mFingerprintManager = fingerprintManager;
73         mIcon = icon;
74         mErrorTextView = errorTextView;
75         mCallback = callback;
76     }
77 
isFingerprintAuthAvailable()78     public boolean isFingerprintAuthAvailable() {
79         return mFingerprintManager.isHardwareDetected()
80                 && mFingerprintManager.hasEnrolledFingerprints();
81     }
82 
startListening(FingerprintManager.CryptoObject cryptoObject)83     public void startListening(FingerprintManager.CryptoObject cryptoObject) {
84         if (!isFingerprintAuthAvailable()) {
85             return;
86         }
87         mCancellationSignal = new CancellationSignal();
88         mSelfCancelled = false;
89         mFingerprintManager
90                 .authenticate(cryptoObject, mCancellationSignal, 0 /* flags */, this, null);
91         mIcon.setImageResource(R.drawable.ic_fp_40px);
92     }
93 
stopListening()94     public void stopListening() {
95         if (mCancellationSignal != null) {
96             mSelfCancelled = true;
97             mCancellationSignal.cancel();
98             mCancellationSignal = null;
99         }
100     }
101 
102     @Override
onAuthenticationError(int errMsgId, CharSequence errString)103     public void onAuthenticationError(int errMsgId, CharSequence errString) {
104         if (!mSelfCancelled) {
105             showError(errString);
106             mIcon.postDelayed(new Runnable() {
107                 @Override
108                 public void run() {
109                     mCallback.onError();
110                 }
111             }, ERROR_TIMEOUT_MILLIS);
112         }
113     }
114 
115     @Override
onAuthenticationHelp(int helpMsgId, CharSequence helpString)116     public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
117         showError(helpString);
118     }
119 
120     @Override
onAuthenticationFailed()121     public void onAuthenticationFailed() {
122         showError(mIcon.getResources().getString(
123                 R.string.fingerprint_not_recognized));
124     }
125 
126     @Override
onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result)127     public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
128         mErrorTextView.removeCallbacks(mResetErrorTextRunnable);
129         mIcon.setImageResource(R.drawable.ic_fingerprint_success);
130         mErrorTextView.setTextColor(
131                 mErrorTextView.getResources().getColor(R.color.success_color, null));
132         mErrorTextView.setText(
133                 mErrorTextView.getResources().getString(R.string.fingerprint_success));
134         mIcon.postDelayed(new Runnable() {
135             @Override
136             public void run() {
137                 mCallback.onAuthenticated();
138             }
139         }, SUCCESS_DELAY_MILLIS);
140     }
141 
showError(CharSequence error)142     private void showError(CharSequence error) {
143         mIcon.setImageResource(R.drawable.ic_fingerprint_error);
144         mErrorTextView.setText(error);
145         mErrorTextView.setTextColor(
146                 mErrorTextView.getResources().getColor(R.color.warning_color, null));
147         mErrorTextView.removeCallbacks(mResetErrorTextRunnable);
148         mErrorTextView.postDelayed(mResetErrorTextRunnable, ERROR_TIMEOUT_MILLIS);
149     }
150 
151     @VisibleForTesting
152     Runnable mResetErrorTextRunnable = new Runnable() {
153         @Override
154         public void run() {
155             mErrorTextView.setTextColor(
156                     mErrorTextView.getResources().getColor(R.color.hint_color, null));
157             mErrorTextView.setText(
158                     mErrorTextView.getResources().getString(R.string.fingerprint_hint));
159             mIcon.setImageResource(R.drawable.ic_fp_40px);
160         }
161     };
162 
163     public interface Callback {
164 
onAuthenticated()165         void onAuthenticated();
166 
onError()167         void onError();
168     }
169 }
170