1 /**
2  * Copyright (C) 2016 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.android.server.fingerprint;
18 
19 import android.content.Context;
20 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
21 import android.hardware.biometrics.BiometricPrompt;
22 import android.hardware.biometrics.IBiometricPromptReceiver;
23 import android.hardware.fingerprint.Fingerprint;
24 import android.hardware.fingerprint.FingerprintManager;
25 import android.hardware.fingerprint.IFingerprintServiceReceiver;
26 import android.os.Bundle;
27 import android.os.IBinder;
28 import android.os.RemoteException;
29 import android.util.Slog;
30 
31 import com.android.internal.logging.MetricsLogger;
32 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
33 import com.android.internal.statusbar.IStatusBarService;
34 
35 /**
36  * A class to keep track of the authentication state for a given client.
37  */
38 public abstract class AuthenticationClient extends ClientMonitor {
39     private long mOpId;
40 
handleFailedAttempt()41     public abstract int handleFailedAttempt();
resetFailedAttempts()42     public abstract void resetFailedAttempts();
43 
44     public static final int LOCKOUT_NONE = 0;
45     public static final int LOCKOUT_TIMED = 1;
46     public static final int LOCKOUT_PERMANENT = 2;
47 
48     // Callback mechanism received from the client
49     // (BiometricPrompt -> FingerprintManager -> FingerprintService -> AuthenticationClient)
50     private IBiometricPromptReceiver mDialogReceiverFromClient;
51     private Bundle mBundle;
52     private IStatusBarService mStatusBarService;
53     private boolean mInLockout;
54     private final FingerprintManager mFingerprintManager;
55     protected boolean mDialogDismissed;
56 
57     // Receives events from SystemUI and handles them before forwarding them to FingerprintDialog
58     protected IBiometricPromptReceiver mDialogReceiver = new IBiometricPromptReceiver.Stub() {
59         @Override // binder call
60         public void onDialogDismissed(int reason) {
61             if (mBundle != null && mDialogReceiverFromClient != null) {
62                 try {
63                     mDialogReceiverFromClient.onDialogDismissed(reason);
64                     if (reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL) {
65                         onError(FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED,
66                                 0 /* vendorCode */);
67                     }
68                     mDialogDismissed = true;
69                 } catch (RemoteException e) {
70                     Slog.e(TAG, "Unable to notify dialog dismissed", e);
71                 }
72                 stop(true /* initiatedByClient */);
73             }
74         }
75     };
76 
77     /**
78      * This method is called when authentication starts.
79      */
onStart()80     public abstract void onStart();
81 
82     /**
83      * This method is called when a fingerprint is authenticated or authentication is stopped
84      * (cancelled by the user, or an error such as lockout has occurred).
85      */
onStop()86     public abstract void onStop();
87 
AuthenticationClient(Context context, long halDeviceId, IBinder token, IFingerprintServiceReceiver receiver, int targetUserId, int groupId, long opId, boolean restricted, String owner, Bundle bundle, IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService)88     public AuthenticationClient(Context context, long halDeviceId, IBinder token,
89             IFingerprintServiceReceiver receiver, int targetUserId, int groupId, long opId,
90             boolean restricted, String owner, Bundle bundle,
91             IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService) {
92         super(context, halDeviceId, token, receiver, targetUserId, groupId, restricted, owner);
93         mOpId = opId;
94         mBundle = bundle;
95         mDialogReceiverFromClient = dialogReceiver;
96         mStatusBarService = statusBarService;
97         mFingerprintManager = (FingerprintManager) getContext()
98                 .getSystemService(Context.FINGERPRINT_SERVICE);
99     }
100 
101     @Override
binderDied()102     public void binderDied() {
103         super.binderDied();
104         // When the binder dies, we should stop the client. This probably belongs in
105         // ClientMonitor's binderDied(), but testing all the cases would be tricky.
106         // AuthenticationClient is the most user-visible case.
107         stop(false /* initiatedByClient */);
108     }
109 
110     @Override
onAcquired(int acquiredInfo, int vendorCode)111     public boolean onAcquired(int acquiredInfo, int vendorCode) {
112         // If the dialog is showing, the client doesn't need to receive onAcquired messages.
113         if (mBundle != null) {
114             try {
115                 if (acquiredInfo != FingerprintManager.FINGERPRINT_ACQUIRED_GOOD) {
116                     mStatusBarService.onFingerprintHelp(
117                             mFingerprintManager.getAcquiredString(acquiredInfo, vendorCode));
118                 }
119                 return false; // acquisition continues
120             } catch (RemoteException e) {
121                 Slog.e(TAG, "Remote exception when sending acquired message", e);
122                 return true; // client failed
123             } finally {
124                 // Good scans will keep the device awake
125                 if (acquiredInfo == FingerprintManager.FINGERPRINT_ACQUIRED_GOOD) {
126                     notifyUserActivity();
127                 }
128             }
129         } else {
130             return super.onAcquired(acquiredInfo, vendorCode);
131         }
132     }
133 
134     @Override
onError(int error, int vendorCode)135     public boolean onError(int error, int vendorCode) {
136         if (mDialogDismissed) {
137             // If user cancels authentication, the application has already received the
138             // FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED message from onDialogDismissed()
139             // and stopped the fingerprint hardware, so there is no need to send a
140             // FingerprintManager.FINGERPRINT_ERROR_CANCELED message.
141             return true;
142         }
143         if (mBundle != null) {
144             try {
145                 mStatusBarService.onFingerprintError(
146                         mFingerprintManager.getErrorString(error, vendorCode));
147             } catch (RemoteException e) {
148                 Slog.e(TAG, "Remote exception when sending error", e);
149             }
150         }
151         return super.onError(error, vendorCode);
152     }
153 
154     @Override
onAuthenticated(int fingerId, int groupId)155     public boolean onAuthenticated(int fingerId, int groupId) {
156         boolean result = false;
157         boolean authenticated = fingerId != 0;
158 
159         // If the fingerprint dialog is showing, notify authentication succeeded
160         if (mBundle != null) {
161             try {
162                 if (authenticated) {
163                     mStatusBarService.onFingerprintAuthenticated();
164                 } else {
165                     mStatusBarService.onFingerprintHelp(getContext().getResources().getString(
166                             com.android.internal.R.string.fingerprint_not_recognized));
167                 }
168             } catch (RemoteException e) {
169                 Slog.e(TAG, "Failed to notify Authenticated:", e);
170             }
171         }
172 
173         IFingerprintServiceReceiver receiver = getReceiver();
174         if (receiver != null) {
175             try {
176                 MetricsLogger.action(getContext(), MetricsEvent.ACTION_FINGERPRINT_AUTH,
177                         authenticated);
178                 if (!authenticated) {
179                     receiver.onAuthenticationFailed(getHalDeviceId());
180                 } else {
181                     if (DEBUG) {
182                         Slog.v(TAG, "onAuthenticated(owner=" + getOwnerString()
183                                 + ", id=" + fingerId + ", gp=" + groupId + ")");
184                     }
185                     Fingerprint fp = !getIsRestricted()
186                             ? new Fingerprint("" /* TODO */, groupId, fingerId, getHalDeviceId())
187                             : null;
188                     receiver.onAuthenticationSucceeded(getHalDeviceId(), fp, getTargetUserId());
189                 }
190             } catch (RemoteException e) {
191                 Slog.w(TAG, "Failed to notify Authenticated:", e);
192                 result = true; // client failed
193             }
194         } else {
195             result = true; // client not listening
196         }
197         if (!authenticated) {
198             if (receiver != null) {
199                 vibrateError();
200             }
201             // allow system-defined limit of number of attempts before giving up
202             int lockoutMode =  handleFailedAttempt();
203             if (lockoutMode != LOCKOUT_NONE) {
204                 try {
205                     mInLockout = true;
206                     Slog.w(TAG, "Forcing lockout (fp driver code should do this!), mode(" +
207                             lockoutMode + ")");
208                     stop(false);
209                     int errorCode = lockoutMode == LOCKOUT_TIMED ?
210                             FingerprintManager.FINGERPRINT_ERROR_LOCKOUT :
211                             FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
212 
213                     // TODO: if the dialog is showing, this error should be delayed. On a similar
214                     // note, AuthenticationClient should override onError and delay all other errors
215                     // as well, if the dialog is showing
216                     receiver.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */);
217 
218                     // Send the lockout message to the system dialog
219                     if (mBundle != null) {
220                         mStatusBarService.onFingerprintError(
221                                 mFingerprintManager.getErrorString(errorCode, 0 /* vendorCode */));
222                     }
223                 } catch (RemoteException e) {
224                     Slog.w(TAG, "Failed to notify lockout:", e);
225                 }
226             }
227             result |= lockoutMode != LOCKOUT_NONE; // in a lockout mode
228         } else {
229             if (receiver != null) {
230                 vibrateSuccess();
231             }
232             result |= true; // we have a valid fingerprint, done
233             resetFailedAttempts();
234             onStop();
235         }
236         return result;
237     }
238 
239     /**
240      * Start authentication
241      */
242     @Override
start()243     public int start() {
244         IBiometricsFingerprint daemon = getFingerprintDaemon();
245         if (daemon == null) {
246             Slog.w(TAG, "start authentication: no fingerprint HAL!");
247             return ERROR_ESRCH;
248         }
249         onStart();
250         try {
251             final int result = daemon.authenticate(mOpId, getGroupId());
252             if (result != 0) {
253                 Slog.w(TAG, "startAuthentication failed, result=" + result);
254                 MetricsLogger.histogram(getContext(), "fingeprintd_auth_start_error", result);
255                 onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
256                 return result;
257             }
258             if (DEBUG) Slog.w(TAG, "client " + getOwnerString() + " is authenticating...");
259 
260             // If authenticating with system dialog, show the dialog
261             if (mBundle != null) {
262                 try {
263                     mStatusBarService.showFingerprintDialog(mBundle, mDialogReceiver);
264                 } catch (RemoteException e) {
265                     Slog.e(TAG, "Unable to show fingerprint dialog", e);
266                 }
267             }
268         } catch (RemoteException e) {
269             Slog.e(TAG, "startAuthentication failed", e);
270             return ERROR_ESRCH;
271         }
272         return 0; // success
273     }
274 
275     @Override
stop(boolean initiatedByClient)276     public int stop(boolean initiatedByClient) {
277         if (mAlreadyCancelled) {
278             Slog.w(TAG, "stopAuthentication: already cancelled!");
279             return 0;
280         }
281 
282         onStop();
283         IBiometricsFingerprint daemon = getFingerprintDaemon();
284         if (daemon == null) {
285             Slog.w(TAG, "stopAuthentication: no fingerprint HAL!");
286             return ERROR_ESRCH;
287         }
288         try {
289             final int result = daemon.cancel();
290             if (result != 0) {
291                 Slog.w(TAG, "stopAuthentication failed, result=" + result);
292                 return result;
293             }
294             if (DEBUG) Slog.w(TAG, "client " + getOwnerString() + " is no longer authenticating");
295         } catch (RemoteException e) {
296             Slog.e(TAG, "stopAuthentication failed", e);
297             return ERROR_ESRCH;
298         } finally {
299             // If the user already cancelled authentication (via some interaction with the
300             // dialog, we do not need to hide it since it's already hidden.
301             // If the device is in lockout, don't hide the dialog - it will automatically hide
302             // after BiometricPrompt.HIDE_DIALOG_DELAY
303             if (mBundle != null && !mDialogDismissed && !mInLockout) {
304                 try {
305                     mStatusBarService.hideFingerprintDialog();
306                 } catch (RemoteException e) {
307                     Slog.e(TAG, "Unable to hide fingerprint dialog", e);
308                 }
309             }
310         }
311         mAlreadyCancelled = true;
312         return 0; // success
313     }
314 
315     @Override
onEnrollResult(int fingerId, int groupId, int remaining)316     public boolean onEnrollResult(int fingerId, int groupId, int remaining) {
317         if (DEBUG) Slog.w(TAG, "onEnrollResult() called for authenticate!");
318         return true; // Invalid for Authenticate
319     }
320 
321     @Override
onRemoved(int fingerId, int groupId, int remaining)322     public boolean onRemoved(int fingerId, int groupId, int remaining) {
323         if (DEBUG) Slog.w(TAG, "onRemoved() called for authenticate!");
324         return true; // Invalid for Authenticate
325     }
326 
327     @Override
onEnumerationResult(int fingerId, int groupId, int remaining)328     public boolean onEnumerationResult(int fingerId, int groupId, int remaining) {
329         if (DEBUG) Slog.w(TAG, "onEnumerationResult() called for authenticate!");
330         return true; // Invalid for Authenticate
331     }
332 }
333