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  */
31 public class FingerprintUiHelper extends FingerprintManager.AuthenticationCallback {
32 
33     @VisibleForTesting static final long ERROR_TIMEOUT_MILLIS = 1600;
34     @VisibleForTesting static final long SUCCESS_DELAY_MILLIS = 1300;
35 
36     private final FingerprintManager mFingerprintManager;
37     private final ImageView mIcon;
38     private final TextView mErrorTextView;
39     private final Callback mCallback;
40     private CancellationSignal mCancellationSignal;
41 
42     @VisibleForTesting boolean mSelfCancelled;
43 
44     /**
45      * Builder class for {@link FingerprintUiHelper} in which injected fields from Dagger
46      * holds its fields and takes other arguments in the {@link #build} method.
47      */
48     public static class FingerprintUiHelperBuilder {
49         private final FingerprintManager mFingerPrintManager;
50 
51         @Inject
FingerprintUiHelperBuilder(FingerprintManager fingerprintManager)52         public FingerprintUiHelperBuilder(FingerprintManager fingerprintManager) {
53             mFingerPrintManager = fingerprintManager;
54         }
55 
build(ImageView icon, TextView errorTextView, Callback callback)56         public FingerprintUiHelper build(ImageView icon, TextView errorTextView, Callback callback) {
57             return new FingerprintUiHelper(mFingerPrintManager, icon, errorTextView,
58                     callback);
59         }
60     }
61 
62     /**
63      * Constructor for {@link FingerprintUiHelper}. This method is expected to be called from
64      * only the {@link FingerprintUiHelperBuilder} class.
65      */
FingerprintUiHelper(FingerprintManager fingerprintManager, ImageView icon, TextView errorTextView, Callback callback)66     private FingerprintUiHelper(FingerprintManager fingerprintManager,
67             ImageView icon, TextView errorTextView, Callback callback) {
68         mFingerprintManager = fingerprintManager;
69         mIcon = icon;
70         mErrorTextView = errorTextView;
71         mCallback = callback;
72     }
73 
isFingerprintAuthAvailable()74     public boolean isFingerprintAuthAvailable() {
75         return mFingerprintManager.isHardwareDetected()
76                 && mFingerprintManager.hasEnrolledFingerprints();
77     }
78 
startListening(FingerprintManager.CryptoObject cryptoObject)79     public void startListening(FingerprintManager.CryptoObject cryptoObject) {
80         if (!isFingerprintAuthAvailable()) {
81             return;
82         }
83         mCancellationSignal = new CancellationSignal();
84         mSelfCancelled = false;
85         mFingerprintManager
86                 .authenticate(cryptoObject, mCancellationSignal, 0 /* flags */, this, null);
87         mIcon.setImageResource(R.drawable.ic_fp_40px);
88     }
89 
stopListening()90     public void stopListening() {
91         if (mCancellationSignal != null) {
92             mSelfCancelled = true;
93             mCancellationSignal.cancel();
94             mCancellationSignal = null;
95         }
96     }
97 
98     @Override
onAuthenticationError(int errMsgId, CharSequence errString)99     public void onAuthenticationError(int errMsgId, CharSequence errString) {
100         if (!mSelfCancelled) {
101             showError(errString);
102             mIcon.postDelayed(new Runnable() {
103                 @Override
104                 public void run() {
105                     mCallback.onError();
106                 }
107             }, ERROR_TIMEOUT_MILLIS);
108         }
109     }
110 
111     @Override
onAuthenticationHelp(int helpMsgId, CharSequence helpString)112     public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
113         showError(helpString);
114     }
115 
116     @Override
onAuthenticationFailed()117     public void onAuthenticationFailed() {
118         showError(mIcon.getResources().getString(
119                 R.string.fingerprint_not_recognized));
120     }
121 
122     @Override
onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result)123     public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
124         mErrorTextView.removeCallbacks(mResetErrorTextRunnable);
125         mIcon.setImageResource(R.drawable.ic_fingerprint_success);
126         mErrorTextView.setTextColor(
127                 mErrorTextView.getResources().getColor(R.color.success_color, null));
128         mErrorTextView.setText(
129                 mErrorTextView.getResources().getString(R.string.fingerprint_success));
130         mIcon.postDelayed(new Runnable() {
131             @Override
132             public void run() {
133                 mCallback.onAuthenticated();
134             }
135         }, SUCCESS_DELAY_MILLIS);
136     }
137 
showError(CharSequence error)138     private void showError(CharSequence error) {
139         mIcon.setImageResource(R.drawable.ic_fingerprint_error);
140         mErrorTextView.setText(error);
141         mErrorTextView.setTextColor(
142                 mErrorTextView.getResources().getColor(R.color.warning_color, null));
143         mErrorTextView.removeCallbacks(mResetErrorTextRunnable);
144         mErrorTextView.postDelayed(mResetErrorTextRunnable, ERROR_TIMEOUT_MILLIS);
145     }
146 
147     @VisibleForTesting
148     Runnable mResetErrorTextRunnable = new Runnable() {
149         @Override
150         public void run() {
151             mErrorTextView.setTextColor(
152                     mErrorTextView.getResources().getColor(R.color.hint_color, null));
153             mErrorTextView.setText(
154                     mErrorTextView.getResources().getString(R.string.fingerprint_hint));
155             mIcon.setImageResource(R.drawable.ic_fp_40px);
156         }
157     };
158 
159     public interface Callback {
160 
onAuthenticated()161         void onAuthenticated();
162 
onError()163         void onError();
164     }
165 }
166