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