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 41 public abstract int handleFailedAttempt(); 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 */ 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 */ 86 public abstract void onStop(); 87 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 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 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 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 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 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 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 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 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 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