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