Merge "strong fp lockout after 20 failed attempts" into oc-dev
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index fccc877..6023033 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -118,6 +118,15 @@
* @hide
*/
public static final int FINGERPRINT_ERROR_VENDOR = 8;
+
+ /**
+ * The operation was canceled because FINGERPRINT_ERROR_LOCKOUT occurred too many times.
+ * Fingerprint authentication is disabled until the user unlocks with strong authentication
+ * (PIN/Pattern/Password)
+ * @hide
+ */
+ public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9;
+
/**
* @hide
*/
@@ -1013,6 +1022,9 @@
return mContext.getString(com.android.internal.R.string.fingerprint_error_canceled);
case FINGERPRINT_ERROR_LOCKOUT:
return mContext.getString(com.android.internal.R.string.fingerprint_error_lockout);
+ case FINGERPRINT_ERROR_LOCKOUT_PERMANENT:
+ return mContext.getString(
+ com.android.internal.R.string.fingerprint_error_lockout_permanent);
case FINGERPRINT_ERROR_VENDOR: {
String[] msgArray = mContext.getResources().getStringArray(
com.android.internal.R.array.fingerprint_error_vendor);
diff --git a/core/proto/android/service/fingerprint.proto b/core/proto/android/service/fingerprint.proto
index 79dba86..f88b762 100644
--- a/core/proto/android/service/fingerprint.proto
+++ b/core/proto/android/service/fingerprint.proto
@@ -54,4 +54,7 @@
// Total number of lockouts.
int32 lockout = 4;
+
+ // Total number of permanent lockouts.
+ int32 lockout_permanent = 5;
}
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 98356a2..f747d3d 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1335,6 +1335,8 @@
<string name="fingerprint_error_canceled">Fingerprint operation canceled.</string>
<!-- Generic error message shown when the fingerprint operation fails because too many attempts have been made. -->
<string name="fingerprint_error_lockout">Too many attempts. Try again later.</string>
+ <!-- Generic error message shown when the fingerprint operation fails because strong authentication is required -->
+ <string name="fingerprint_error_lockout_permanent">Too many attempts. Fingerprint sensor disabled.</string>
<!-- Generic error message shown when the fingerprint hardware can't recognize the fingerprint -->
<string name="fingerprint_error_unable_to_process">Try again.</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 74779f3..6e3f0e0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2281,6 +2281,7 @@
<java-symbol type="array" name="fingerprint_acquired_vendor" />
<java-symbol type="string" name="fingerprint_error_canceled" />
<java-symbol type="string" name="fingerprint_error_lockout" />
+ <java-symbol type="string" name="fingerprint_error_lockout_permanent" />
<java-symbol type="string" name="fingerprint_name_template" />
<!-- Fingerprint config -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 67a2989..2d30476 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -502,6 +502,12 @@
}
}
+ if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
+ mLockPatternUtils.requireStrongAuth(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT,
+ getCurrentUser());
+ }
+
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
diff --git a/services/core/java/com/android/server/fingerprint/AuthenticationClient.java b/services/core/java/com/android/server/fingerprint/AuthenticationClient.java
index fe49813..4fc6ddd 100644
--- a/services/core/java/com/android/server/fingerprint/AuthenticationClient.java
+++ b/services/core/java/com/android/server/fingerprint/AuthenticationClient.java
@@ -34,8 +34,13 @@
public abstract class AuthenticationClient extends ClientMonitor {
private long mOpId;
- public abstract boolean handleFailedAttempt();
+ public abstract int handleFailedAttempt();
public abstract void resetFailedAttempts();
+
+ public static final int LOCKOUT_NONE = 0;
+ public static final int LOCKOUT_TIMED = 1;
+ public static final int LOCKOUT_PERMANENT = 2;
+
private boolean mAlreadyCancelled;
public AuthenticationClient(Context context, long halDeviceId, IBinder token,
@@ -79,19 +84,21 @@
FingerprintUtils.vibrateFingerprintError(getContext());
}
// allow system-defined limit of number of attempts before giving up
- boolean inLockoutMode = handleFailedAttempt();
- // send lockout event in case driver doesn't enforce it.
- if (inLockoutMode) {
+ int lockoutMode = handleFailedAttempt();
+ if (lockoutMode != LOCKOUT_NONE) {
try {
- Slog.w(TAG, "Forcing lockout (fp driver code should do this!)");
- stop(false); // cancel fingerprint authentication
- receiver.onError(getHalDeviceId(),
- FingerprintManager.FINGERPRINT_ERROR_LOCKOUT, 0 /* vendorCode */);
+ Slog.w(TAG, "Forcing lockout (fp driver code should do this!), mode(" +
+ lockoutMode + ")");
+ stop(false);
+ int errorCode = lockoutMode == LOCKOUT_TIMED ?
+ FingerprintManager.FINGERPRINT_ERROR_LOCKOUT :
+ FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
+ receiver.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to notify lockout:", e);
}
}
- result |= inLockoutMode;
+ result |= lockoutMode != LOCKOUT_NONE; // in a lockout mode
} else {
if (receiver != null) {
FingerprintUtils.vibrateFingerprintSuccess(getContext());
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index 6f5b028..6cc7071 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -108,6 +108,7 @@
int acquire; // total number of acquisitions. Should be >= accept+reject due to poor image
// acquisition in some cases (too fast, too slow, dirty sensor, etc.)
int lockout; // total number of lockouts
+ int permanentLockout; // total number of permanent lockouts
}
private final ArrayList<FingerprintServiceLockoutResetMonitor> mLockoutMonitors =
@@ -118,13 +119,16 @@
Collections.synchronizedMap(new HashMap<>());
private final AppOpsManager mAppOps;
private static final long FAIL_LOCKOUT_TIMEOUT_MS = 30*1000;
- private static final int MAX_FAILED_ATTEMPTS = 5;
+ private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED = 5;
+ private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT = 20;
+
private static final long CANCEL_TIMEOUT_LIMIT = 3000; // max wait for onCancel() from HAL,in ms
private final String mKeyguardPackage;
private int mCurrentUserId = UserHandle.USER_NULL;
private final FingerprintUtils mFingerprintUtils = FingerprintUtils.getInstance();
private Context mContext;
private long mHalDeviceId;
+ private boolean mTimedLockoutCleared;
private int mFailedAttempts;
@GuardedBy("this")
private IBiometricsFingerprint mDaemon;
@@ -173,7 +177,7 @@
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_LOCKOUT_RESET.equals(intent.getAction())) {
- resetFailedAttempts();
+ resetFailedAttempts(false /* clearAttemptCounter */);
}
}
};
@@ -181,7 +185,7 @@
private final Runnable mResetFailedAttemptsRunnable = new Runnable() {
@Override
public void run() {
- resetFailedAttempts();
+ resetFailedAttempts(true /* clearAttemptCounter */);
}
};
@@ -369,6 +373,7 @@
if (client != null && client.onError(error, vendorCode)) {
removeClient(client);
}
+
if (DEBUG) Slog.v(TAG, "handleError(client="
+ (client != null ? client.getOwnerString() : "null") + ", error = " + error + ")");
// This is the magic code that starts the next client when the old client finishes.
@@ -438,7 +443,7 @@
if (client != null && client.onAcquired(acquiredInfo, vendorCode)) {
removeClient(client);
}
- if (mPerformanceStats != null && !inLockoutMode()
+ if (mPerformanceStats != null && getLockoutMode() == AuthenticationClient.LOCKOUT_NONE
&& client instanceof AuthenticationClient) {
// ignore enrollment acquisitions or acquisitions when we're locked out
mPerformanceStats.acquire++;
@@ -482,8 +487,14 @@
}
}
- private boolean inLockoutMode() {
- return mFailedAttempts >= MAX_FAILED_ATTEMPTS;
+ private int getLockoutMode() {
+ if (mFailedAttempts >= MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT) {
+ return AuthenticationClient.LOCKOUT_PERMANENT;
+ } else if (mFailedAttempts > 0 && mTimedLockoutCleared == false &&
+ (mFailedAttempts % MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED == 0)) {
+ return AuthenticationClient.LOCKOUT_TIMED;
+ }
+ return AuthenticationClient.LOCKOUT_NONE;
}
private void scheduleLockoutReset() {
@@ -801,22 +812,27 @@
AuthenticationClient client = new AuthenticationClient(getContext(), mHalDeviceId, token,
receiver, mCurrentUserId, groupId, opId, restricted, opPackageName) {
@Override
- public boolean handleFailedAttempt() {
+ public int handleFailedAttempt() {
mFailedAttempts++;
- if (mFailedAttempts == MAX_FAILED_ATTEMPTS) {
+ mTimedLockoutCleared = false;
+ final int lockoutMode = getLockoutMode();
+ if (lockoutMode == AuthenticationClient.LOCKOUT_PERMANENT) {
+ mPerformanceStats.permanentLockout++;
+ } else if (lockoutMode == AuthenticationClient.LOCKOUT_TIMED) {
mPerformanceStats.lockout++;
}
- if (inLockoutMode()) {
- // Failing multiple times will continue to push out the lockout time.
+
+ // Failing multiple times will continue to push out the lockout time
+ if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) {
scheduleLockoutReset();
- return true;
+ return lockoutMode;
}
- return false;
+ return AuthenticationClient.LOCKOUT_NONE;
}
@Override
public void resetFailedAttempts() {
- FingerprintService.this.resetFailedAttempts();
+ FingerprintService.this.resetFailedAttempts(true /* clearAttemptCounter */);
}
@Override
@@ -830,11 +846,15 @@
}
};
- if (inLockoutMode()) {
- Slog.v(TAG, "In lockout mode; disallowing authentication");
- // Don't bother starting the client. Just send the error message.
- if (!client.onError(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT, 0 /* vendorCode */)) {
- Slog.w(TAG, "Cannot send timeout message to client");
+ int lockoutMode = getLockoutMode();
+ if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) {
+ Slog.v(TAG, "In lockout mode(" + lockoutMode +
+ ") ; disallowing authentication");
+ int errorCode = lockoutMode == AuthenticationClient.LOCKOUT_TIMED ?
+ FingerprintManager.FINGERPRINT_ERROR_LOCKOUT :
+ FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
+ if (!client.onError(errorCode, 0 /* vendorCode */)) {
+ Slog.w(TAG, "Cannot send permanent lockout message to client");
}
return;
}
@@ -864,11 +884,16 @@
startClient(client, true /* initiatedByClient */);
}
- protected void resetFailedAttempts() {
- if (DEBUG && inLockoutMode()) {
- Slog.v(TAG, "Reset fingerprint lockout");
+ // attempt counter should only be cleared when Keyguard goes away or when
+ // a fingerprint is successfully authenticated
+ protected void resetFailedAttempts(boolean clearAttemptCounter) {
+ if (DEBUG && getLockoutMode() != AuthenticationClient.LOCKOUT_NONE) {
+ Slog.v(TAG, "Reset fingerprint lockout, clearAttemptCounter=" + clearAttemptCounter);
}
- mFailedAttempts = 0;
+ if (clearAttemptCounter) {
+ mFailedAttempts = 0;
+ }
+ mTimedLockoutCleared = true;
// If we're asked to reset failed attempts externally (i.e. from Keyguard),
// the alarm might still be pending; remove it.
cancelLockoutReset();
@@ -1301,6 +1326,7 @@
set.put("reject", (stats != null) ? stats.reject : 0);
set.put("acquire", (stats != null) ? stats.acquire : 0);
set.put("lockout", (stats != null) ? stats.lockout : 0);
+ set.put("permanentLockout", (stats != null) ? stats.permanentLockout : 0);
// cryptoStats measures statistics about secure fingerprint transactions
// (e.g. to unlock password storage, make secure purchases, etc.)
set.put("acceptCrypto", (cryptoStats != null) ? cryptoStats.accept : 0);
@@ -1336,6 +1362,7 @@
proto.write(FingerprintActionStatsProto.REJECT, normal.reject);
proto.write(FingerprintActionStatsProto.ACQUIRE, normal.acquire);
proto.write(FingerprintActionStatsProto.LOCKOUT, normal.lockout);
+ proto.write(FingerprintActionStatsProto.LOCKOUT_PERMANENT, normal.lockout);
proto.end(countsToken);
}
@@ -1348,6 +1375,7 @@
proto.write(FingerprintActionStatsProto.REJECT, crypto.reject);
proto.write(FingerprintActionStatsProto.ACQUIRE, crypto.acquire);
proto.write(FingerprintActionStatsProto.LOCKOUT, crypto.lockout);
+ proto.write(FingerprintActionStatsProto.LOCKOUT_PERMANENT, crypto.lockout);
proto.end(countsToken);
}