blob: 6e5858a4605663f3d2c89f0a9fca74f0d4e440d8 [file] [log] [blame]
Kevin Chyn037c4d52018-06-11 19:17:32 -07001/*
2 * Copyright (C) 2018 The Android Open Source Project
Jim Millercb2ce6f2016-04-13 20:28:18 -07003 *
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
Kevin Chyn037c4d52018-06-11 19:17:32 -070014 * limitations under the License
Jim Millercb2ce6f2016-04-13 20:28:18 -070015 */
16
Kevin Chyn037c4d52018-06-11 19:17:32 -070017package com.android.server.biometrics.common;
Jim Millercb2ce6f2016-04-13 20:28:18 -070018
Jim Millercb2ce6f2016-04-13 20:28:18 -070019import android.content.Context;
Kevin Chyna56dff72018-06-19 18:41:12 -070020import android.hardware.biometrics.BiometricAuthenticator;
Kevin Chyn037c4d52018-06-11 19:17:32 -070021import android.hardware.biometrics.BiometricConstants;
Vishwath Mohanecf00ce2018-04-05 10:28:24 -070022import android.hardware.biometrics.BiometricPrompt;
23import android.hardware.biometrics.IBiometricPromptReceiver;
Jim Millercb2ce6f2016-04-13 20:28:18 -070024import android.hardware.fingerprint.Fingerprint;
25import android.hardware.fingerprint.FingerprintManager;
Kevin Chynaae4a152018-01-18 11:48:09 -080026import android.os.Bundle;
Jim Millercb2ce6f2016-04-13 20:28:18 -070027import android.os.IBinder;
28import android.os.RemoteException;
Jim Millercb2ce6f2016-04-13 20:28:18 -070029import android.util.Slog;
30
Kevin Chynaae4a152018-01-18 11:48:09 -080031import com.android.internal.statusbar.IStatusBarService;
32
Jim Millercb2ce6f2016-04-13 20:28:18 -070033/**
34 * A class to keep track of the authentication state for a given client.
35 */
36public abstract class AuthenticationClient extends ClientMonitor {
37 private long mOpId;
38
Kevin Chyndf9d33e2017-05-03 21:40:12 -070039 public abstract int handleFailedAttempt();
Jim Millercb2ce6f2016-04-13 20:28:18 -070040 public abstract void resetFailedAttempts();
Kevin Chyndf9d33e2017-05-03 21:40:12 -070041
42 public static final int LOCKOUT_NONE = 0;
43 public static final int LOCKOUT_TIMED = 1;
44 public static final int LOCKOUT_PERMANENT = 2;
45
Kevin Chynaae4a152018-01-18 11:48:09 -080046 // Callback mechanism received from the client
Vishwath Mohanecf00ce2018-04-05 10:28:24 -070047 // (BiometricPrompt -> FingerprintManager -> FingerprintService -> AuthenticationClient)
48 private IBiometricPromptReceiver mDialogReceiverFromClient;
Kevin Chynaae4a152018-01-18 11:48:09 -080049 private Bundle mBundle;
50 private IStatusBarService mStatusBarService;
51 private boolean mInLockout;
Kevin Chyn037c4d52018-06-11 19:17:32 -070052 // TODO: BiometricManager, after other biometric modalities are introduced.
Kevin Chynaae4a152018-01-18 11:48:09 -080053 private final FingerprintManager mFingerprintManager;
54 protected boolean mDialogDismissed;
55
Kevin Chyndba919a2018-03-16 14:35:10 -070056 // Receives events from SystemUI and handles them before forwarding them to FingerprintDialog
Vishwath Mohanecf00ce2018-04-05 10:28:24 -070057 protected IBiometricPromptReceiver mDialogReceiver = new IBiometricPromptReceiver.Stub() {
Kevin Chynaae4a152018-01-18 11:48:09 -080058 @Override // binder call
59 public void onDialogDismissed(int reason) {
60 if (mBundle != null && mDialogReceiverFromClient != null) {
61 try {
62 mDialogReceiverFromClient.onDialogDismissed(reason);
Vishwath Mohanecf00ce2018-04-05 10:28:24 -070063 if (reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL) {
Kevin Chyna56dff72018-06-19 18:41:12 -070064 onError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED,
Kevin Chynaae4a152018-01-18 11:48:09 -080065 0 /* vendorCode */);
66 }
67 mDialogDismissed = true;
68 } catch (RemoteException e) {
Kevin Chyn037c4d52018-06-11 19:17:32 -070069 Slog.e(getLogTag(), "Unable to notify dialog dismissed", e);
Kevin Chynaae4a152018-01-18 11:48:09 -080070 }
71 stop(true /* initiatedByClient */);
72 }
73 }
74 };
75
Kevin Chynd4f43c22018-03-12 17:33:13 -070076 /**
77 * This method is called when authentication starts.
78 */
79 public abstract void onStart();
80
81 /**
82 * This method is called when a fingerprint is authenticated or authentication is stopped
83 * (cancelled by the user, or an error such as lockout has occurred).
84 */
85 public abstract void onStop();
86
Kevin Chyn037c4d52018-06-11 19:17:32 -070087 public AuthenticationClient(Context context, Metrics metrics,
88 BiometricService.DaemonWrapper daemon, long halDeviceId, IBinder token,
89 BiometricService.ServiceListener listener, int targetUserId, int groupId, long opId,
Kevin Chynaae4a152018-01-18 11:48:09 -080090 boolean restricted, String owner, Bundle bundle,
Vishwath Mohanecf00ce2018-04-05 10:28:24 -070091 IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService) {
Kevin Chyn037c4d52018-06-11 19:17:32 -070092 super(context, metrics, daemon, halDeviceId, token, listener, targetUserId, groupId,
93 restricted, owner);
Jim Millercb2ce6f2016-04-13 20:28:18 -070094 mOpId = opId;
Kevin Chynaae4a152018-01-18 11:48:09 -080095 mBundle = bundle;
96 mDialogReceiverFromClient = dialogReceiver;
97 mStatusBarService = statusBarService;
98 mFingerprintManager = (FingerprintManager) getContext()
99 .getSystemService(Context.FINGERPRINT_SERVICE);
100 }
101
102 @Override
103 public void binderDied() {
104 super.binderDied();
105 // When the binder dies, we should stop the client. This probably belongs in
106 // ClientMonitor's binderDied(), but testing all the cases would be tricky.
107 // AuthenticationClient is the most user-visible case.
108 stop(false /* initiatedByClient */);
109 }
110
111 @Override
112 public boolean onAcquired(int acquiredInfo, int vendorCode) {
113 // If the dialog is showing, the client doesn't need to receive onAcquired messages.
114 if (mBundle != null) {
115 try {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700116 if (acquiredInfo != BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) {
Kevin Chynaae4a152018-01-18 11:48:09 -0800117 mStatusBarService.onFingerprintHelp(
118 mFingerprintManager.getAcquiredString(acquiredInfo, vendorCode));
119 }
120 return false; // acquisition continues
121 } catch (RemoteException e) {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700122 Slog.e(getLogTag(), "Remote exception when sending acquired message", e);
Kevin Chynaae4a152018-01-18 11:48:09 -0800123 return true; // client failed
124 } finally {
125 // Good scans will keep the device awake
Kevin Chyn037c4d52018-06-11 19:17:32 -0700126 if (acquiredInfo == BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) {
Kevin Chynaae4a152018-01-18 11:48:09 -0800127 notifyUserActivity();
128 }
129 }
130 } else {
131 return super.onAcquired(acquiredInfo, vendorCode);
132 }
133 }
134
135 @Override
Kevin Chyna56dff72018-06-19 18:41:12 -0700136 public boolean onError(long deviceId, int error, int vendorCode) {
Kevin Chynaae4a152018-01-18 11:48:09 -0800137 if (mDialogDismissed) {
138 // If user cancels authentication, the application has already received the
139 // FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED message from onDialogDismissed()
140 // and stopped the fingerprint hardware, so there is no need to send a
141 // FingerprintManager.FINGERPRINT_ERROR_CANCELED message.
142 return true;
143 }
144 if (mBundle != null) {
145 try {
146 mStatusBarService.onFingerprintError(
147 mFingerprintManager.getErrorString(error, vendorCode));
148 } catch (RemoteException e) {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700149 Slog.e(getLogTag(), "Remote exception when sending error", e);
Kevin Chynaae4a152018-01-18 11:48:09 -0800150 }
151 }
Kevin Chyna56dff72018-06-19 18:41:12 -0700152 return super.onError(deviceId, error, vendorCode);
Jim Millercb2ce6f2016-04-13 20:28:18 -0700153 }
154
155 @Override
156 public boolean onAuthenticated(int fingerId, int groupId) {
157 boolean result = false;
158 boolean authenticated = fingerId != 0;
159
Kevin Chynaae4a152018-01-18 11:48:09 -0800160 // If the fingerprint dialog is showing, notify authentication succeeded
161 if (mBundle != null) {
162 try {
163 if (authenticated) {
164 mStatusBarService.onFingerprintAuthenticated();
165 } else {
166 mStatusBarService.onFingerprintHelp(getContext().getResources().getString(
167 com.android.internal.R.string.fingerprint_not_recognized));
168 }
169 } catch (RemoteException e) {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700170 Slog.e(getLogTag(), "Failed to notify Authenticated:", e);
Kevin Chynaae4a152018-01-18 11:48:09 -0800171 }
172 }
173
Kevin Chyn037c4d52018-06-11 19:17:32 -0700174 final BiometricService.ServiceListener listener = getListener();
175 if (listener != null) {
Jim Millercb2ce6f2016-04-13 20:28:18 -0700176 try {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700177 mMetricsLogger.action(mMetrics.actionBiometricAuth(), authenticated);
Jim Millercb2ce6f2016-04-13 20:28:18 -0700178 if (!authenticated) {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700179 listener.onAuthenticationFailed(getHalDeviceId());
Jim Millercb2ce6f2016-04-13 20:28:18 -0700180 } else {
181 if (DEBUG) {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700182 Slog.v(getLogTag(), "onAuthenticated(owner=" + getOwnerString()
Jim Millercb2ce6f2016-04-13 20:28:18 -0700183 + ", id=" + fingerId + ", gp=" + groupId + ")");
184 }
185 Fingerprint fp = !getIsRestricted()
186 ? new Fingerprint("" /* TODO */, groupId, fingerId, getHalDeviceId())
187 : null;
Kevin Chyn037c4d52018-06-11 19:17:32 -0700188 listener.onAuthenticationSucceeded(getHalDeviceId(), fp, getTargetUserId());
Jim Millercb2ce6f2016-04-13 20:28:18 -0700189 }
190 } catch (RemoteException e) {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700191 Slog.w(getLogTag(), "Failed to notify Authenticated:", e);
Jim Millercb2ce6f2016-04-13 20:28:18 -0700192 result = true; // client failed
193 }
194 } else {
195 result = true; // client not listening
196 }
Jim Millerd1974862016-05-03 18:35:18 -0700197 if (!authenticated) {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700198 if (listener != null) {
Michael Wright6726fd52017-06-27 00:41:45 +0100199 vibrateError();
Jim Millercb2ce6f2016-04-13 20:28:18 -0700200 }
201 // allow system-defined limit of number of attempts before giving up
Kevin Chyndf9d33e2017-05-03 21:40:12 -0700202 int lockoutMode = handleFailedAttempt();
203 if (lockoutMode != LOCKOUT_NONE) {
Jim Millerd1974862016-05-03 18:35:18 -0700204 try {
Kevin Chynaae4a152018-01-18 11:48:09 -0800205 mInLockout = true;
Kevin Chyn037c4d52018-06-11 19:17:32 -0700206 Slog.w(getLogTag(), "Forcing lockout (fp driver code should do this!), mode(" +
Kevin Chyndf9d33e2017-05-03 21:40:12 -0700207 lockoutMode + ")");
208 stop(false);
209 int errorCode = lockoutMode == LOCKOUT_TIMED ?
Kevin Chyn037c4d52018-06-11 19:17:32 -0700210 BiometricConstants.BIOMETRIC_ERROR_LOCKOUT :
211 BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
Kevin Chynaae4a152018-01-18 11:48:09 -0800212
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
Kevin Chyn037c4d52018-06-11 19:17:32 -0700216 listener.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */);
Kevin Chynaae4a152018-01-18 11:48:09 -0800217
218 // Send the lockout message to the system dialog
219 if (mBundle != null) {
220 mStatusBarService.onFingerprintError(
221 mFingerprintManager.getErrorString(errorCode, 0 /* vendorCode */));
222 }
Jim Millerd1974862016-05-03 18:35:18 -0700223 } catch (RemoteException e) {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700224 Slog.w(getLogTag(), "Failed to notify lockout:", e);
Jim Millerd1974862016-05-03 18:35:18 -0700225 }
226 }
Kevin Chyndf9d33e2017-05-03 21:40:12 -0700227 result |= lockoutMode != LOCKOUT_NONE; // in a lockout mode
Jim Millercb2ce6f2016-04-13 20:28:18 -0700228 } else {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700229 if (listener != null) {
Michael Wright6726fd52017-06-27 00:41:45 +0100230 vibrateSuccess();
Jim Millercb2ce6f2016-04-13 20:28:18 -0700231 }
232 result |= true; // we have a valid fingerprint, done
233 resetFailedAttempts();
Kevin Chynd4f43c22018-03-12 17:33:13 -0700234 onStop();
Jim Millercb2ce6f2016-04-13 20:28:18 -0700235 }
236 return result;
237 }
238
239 /**
240 * Start authentication
241 */
242 @Override
243 public int start() {
Kevin Chynd4f43c22018-03-12 17:33:13 -0700244 onStart();
Jim Millercb2ce6f2016-04-13 20:28:18 -0700245 try {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700246 final int result = getDaemonWrapper().authenticate(mOpId, getGroupId());
Jim Millercb2ce6f2016-04-13 20:28:18 -0700247 if (result != 0) {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700248 Slog.w(getLogTag(), "startAuthentication failed, result=" + result);
249 mMetricsLogger.histogram(mMetrics.tagAuthStartError(), result);
Kevin Chyna56dff72018-06-19 18:41:12 -0700250 onError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
251 0 /* vendorCode */);
Jim Millercb2ce6f2016-04-13 20:28:18 -0700252 return result;
253 }
Kevin Chyn037c4d52018-06-11 19:17:32 -0700254 if (DEBUG) Slog.w(getLogTag(), "client " + getOwnerString() + " is authenticating...");
Kevin Chynaae4a152018-01-18 11:48:09 -0800255
256 // If authenticating with system dialog, show the dialog
257 if (mBundle != null) {
258 try {
259 mStatusBarService.showFingerprintDialog(mBundle, mDialogReceiver);
260 } catch (RemoteException e) {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700261 Slog.e(getLogTag(), "Unable to show fingerprint dialog", e);
Kevin Chynaae4a152018-01-18 11:48:09 -0800262 }
263 }
Jim Millercb2ce6f2016-04-13 20:28:18 -0700264 } catch (RemoteException e) {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700265 Slog.e(getLogTag(), "startAuthentication failed", e);
Jim Millercb2ce6f2016-04-13 20:28:18 -0700266 return ERROR_ESRCH;
267 }
268 return 0; // success
269 }
270
271 @Override
272 public int stop(boolean initiatedByClient) {
Kevin Chyn625a0142017-04-10 14:53:59 -0700273 if (mAlreadyCancelled) {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700274 Slog.w(getLogTag(), "stopAuthentication: already cancelled!");
Kevin Chyn625a0142017-04-10 14:53:59 -0700275 return 0;
276 }
Kevin Chynaae4a152018-01-18 11:48:09 -0800277
Kevin Chynd4f43c22018-03-12 17:33:13 -0700278 onStop();
Kevin Chyn037c4d52018-06-11 19:17:32 -0700279
Jim Millercb2ce6f2016-04-13 20:28:18 -0700280 try {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700281 final int result = getDaemonWrapper().cancel();
Jim Millercb2ce6f2016-04-13 20:28:18 -0700282 if (result != 0) {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700283 Slog.w(getLogTag(), "stopAuthentication failed, result=" + result);
Jim Millercb2ce6f2016-04-13 20:28:18 -0700284 return result;
285 }
Kevin Chyn037c4d52018-06-11 19:17:32 -0700286 if (DEBUG) Slog.w(getLogTag(), "client " + getOwnerString() + " is no longer authenticating");
Jim Millercb2ce6f2016-04-13 20:28:18 -0700287 } catch (RemoteException e) {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700288 Slog.e(getLogTag(), "stopAuthentication failed", e);
Jim Millercb2ce6f2016-04-13 20:28:18 -0700289 return ERROR_ESRCH;
Kevin Chynaae4a152018-01-18 11:48:09 -0800290 } finally {
291 // If the user already cancelled authentication (via some interaction with the
292 // dialog, we do not need to hide it since it's already hidden.
293 // If the device is in lockout, don't hide the dialog - it will automatically hide
Vishwath Mohanecf00ce2018-04-05 10:28:24 -0700294 // after BiometricPrompt.HIDE_DIALOG_DELAY
Kevin Chynaae4a152018-01-18 11:48:09 -0800295 if (mBundle != null && !mDialogDismissed && !mInLockout) {
296 try {
297 mStatusBarService.hideFingerprintDialog();
298 } catch (RemoteException e) {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700299 Slog.e(getLogTag(), "Unable to hide fingerprint dialog", e);
Kevin Chynaae4a152018-01-18 11:48:09 -0800300 }
301 }
Jim Millercb2ce6f2016-04-13 20:28:18 -0700302 }
Kevin Chyn037c4d52018-06-11 19:17:32 -0700303
Kevin Chyn625a0142017-04-10 14:53:59 -0700304 mAlreadyCancelled = true;
Jim Millercb2ce6f2016-04-13 20:28:18 -0700305 return 0; // success
306 }
307
308 @Override
Kevin Chyna56dff72018-06-19 18:41:12 -0700309 public boolean onEnrollResult(BiometricAuthenticator.Identifier identifier,
310 int remaining) {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700311 if (DEBUG) Slog.w(getLogTag(), "onEnrollResult() called for authenticate!");
Jim Millercb2ce6f2016-04-13 20:28:18 -0700312 return true; // Invalid for Authenticate
313 }
314
315 @Override
Kevin Chyna56dff72018-06-19 18:41:12 -0700316 public boolean onRemoved(BiometricAuthenticator.Identifier identifier, int remaining) {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700317 if (DEBUG) Slog.w(getLogTag(), "onRemoved() called for authenticate!");
Jim Millercb2ce6f2016-04-13 20:28:18 -0700318 return true; // Invalid for Authenticate
319 }
320
321 @Override
Kevin Chyna56dff72018-06-19 18:41:12 -0700322 public boolean onEnumerationResult(BiometricAuthenticator.Identifier identifier,
323 int remaining) {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700324 if (DEBUG) Slog.w(getLogTag(), "onEnumerationResult() called for authenticate!");
Jim Millercb2ce6f2016-04-13 20:28:18 -0700325 return true; // Invalid for Authenticate
326 }
327}