diff options
18 files changed, 935 insertions, 72 deletions
diff --git a/Android.bp b/Android.bp index d1332bb4fe87..c5160fdf7ece 100644 --- a/Android.bp +++ b/Android.bp @@ -147,9 +147,10 @@ java_library { "core/java/android/hardware/display/IDisplayManager.aidl", "core/java/android/hardware/display/IDisplayManagerCallback.aidl", "core/java/android/hardware/display/IVirtualDisplayCallback.aidl", + "core/java/android/hardware/fingerprint/IFingerprintClientActiveCallback.aidl", + "core/java/android/hardware/fingerprint/IFingerprintDialogReceiver.aidl", "core/java/android/hardware/fingerprint/IFingerprintService.aidl", "core/java/android/hardware/fingerprint/IFingerprintServiceLockoutResetCallback.aidl", - "core/java/android/hardware/fingerprint/IFingerprintClientActiveCallback.aidl", "core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl", "core/java/android/hardware/hdmi/IHdmiControlCallback.aidl", "core/java/android/hardware/hdmi/IHdmiControlService.aidl", diff --git a/api/current.txt b/api/current.txt index 5f153e6d91a9..5365cacfdc0a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -16339,6 +16339,20 @@ package android.hardware.display { package android.hardware.fingerprint { + public class FingerprintDialog { + method public void authenticate(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.CancellationSignal, java.util.concurrent.Executor, android.hardware.fingerprint.FingerprintManager.AuthenticationCallback); + method public void authenticate(android.os.CancellationSignal, java.util.concurrent.Executor, android.hardware.fingerprint.FingerprintManager.AuthenticationCallback); + } + + public static class FingerprintDialog.Builder { + ctor public FingerprintDialog.Builder(); + method public android.hardware.fingerprint.FingerprintDialog build(android.content.Context); + method public android.hardware.fingerprint.FingerprintDialog.Builder setDescription(java.lang.CharSequence); + method public android.hardware.fingerprint.FingerprintDialog.Builder setNegativeButton(java.lang.CharSequence, java.util.concurrent.Executor, android.content.DialogInterface.OnClickListener); + method public android.hardware.fingerprint.FingerprintDialog.Builder setSubtitle(java.lang.CharSequence); + method public android.hardware.fingerprint.FingerprintDialog.Builder setTitle(java.lang.CharSequence); + } + public class FingerprintManager { method public void authenticate(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.CancellationSignal, int, android.hardware.fingerprint.FingerprintManager.AuthenticationCallback, android.os.Handler); method public boolean hasEnrolledFingerprints(); diff --git a/core/java/android/hardware/fingerprint/FingerprintDialog.java b/core/java/android/hardware/fingerprint/FingerprintDialog.java new file mode 100644 index 000000000000..6b7fab773b43 --- /dev/null +++ b/core/java/android/hardware/fingerprint/FingerprintDialog.java @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.fingerprint; + +import static android.Manifest.permission.USE_FINGERPRINT; + +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.content.Context; +import android.content.DialogInterface; +import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback; +import android.hardware.fingerprint.FingerprintManager.AuthenticationResult; +import android.hardware.fingerprint.IFingerprintDialogReceiver; +import android.os.Bundle; +import android.os.CancellationSignal; +import android.text.TextUtils; + +import java.util.concurrent.Executor; + +/** + * A class that manages a system-provided fingerprint dialog. + */ +public class FingerprintDialog { + + /** + * @hide + */ + public static final String KEY_TITLE = "title"; + /** + * @hide + */ + public static final String KEY_SUBTITLE = "subtitle"; + /** + * @hide + */ + public static final String KEY_DESCRIPTION = "description"; + /** + * @hide + */ + public static final String KEY_POSITIVE_TEXT = "positive_text"; + /** + * @hide + */ + public static final String KEY_NEGATIVE_TEXT = "negative_text"; + + /** + * Error/help message will show for this amount of time. + * For error messages, the dialog will also be dismissed after this amount of time. + * Error messages will be propagated back to the application via AuthenticationCallback + * after this amount of time. + * @hide + */ + public static final int HIDE_DIALOG_DELAY = 3000; // ms + /** + * @hide + */ + public static final int DISMISSED_REASON_POSITIVE = 1; + + /** + * @hide + */ + public static final int DISMISSED_REASON_NEGATIVE = 2; + + /** + * @hide + */ + public static final int DISMISSED_REASON_USER_CANCEL = 3; + + private static class ButtonInfo { + Executor executor; + DialogInterface.OnClickListener listener; + ButtonInfo(Executor ex, DialogInterface.OnClickListener l) { + executor = ex; + listener = l; + } + } + + /** + * A builder that collects arguments, to be shown on the system-provided fingerprint dialog. + **/ + public static class Builder { + private final Bundle bundle; + private ButtonInfo positiveButtonInfo; + private ButtonInfo negativeButtonInfo; + + /** + * Creates a builder for a fingerprint dialog. + */ + public Builder() { + bundle = new Bundle(); + } + + /** + * Required: Set the title to display. + * @param title + * @return + */ + public Builder setTitle(@NonNull CharSequence title) { + bundle.putCharSequence(KEY_TITLE, title); + return this; + } + + /** + * Optional: Set the subtitle to display. + * @param subtitle + * @return + */ + public Builder setSubtitle(@NonNull CharSequence subtitle) { + bundle.putCharSequence(KEY_SUBTITLE, subtitle); + return this; + } + + /** + * Optional: Set the description to display. + * @param description + * @return + */ + public Builder setDescription(@NonNull CharSequence description) { + bundle.putCharSequence(KEY_DESCRIPTION, description); + return this; + } + + /** + * Optional: Set the text for the positive button. If not set, the positive button + * will not show. + * @param text + * @return + * @hide + */ + public Builder setPositiveButton(@NonNull CharSequence text, + @NonNull @CallbackExecutor Executor executor, + @NonNull DialogInterface.OnClickListener listener) { + if (TextUtils.isEmpty(text)) { + throw new IllegalArgumentException("Text must be set and non-empty"); + } + if (executor == null) { + throw new IllegalArgumentException("Executor must not be null"); + } + if (listener == null) { + throw new IllegalArgumentException("Listener must not be null"); + } + bundle.putCharSequence(KEY_POSITIVE_TEXT, text); + positiveButtonInfo = new ButtonInfo(executor, listener); + return this; + } + + /** + * Required: Set the text for the negative button. + * @param text + * @return + */ + public Builder setNegativeButton(@NonNull CharSequence text, + @NonNull @CallbackExecutor Executor executor, + @NonNull DialogInterface.OnClickListener listener) { + if (TextUtils.isEmpty(text)) { + throw new IllegalArgumentException("Text must be set and non-empty"); + } + if (executor == null) { + throw new IllegalArgumentException("Executor must not be null"); + } + if (listener == null) { + throw new IllegalArgumentException("Listener must not be null"); + } + bundle.putCharSequence(KEY_NEGATIVE_TEXT, text); + negativeButtonInfo = new ButtonInfo(executor, listener); + return this; + } + + /** + * Creates a {@link FingerprintDialog} with the arguments supplied to this builder. + * @param context + * @return a {@link FingerprintDialog} + * @throws IllegalArgumentException if any of the required fields are not set. + */ + public FingerprintDialog build(Context context) { + final CharSequence title = bundle.getCharSequence(KEY_TITLE); + final CharSequence negative = bundle.getCharSequence(KEY_NEGATIVE_TEXT); + + if (TextUtils.isEmpty(title)) { + throw new IllegalArgumentException("Title must be set and non-empty"); + } else if (TextUtils.isEmpty(negative)) { + throw new IllegalArgumentException("Negative text must be set and non-empty"); + } + return new FingerprintDialog(context, bundle, positiveButtonInfo, negativeButtonInfo); + } + } + + private FingerprintManager mFingerprintManager; + private Bundle mBundle; + private ButtonInfo mPositiveButtonInfo; + private ButtonInfo mNegativeButtonInfo; + + IFingerprintDialogReceiver mDialogReceiver = new IFingerprintDialogReceiver.Stub() { + @Override + public void onDialogDismissed(int reason) { + // Check the reason and invoke OnClickListener(s) if necessary + if (reason == DISMISSED_REASON_POSITIVE) { + mPositiveButtonInfo.executor.execute(() -> { + mPositiveButtonInfo.listener.onClick(null, DialogInterface.BUTTON_POSITIVE); + }); + } else if (reason == DISMISSED_REASON_NEGATIVE) { + mNegativeButtonInfo.executor.execute(() -> { + mNegativeButtonInfo.listener.onClick(null, DialogInterface.BUTTON_NEGATIVE); + }); + } + } + }; + + private FingerprintDialog(Context context, Bundle bundle, + ButtonInfo positiveButtonInfo, ButtonInfo negativeButtonInfo) { + mBundle = bundle; + mPositiveButtonInfo = positiveButtonInfo; + mNegativeButtonInfo = negativeButtonInfo; + mFingerprintManager = context.getSystemService(FingerprintManager.class); + } + + /** + * This call warms up the fingerprint hardware, displays a system-provided dialog, + * and starts scanning for a fingerprint. It terminates when + * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when + * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, + * when {@link AuthenticationCallback#onAuthenticationFailed()} is called or when the user + * dismisses the system-provided dialog, at which point the crypto object becomes invalid. + * This operation can be canceled by using the provided cancel object. The application will + * receive authentication errors through {@link AuthenticationCallback}, and button events + * through the corresponding callback set in + * {@link Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}. + * It is safe to reuse the {@link FingerprintDialog} object, and calling + * {@link FingerprintDialog#authenticate(CancellationSignal, Executor, AuthenticationCallback)} + * while an existing authentication attempt is occurring will stop the previous client and + * start a new authentication. The interrupted client will receive a cancelled notification + * through {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}. + * + * @throws IllegalArgumentException if any of the arguments are null + * + * @param crypto object associated with the call + * @param cancel an object that can be used to cancel authentication + * @param executor an executor to handle callback events + * @param callback an object to receive authentication events + */ + @RequiresPermission(USE_FINGERPRINT) + public void authenticate(@NonNull FingerprintManager.CryptoObject crypto, + @NonNull CancellationSignal cancel, + @NonNull @CallbackExecutor Executor executor, + @NonNull FingerprintManager.AuthenticationCallback callback) { + mFingerprintManager.authenticate(crypto, cancel, mBundle, executor, mDialogReceiver, + callback); + } + + /** + * This call warms up the fingerprint hardware, displays a system-provided dialog, + * and starts scanning for a fingerprint. It terminates when + * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when + * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, + * when {@link AuthenticationCallback#onAuthenticationFailed()} is called or when the user + * dismisses the system-provided dialog. This operation can be canceled by using the provided + * cancel object. The application will receive authentication errors through + * {@link AuthenticationCallback}, and button events through the corresponding callback set in + * {@link Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}. + * It is safe to reuse the {@link FingerprintDialog} object, and calling + * {@link FingerprintDialog#authenticate(CancellationSignal, Executor, AuthenticationCallback)} + * while an existing authentication attempt is occurring will stop the previous client and + * start a new authentication. The interrupted client will receive a cancelled notification + * through {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}. + * + * @throws IllegalArgumentException if any of the arguments are null + * + * @param cancel an object that can be used to cancel authentication + * @param executor an executor to handle callback events + * @param callback an object to receive authentication events + */ + @RequiresPermission(USE_FINGERPRINT) + public void authenticate(@NonNull CancellationSignal cancel, + @NonNull @CallbackExecutor Executor executor, + @NonNull FingerprintManager.AuthenticationCallback callback) { + mFingerprintManager.authenticate(cancel, mBundle, executor, mDialogReceiver, callback); + } +} diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 987718a82c47..1a0b276776c3 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -16,6 +16,11 @@ package android.hardware.fingerprint; +import static android.Manifest.permission.INTERACT_ACROSS_USERS; +import static android.Manifest.permission.MANAGE_FINGERPRINT; +import static android.Manifest.permission.USE_FINGERPRINT; + +import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -23,6 +28,7 @@ import android.annotation.SystemService; import android.app.ActivityManager; import android.content.Context; import android.os.Binder; +import android.os.Bundle; import android.os.CancellationSignal; import android.os.CancellationSignal.OnCancelListener; import android.os.Handler; @@ -38,14 +44,11 @@ import android.util.Slog; import java.security.Signature; import java.util.List; +import java.util.concurrent.Executor; import javax.crypto.Cipher; import javax.crypto.Mac; -import static android.Manifest.permission.INTERACT_ACROSS_USERS; -import static android.Manifest.permission.MANAGE_FINGERPRINT; -import static android.Manifest.permission.USE_FINGERPRINT; - /** * A class that coordinates access to the fingerprint hardware. */ @@ -204,6 +207,7 @@ public class FingerprintManager { private CryptoObject mCryptoObject; private Fingerprint mRemovalFingerprint; private Handler mHandler; + private Executor mExecutor; private class OnEnrollCancelListener implements OnCancelListener { @Override @@ -505,7 +509,9 @@ public class FingerprintManager { } /** - * Per-user version + * Per-user version, see {@link FingerprintManager#authenticate(CryptoObject, + * CancellationSignal, int, AuthenticationCallback, Handler)} + * @param userId the user ID that the fingerprint hardware will authenticate for. * @hide */ @RequiresPermission(USE_FINGERPRINT) @@ -530,7 +536,7 @@ public class FingerprintManager { mCryptoObject = crypto; long sessionId = crypto != null ? crypto.getOpId() : 0; mService.authenticate(mToken, sessionId, userId, mServiceReceiver, flags, - mContext.getOpPackageName()); + mContext.getOpPackageName(), null /* bundle */, null /* receiver */); } catch (RemoteException e) { Log.w(TAG, "Remote exception while authenticating: ", e); if (callback != null) { @@ -543,6 +549,112 @@ public class FingerprintManager { } /** + * Per-user version, see {@link FingerprintManager#authenticate(CryptoObject, + * CancellationSignal, Bundle, Executor, IFingerprintDialogReceiver, AuthenticationCallback)} + * @param userId the user ID that the fingerprint hardware will authenticate for. + * @hide + */ + public void authenticate(int userId, + @Nullable CryptoObject crypto, + @NonNull CancellationSignal cancel, + @NonNull Bundle bundle, + @NonNull @CallbackExecutor Executor executor, + @NonNull IFingerprintDialogReceiver receiver, + @NonNull AuthenticationCallback callback) { + mCryptoObject = crypto; + if (cancel.isCanceled()) { + Log.w(TAG, "authentication already canceled"); + return; + } else { + cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto)); + } + + if (mService != null) { + try { + mExecutor = executor; + mAuthenticationCallback = callback; + final long sessionId = crypto != null ? crypto.getOpId() : 0; + mService.authenticate(mToken, sessionId, userId, mServiceReceiver, + 0 /* flags */, mContext.getOpPackageName(), bundle, receiver); + } catch (RemoteException e) { + Log.w(TAG, "Remote exception while authenticating", e); + mExecutor.execute(() -> { + callback.onAuthenticationError(FINGERPRINT_ERROR_HW_UNAVAILABLE, + getErrorString(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */)); + }); + } + } + } + + /** + * Private method, see {@link FingerprintDialog#authenticate(CancellationSignal, Executor, + * AuthenticationCallback)} + * @param cancel + * @param executor + * @param callback + * @hide + */ + public void authenticate( + @NonNull CancellationSignal cancel, + @NonNull Bundle bundle, + @NonNull @CallbackExecutor Executor executor, + @NonNull IFingerprintDialogReceiver receiver, + @NonNull AuthenticationCallback callback) { + if (cancel == null) { + throw new IllegalArgumentException("Must supply a cancellation signal"); + } + if (bundle == null) { + throw new IllegalArgumentException("Must supply a bundle"); + } + if (executor == null) { + throw new IllegalArgumentException("Must supply an executor"); + } + if (receiver == null) { + throw new IllegalArgumentException("Must supply a receiver"); + } + if (callback == null) { + throw new IllegalArgumentException("Must supply a calback"); + } + authenticate(UserHandle.myUserId(), null, cancel, bundle, executor, receiver, callback); + } + + /** + * Private method, see {@link FingerprintDialog#authenticate(CryptoObject, CancellationSignal, + * Executor, AuthenticationCallback)} + * @param crypto + * @param cancel + * @param executor + * @param callback + * @hide + */ + public void authenticate(@NonNull CryptoObject crypto, + @NonNull CancellationSignal cancel, + @NonNull Bundle bundle, + @NonNull @CallbackExecutor Executor executor, + @NonNull IFingerprintDialogReceiver receiver, + @NonNull AuthenticationCallback callback) { + if (crypto == null) { + throw new IllegalArgumentException("Must supply a crypto object"); + } + if (cancel == null) { + throw new IllegalArgumentException("Must supply a cancellation signal"); + } + if (bundle == null) { + throw new IllegalArgumentException("Must supply a bundle"); + } + if (executor == null) { + throw new IllegalArgumentException("Must supply an executor"); + } + if (receiver == null) { + throw new IllegalArgumentException("Must supply a receiver"); + } + if (callback == null) { + throw new IllegalArgumentException("Must supply a calback"); + } + authenticate(UserHandle.myUserId(), crypto, cancel, bundle, executor, receiver, callback); + } + + /** * Request fingerprint enrollment. This call warms up the fingerprint hardware * and starts scanning for fingerprints. Progress will be indicated by callbacks to the * {@link EnrollmentCallback} object. It terminates when @@ -929,63 +1041,63 @@ public class FingerprintManager { } } - private void sendErrorResult(long deviceId, int errMsgId, int vendorCode) { - // emulate HAL 2.1 behavior and send real errMsgId - final int clientErrMsgId = errMsgId == FINGERPRINT_ERROR_VENDOR - ? (vendorCode + FINGERPRINT_ERROR_VENDOR_BASE) : errMsgId; - if (mEnrollmentCallback != null) { - mEnrollmentCallback.onEnrollmentError(clientErrMsgId, - getErrorString(errMsgId, vendorCode)); - } else if (mAuthenticationCallback != null) { - mAuthenticationCallback.onAuthenticationError(clientErrMsgId, - getErrorString(errMsgId, vendorCode)); - } else if (mRemovalCallback != null) { - mRemovalCallback.onRemovalError(mRemovalFingerprint, clientErrMsgId, - getErrorString(errMsgId, vendorCode)); - } else if (mEnumerateCallback != null) { - mEnumerateCallback.onEnumerateError(clientErrMsgId, - getErrorString(errMsgId, vendorCode)); - } - } - private void sendEnrollResult(Fingerprint fp, int remaining) { if (mEnrollmentCallback != null) { mEnrollmentCallback.onEnrollmentProgress(remaining); } } + }; - private void sendAuthenticatedSucceeded(Fingerprint fp, int userId) { - if (mAuthenticationCallback != null) { - final AuthenticationResult result = - new AuthenticationResult(mCryptoObject, fp, userId); - mAuthenticationCallback.onAuthenticationSucceeded(result); - } + private void sendAuthenticatedSucceeded(Fingerprint fp, int userId) { + if (mAuthenticationCallback != null) { + final AuthenticationResult result = + new AuthenticationResult(mCryptoObject, fp, userId); + mAuthenticationCallback.onAuthenticationSucceeded(result); } + } - private void sendAuthenticatedFailed() { - if (mAuthenticationCallback != null) { - mAuthenticationCallback.onAuthenticationFailed(); - } + private void sendAuthenticatedFailed() { + if (mAuthenticationCallback != null) { + mAuthenticationCallback.onAuthenticationFailed(); } + } - private void sendAcquiredResult(long deviceId, int acquireInfo, int vendorCode) { - if (mAuthenticationCallback != null) { - mAuthenticationCallback.onAuthenticationAcquired(acquireInfo); - } - final String msg = getAcquiredString(acquireInfo, vendorCode); - if (msg == null) { - return; - } - // emulate HAL 2.1 behavior and send real acquiredInfo - final int clientInfo = acquireInfo == FINGERPRINT_ACQUIRED_VENDOR - ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquireInfo; - if (mEnrollmentCallback != null) { - mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg); - } else if (mAuthenticationCallback != null) { - mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg); - } + private void sendAcquiredResult(long deviceId, int acquireInfo, int vendorCode) { + if (mAuthenticationCallback != null) { + mAuthenticationCallback.onAuthenticationAcquired(acquireInfo); } - }; + final String msg = getAcquiredString(acquireInfo, vendorCode); + if (msg == null) { + return; + } + // emulate HAL 2.1 behavior and send real acquiredInfo + final int clientInfo = acquireInfo == FINGERPRINT_ACQUIRED_VENDOR + ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquireInfo; + if (mEnrollmentCallback != null) { + mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg); + } else if (mAuthenticationCallback != null) { + mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg); + } + } + + private void sendErrorResult(long deviceId, int errMsgId, int vendorCode) { + // emulate HAL 2.1 behavior and send real errMsgId + final int clientErrMsgId = errMsgId == FINGERPRINT_ERROR_VENDOR + ? (vendorCode + FINGERPRINT_ERROR_VENDOR_BASE) : errMsgId; + if (mEnrollmentCallback != null) { + mEnrollmentCallback.onEnrollmentError(clientErrMsgId, + getErrorString(errMsgId, vendorCode)); + } else if (mAuthenticationCallback != null) { + mAuthenticationCallback.onAuthenticationError(clientErrMsgId, + getErrorString(errMsgId, vendorCode)); + } else if (mRemovalCallback != null) { + mRemovalCallback.onRemovalError(mRemovalFingerprint, clientErrMsgId, + getErrorString(errMsgId, vendorCode)); + } else if (mEnumerateCallback != null) { + mEnumerateCallback.onEnumerateError(clientErrMsgId, + getErrorString(errMsgId, vendorCode)); + } + } /** * @hide @@ -1023,7 +1135,10 @@ public class FingerprintManager { } } - private String getErrorString(int errMsg, int vendorCode) { + /** + * @hide + */ + public String getErrorString(int errMsg, int vendorCode) { switch (errMsg) { case FINGERPRINT_ERROR_UNABLE_TO_PROCESS: return mContext.getString( @@ -1043,6 +1158,9 @@ public class FingerprintManager { case FINGERPRINT_ERROR_LOCKOUT_PERMANENT: return mContext.getString( com.android.internal.R.string.fingerprint_error_lockout_permanent); + case FINGERPRINT_ERROR_USER_CANCELED: + return mContext.getString( + com.android.internal.R.string.fingerprint_error_user_canceled); case FINGERPRINT_ERROR_VENDOR: { String[] msgArray = mContext.getResources().getStringArray( com.android.internal.R.array.fingerprint_error_vendor); @@ -1055,7 +1173,10 @@ public class FingerprintManager { return null; } - private String getAcquiredString(int acquireInfo, int vendorCode) { + /** + * @hide + */ + public String getAcquiredString(int acquireInfo, int vendorCode) { switch (acquireInfo) { case FINGERPRINT_ACQUIRED_GOOD: return null; @@ -1096,22 +1217,47 @@ public class FingerprintManager { @Override // binder call public void onAcquired(long deviceId, int acquireInfo, int vendorCode) { - mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode, deviceId).sendToTarget(); + if (mExecutor != null) { + mExecutor.execute(() -> { + sendAcquiredResult(deviceId, acquireInfo, vendorCode); + }); + } else { + mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode, + deviceId).sendToTarget(); + } } @Override // binder call public void onAuthenticationSucceeded(long deviceId, Fingerprint fp, int userId) { - mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, 0, fp).sendToTarget(); + if (mExecutor != null) { + mExecutor.execute(() -> { + sendAuthenticatedSucceeded(fp, userId); + }); + } else { + mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, 0, fp).sendToTarget(); + } } @Override // binder call public void onAuthenticationFailed(long deviceId) { - mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget(); + if (mExecutor != null) { + mExecutor.execute(() -> { + sendAuthenticatedFailed(); + }); + } else { + mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget(); + } } @Override // binder call public void onError(long deviceId, int error, int vendorCode) { - mHandler.obtainMessage(MSG_ERROR, error, vendorCode, deviceId).sendToTarget(); + if (mExecutor != null) { + mExecutor.execute(() -> { + sendErrorResult(deviceId, error, vendorCode); + }); + } else { + mHandler.obtainMessage(MSG_ERROR, error, vendorCode, deviceId).sendToTarget(); + } } @Override // binder call diff --git a/core/java/android/hardware/fingerprint/IFingerprintDialogReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintDialogReceiver.aidl new file mode 100644 index 000000000000..13e79741e543 --- /dev/null +++ b/core/java/android/hardware/fingerprint/IFingerprintDialogReceiver.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware.fingerprint; + +import android.hardware.fingerprint.Fingerprint; +import android.os.Bundle; +import android.os.UserHandle; + +/** + * Communication channel from the FingerprintDialog (SysUI) back to AuthenticationClient. + * @hide + */ +oneway interface IFingerprintDialogReceiver { + void onDialogDismissed(int reason); +} diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index 4879d54768fb..f1502e489c11 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -17,6 +17,7 @@ package android.hardware.fingerprint; import android.os.Bundle; import android.hardware.fingerprint.IFingerprintClientActiveCallback; +import android.hardware.fingerprint.IFingerprintDialogReceiver; import android.hardware.fingerprint.IFingerprintServiceReceiver; import android.hardware.fingerprint.IFingerprintServiceLockoutResetCallback; import android.hardware.fingerprint.Fingerprint; @@ -29,7 +30,8 @@ import java.util.List; interface IFingerprintService { // Authenticate the given sessionId with a fingerprint void authenticate(IBinder token, long sessionId, int userId, - IFingerprintServiceReceiver receiver, int flags, String opPackageName); + IFingerprintServiceReceiver receiver, int flags, String opPackageName, + in Bundle bundle, IFingerprintDialogReceiver dialogReceiver); // Cancel authentication for the given sessionId void cancelAuthentication(IBinder token, String opPackageName); diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index e097362a3fe8..f5af80a29ed1 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -18,6 +18,7 @@ package com.android.internal.statusbar; import android.content.ComponentName; import android.graphics.Rect; +import android.hardware.fingerprint.IFingerprintDialogReceiver; import android.os.Bundle; import android.service.notification.StatusBarNotification; @@ -130,4 +131,15 @@ oneway interface IStatusBar void handleSystemKey(in int key); void showShutdownUi(boolean isReboot, String reason); + + // Used to show the dialog when FingerprintService starts authentication + void showFingerprintDialog(in Bundle bundle, IFingerprintDialogReceiver receiver); + // Used to hide the dialog when a finger is authenticated + void onFingerprintAuthenticated(); + // Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc + void onFingerprintHelp(String message); + // Used to set a message - the dialog will dismiss after a certain amount of time + void onFingerprintError(String error); + // Used to hide the fingerprint dialog when the authenticationclient is stopped + void hideFingerprintDialog(); } diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 03603e401110..cb0b53c495dc 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -20,6 +20,7 @@ import android.content.ComponentName; import android.graphics.Rect; import android.os.Bundle; import android.service.notification.StatusBarNotification; +import android.hardware.fingerprint.IFingerprintDialogReceiver; import com.android.internal.statusbar.IStatusBar; import com.android.internal.statusbar.StatusBarIcon; @@ -79,4 +80,15 @@ interface IStatusBarService void remTile(in ComponentName tile); void clickTile(in ComponentName tile); void handleSystemKey(in int key); + + // Used to show the dialog when FingerprintService starts authentication + void showFingerprintDialog(in Bundle bundle, IFingerprintDialogReceiver receiver); + // Used to hide the dialog when a finger is authenticated + void onFingerprintAuthenticated(); + // Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc + void onFingerprintHelp(String message); + // Used to set a message - the dialog will dismiss after a certain amount of time + void onFingerprintError(String error); + // Used to hide the fingerprint dialog when the authenticationclient is stopped + void hideFingerprintDialog(); } diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 170ba4264e04..2be523772334 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1321,6 +1321,9 @@ <string-array name="fingerprint_acquired_vendor"> </string-array> + <!-- Message shown by the fingerprint dialog when fingerprint is not recognized --> + <string name="fingerprint_not_recognized">Not recognized</string> + <!-- Error message shown when the fingerprint hardware can't be accessed --> <string name="fingerprint_error_hw_not_available">Fingerprint hardware not available.</string> <!-- Error message shown when the fingerprint hardware has run out of room for storing fingerprints --> @@ -1329,6 +1332,8 @@ <string name="fingerprint_error_timeout">Fingerprint time out reached. Try again.</string> <!-- Generic error message shown when the fingerprint operation (e.g. enrollment or authentication) is canceled. Generally not shown to the user--> <string name="fingerprint_error_canceled">Fingerprint operation canceled.</string> + <!-- Generic error message shown when the fingerprint authentication operation is canceled due to user input. Generally not shown to the user --> + <string name="fingerprint_error_user_canceled">Fingerprint operation canceled by user.</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 --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 4ef0a6c93d9e..1711ec97b6e9 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2341,9 +2341,11 @@ <java-symbol type="string" name="fingerprint_acquired_too_fast" /> <java-symbol type="array" name="fingerprint_acquired_vendor" /> <java-symbol type="string" name="fingerprint_error_canceled" /> + <java-symbol type="string" name="fingerprint_error_user_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" /> + <java-symbol type="string" name="fingerprint_not_recognized" /> <!-- Fingerprint config --> <java-symbol type="integer" name="config_fingerprintMaxTemplatesPerUser"/> diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk index 73fcdd7aa90d..251ae2da1215 100644 --- a/packages/SystemUI/Android.mk +++ b/packages/SystemUI/Android.mk @@ -27,7 +27,12 @@ LOCAL_USE_AAPT2 := true LOCAL_MODULE_TAGS := optional -LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src) +RELATIVE_FINGERPRINT_PATH := ../../core/java/android/hardware/fingerprint + +LOCAL_SRC_FILES := \ + $(call all-java-files-under, src) \ + $(call all-Iaidl-files-under, src) \ + $(call all-Iaidl-files-under, $(RELATIVE_FINGERPRINT_PATH)) LOCAL_STATIC_ANDROID_LIBRARIES := \ SystemUIPluginLib \ diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index b33f857a80bf..676847088f31 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -350,6 +350,7 @@ <item>com.android.systemui.globalactions.GlobalActionsComponent</item> <item>com.android.systemui.RoundedCorners</item> <item>com.android.systemui.EmulatedDisplayCutout</item> + <item>com.android.systemui.fingerprint.FingerprintDialogImpl</item> </string-array> <!-- SystemUI vender service, used in config_systemUIServiceComponents. --> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 19afcf5e33a8..9e4b4055a289 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -1257,7 +1257,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { mFingerprintCancelSignal.cancel(); } mFingerprintCancelSignal = new CancellationSignal(); - mFpm.authenticate(null, mFingerprintCancelSignal, 0, mAuthenticationCallback, null, userId); + mFpm.authenticate(null, mFingerprintCancelSignal, 0, mAuthenticationCallback, null, + userId); setFingerprintRunningState(FINGERPRINT_STATE_RUNNING); } } diff --git a/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogImpl.java b/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogImpl.java new file mode 100644 index 000000000000..8b6f95ca4e14 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogImpl.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.fingerprint; + +import android.content.pm.PackageManager; +import android.hardware.fingerprint.IFingerprintDialogReceiver; +import android.os.Bundle; +import android.util.Log; + +import com.android.systemui.SystemUI; +import com.android.systemui.statusbar.CommandQueue; + +public class FingerprintDialogImpl extends SystemUI implements CommandQueue.Callbacks { + private static final String TAG = "FingerprintDialogImpl"; + private static final boolean DEBUG = false; + + @Override + public void start() { + if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { + return; + } + getComponent(CommandQueue.class).addCallbacks(this); + } + + @Override + public void showFingerprintDialog(Bundle bundle, IFingerprintDialogReceiver receiver) { + if (DEBUG) Log.d(TAG, "show fingerprint dialog"); + } + + @Override + public void onFingerprintAuthenticated() { + if (DEBUG) Log.d(TAG, "onFingerprintAuthenticated"); + } + + @Override + public void onFingerprintHelp(String message) { + if (DEBUG) Log.d(TAG, "onFingerprintHelp: " + message); + } + + @Override + public void onFingerprintError(String error) { + if (DEBUG) Log.d(TAG, "onFingerprintError: " + error); + } + + @Override + public void hideFingerprintDialog() { + if (DEBUG) Log.d(TAG, "hideFingerprintDialog"); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index c6abcf272722..00bc62e9f1d6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar; import android.content.ComponentName; import android.graphics.Rect; +import android.hardware.fingerprint.IFingerprintDialogReceiver; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -83,6 +84,11 @@ public class CommandQueue extends IStatusBar.Stub { private static final int MSG_SHOW_SHUTDOWN_UI = 36 << MSG_SHIFT; private static final int MSG_SET_TOP_APP_HIDES_STATUS_BAR = 37 << MSG_SHIFT; private static final int MSG_ROTATION_PROPOSAL = 38 << MSG_SHIFT; + private static final int MSG_FINGERPRINT_SHOW = 39 << MSG_SHIFT; + private static final int MSG_FINGERPRINT_AUTHENTICATED = 40 << MSG_SHIFT; + private static final int MSG_FINGERPRINT_HELP = 41 << MSG_SHIFT; + private static final int MSG_FINGERPRINT_ERROR = 42 << MSG_SHIFT; + private static final int MSG_FINGERPRINT_HIDE = 43 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; @@ -145,6 +151,12 @@ public class CommandQueue extends IStatusBar.Stub { default void handleShowShutdownUi(boolean isReboot, String reason) { } default void onRotationProposal(int rotation, boolean isValid) { } + + default void showFingerprintDialog(Bundle bundle, IFingerprintDialogReceiver receiver) { } + default void onFingerprintAuthenticated() { } + default void onFingerprintHelp(String message) { } + default void onFingerprintError(String error) { } + default void hideFingerprintDialog() { } } @VisibleForTesting @@ -470,6 +482,45 @@ public class CommandQueue extends IStatusBar.Stub { } } + @Override + public void showFingerprintDialog(Bundle bundle, IFingerprintDialogReceiver receiver) { + synchronized (mLock) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = bundle; + args.arg2 = receiver; + mHandler.obtainMessage(MSG_FINGERPRINT_SHOW, args) + .sendToTarget(); + } + } + + @Override + public void onFingerprintAuthenticated() { + synchronized (mLock) { + mHandler.obtainMessage(MSG_FINGERPRINT_AUTHENTICATED).sendToTarget(); + } + } + + @Override + public void onFingerprintHelp(String message) { + synchronized (mLock) { + mHandler.obtainMessage(MSG_FINGERPRINT_HELP, message).sendToTarget(); + } + } + + @Override + public void onFingerprintError(String error) { + synchronized (mLock) { + mHandler.obtainMessage(MSG_FINGERPRINT_ERROR, error).sendToTarget(); + } + } + + @Override + public void hideFingerprintDialog() { + synchronized (mLock) { + mHandler.obtainMessage(MSG_FINGERPRINT_HIDE).sendToTarget(); + } + } + private final class H extends Handler { private H(Looper l) { super(l); @@ -671,6 +722,35 @@ public class CommandQueue extends IStatusBar.Stub { mCallbacks.get(i).onRotationProposal(msg.arg1, msg.arg2 != 0); } break; + case MSG_FINGERPRINT_SHOW: + mHandler.removeMessages(MSG_FINGERPRINT_ERROR); + mHandler.removeMessages(MSG_FINGERPRINT_HELP); + mHandler.removeMessages(MSG_FINGERPRINT_AUTHENTICATED); + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).showFingerprintDialog( + (Bundle)((SomeArgs)msg.obj).arg1, + (IFingerprintDialogReceiver)((SomeArgs)msg.obj).arg2); + } + break; + case MSG_FINGERPRINT_AUTHENTICATED: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).onFingerprintAuthenticated(); + } + break; + case MSG_FINGERPRINT_HELP: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).onFingerprintHelp((String) msg.obj); + } + break; + case MSG_FINGERPRINT_ERROR: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).onFingerprintError((String) msg.obj); + } + break; + case MSG_FINGERPRINT_HIDE: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).hideFingerprintDialog(); + } } } } diff --git a/services/core/java/com/android/server/fingerprint/AuthenticationClient.java b/services/core/java/com/android/server/fingerprint/AuthenticationClient.java index 370e569f2598..d30b13c3b564 100644 --- a/services/core/java/com/android/server/fingerprint/AuthenticationClient.java +++ b/services/core/java/com/android/server/fingerprint/AuthenticationClient.java @@ -16,18 +16,22 @@ package com.android.server.fingerprint; -import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; - import android.content.Context; +import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.fingerprint.Fingerprint; +import android.hardware.fingerprint.FingerprintDialog; import android.hardware.fingerprint.FingerprintManager; +import android.hardware.fingerprint.IFingerprintDialogReceiver; import android.hardware.fingerprint.IFingerprintServiceReceiver; +import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.statusbar.IStatusBarService; + /** * A class to keep track of the authentication state for a given client. */ @@ -41,11 +45,99 @@ public abstract class AuthenticationClient extends ClientMonitor { public static final int LOCKOUT_TIMED = 1; public static final int LOCKOUT_PERMANENT = 2; + // Callback mechanism received from the client + // (FingerprintDialog -> FingerprintManager -> FingerprintService -> AuthenticationClient) + private IFingerprintDialogReceiver mDialogReceiverFromClient; + private Bundle mBundle; + private IStatusBarService mStatusBarService; + private boolean mInLockout; + private final FingerprintManager mFingerprintManager; + protected boolean mDialogDismissed; + + // Receives events from SystemUI + protected IFingerprintDialogReceiver mDialogReceiver = new IFingerprintDialogReceiver.Stub() { + @Override // binder call + public void onDialogDismissed(int reason) { + if (mBundle != null && mDialogReceiverFromClient != null) { + try { + mDialogReceiverFromClient.onDialogDismissed(reason); + if (reason == FingerprintDialog.DISMISSED_REASON_USER_CANCEL) { + onError(FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED, + 0 /* vendorCode */); + } + mDialogDismissed = true; + } catch (RemoteException e) { + Slog.e(TAG, "Unable to notify dialog dismissed", e); + } + stop(true /* initiatedByClient */); + } + } + }; + public AuthenticationClient(Context context, long halDeviceId, IBinder token, IFingerprintServiceReceiver receiver, int targetUserId, int groupId, long opId, - boolean restricted, String owner) { + boolean restricted, String owner, Bundle bundle, + IFingerprintDialogReceiver dialogReceiver, IStatusBarService statusBarService) { super(context, halDeviceId, token, receiver, targetUserId, groupId, restricted, owner); mOpId = opId; + mBundle = bundle; + mDialogReceiverFromClient = dialogReceiver; + mStatusBarService = statusBarService; + mFingerprintManager = (FingerprintManager) getContext() + .getSystemService(Context.FINGERPRINT_SERVICE); + } + + @Override + public void binderDied() { + super.binderDied(); + // When the binder dies, we should stop the client. This probably belongs in + // ClientMonitor's binderDied(), but testing all the cases would be tricky. + // AuthenticationClient is the most user-visible case. + stop(false /* initiatedByClient */); + } + + @Override + public boolean onAcquired(int acquiredInfo, int vendorCode) { + // If the dialog is showing, the client doesn't need to receive onAcquired messages. + if (mBundle != null) { + try { + if (acquiredInfo != FingerprintManager.FINGERPRINT_ACQUIRED_GOOD) { + mStatusBarService.onFingerprintHelp( + mFingerprintManager.getAcquiredString(acquiredInfo, vendorCode)); + } + return false; // acquisition continues + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when sending acquired message", e); + return true; // client failed + } finally { + // Good scans will keep the device awake + if (acquiredInfo == FingerprintManager.FINGERPRINT_ACQUIRED_GOOD) { + notifyUserActivity(); + } + } + } else { + return super.onAcquired(acquiredInfo, vendorCode); + } + } + + @Override + public boolean onError(int error, int vendorCode) { + if (mDialogDismissed) { + // If user cancels authentication, the application has already received the + // FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED message from onDialogDismissed() + // and stopped the fingerprint hardware, so there is no need to send a + // FingerprintManager.FINGERPRINT_ERROR_CANCELED message. + return true; + } + if (mBundle != null) { + try { + mStatusBarService.onFingerprintError( + mFingerprintManager.getErrorString(error, vendorCode)); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when sending error", e); + } + } + return super.onError(error, vendorCode); } @Override @@ -53,6 +145,20 @@ public abstract class AuthenticationClient extends ClientMonitor { boolean result = false; boolean authenticated = fingerId != 0; + // If the fingerprint dialog is showing, notify authentication succeeded + if (mBundle != null) { + try { + if (authenticated) { + mStatusBarService.onFingerprintAuthenticated(); + } else { + mStatusBarService.onFingerprintHelp(getContext().getResources().getString( + com.android.internal.R.string.fingerprint_not_recognized)); + } + } catch (RemoteException e) { + Slog.e(TAG, "Failed to notify Authenticated:", e); + } + } + IFingerprintServiceReceiver receiver = getReceiver(); if (receiver != null) { try { @@ -85,13 +191,24 @@ public abstract class AuthenticationClient extends ClientMonitor { int lockoutMode = handleFailedAttempt(); if (lockoutMode != LOCKOUT_NONE) { try { + mInLockout = true; 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; + + // TODO: if the dialog is showing, this error should be delayed. On a similar + // note, AuthenticationClient should override onError and delay all other errors + // as well, if the dialog is showing receiver.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */); + + // Send the lockout message to the system dialog + if (mBundle != null) { + mStatusBarService.onFingerprintError( + mFingerprintManager.getErrorString(errorCode, 0 /* vendorCode */)); + } } catch (RemoteException e) { Slog.w(TAG, "Failed to notify lockout:", e); } @@ -126,6 +243,15 @@ public abstract class AuthenticationClient extends ClientMonitor { return result; } if (DEBUG) Slog.w(TAG, "client " + getOwnerString() + " is authenticating..."); + + // If authenticating with system dialog, show the dialog + if (mBundle != null) { + try { + mStatusBarService.showFingerprintDialog(mBundle, mDialogReceiver); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to show fingerprint dialog", e); + } + } } catch (RemoteException e) { Slog.e(TAG, "startAuthentication failed", e); return ERROR_ESRCH; @@ -139,6 +265,7 @@ public abstract class AuthenticationClient extends ClientMonitor { Slog.w(TAG, "stopAuthentication: already cancelled!"); return 0; } + IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { Slog.w(TAG, "stopAuthentication: no fingerprint HAL!"); @@ -154,6 +281,18 @@ public abstract class AuthenticationClient extends ClientMonitor { } catch (RemoteException e) { Slog.e(TAG, "stopAuthentication failed", e); return ERROR_ESRCH; + } finally { + // If the user already cancelled authentication (via some interaction with the + // dialog, we do not need to hide it since it's already hidden. + // If the device is in lockout, don't hide the dialog - it will automatically hide + // after FingerprintDialog.HIDE_DIALOG_DELAY + if (mBundle != null && !mDialogDismissed && !mInLockout) { + try { + mStatusBarService.hideFingerprintDialog(); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to hide fingerprint dialog", e); + } + } } mAlreadyCancelled = true; return 0; // success diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java index d0d951b85b40..3e1958d9edfc 100644 --- a/services/core/java/com/android/server/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java @@ -40,6 +40,7 @@ import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprintClient import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.IFingerprintClientActiveCallback; +import android.hardware.fingerprint.IFingerprintDialogReceiver; import android.hardware.fingerprint.IFingerprintService; import android.hardware.fingerprint.IFingerprintServiceLockoutResetCallback; import android.hardware.fingerprint.IFingerprintServiceReceiver; @@ -55,6 +56,7 @@ import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.RemoteException; import android.os.SELinux; +import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; @@ -66,6 +68,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; +import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.DumpUtils; import com.android.server.SystemServerInitThreadPool; import com.android.server.SystemService; @@ -131,6 +134,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death private SparseIntArray mFailedAttempts; @GuardedBy("this") private IBiometricsFingerprint mDaemon; + private IStatusBarService mStatusBarService; private final PowerManager mPowerManager; private final AlarmManager mAlarmManager; private final UserManager mUserManager; @@ -222,6 +226,8 @@ public class FingerprintService extends SystemService implements IHwBinder.Death mUserManager = UserManager.get(mContext); mTimedLockoutCleared = new SparseBooleanArray(); mFailedAttempts = new SparseIntArray(); + mStatusBarService = IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)); } @Override @@ -808,13 +814,14 @@ public class FingerprintService extends SystemService implements IHwBinder.Death private void startAuthentication(IBinder token, long opId, int callingUserId, int groupId, IFingerprintServiceReceiver receiver, int flags, boolean restricted, - String opPackageName) { + String opPackageName, Bundle bundle, IFingerprintDialogReceiver dialogReceiver) { updateActiveGroup(groupId, opPackageName); if (DEBUG) Slog.v(TAG, "startAuthentication(" + opPackageName + ")"); AuthenticationClient client = new AuthenticationClient(getContext(), mHalDeviceId, token, - receiver, mCurrentUserId, groupId, opId, restricted, opPackageName) { + receiver, mCurrentUserId, groupId, opId, restricted, opPackageName, bundle, + dialogReceiver, mStatusBarService) { @Override public int handleFailedAttempt() { final int currentUser = ActivityManager.getCurrentUser(); @@ -1037,7 +1044,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death final IFingerprintServiceReceiver receiver, final int flags, final String opPackageName) { checkPermission(MANAGE_FINGERPRINT); - final int limit = mContext.getResources().getInteger( + final int limit = mContext.getResources().getInteger( com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser); final int enrolled = FingerprintService.this.getEnrolledFingerprints(userId).size(); @@ -1085,7 +1092,8 @@ public class FingerprintService extends SystemService implements IHwBinder.Death @Override // Binder call public void authenticate(final IBinder token, final long opId, final int groupId, final IFingerprintServiceReceiver receiver, final int flags, - final String opPackageName) { + final String opPackageName, final Bundle bundle, + final IFingerprintDialogReceiver dialogReceiver) { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); final int callingUserId = UserHandle.getCallingUserId(); @@ -1113,7 +1121,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death mPerformanceStats = stats; startAuthentication(token, opId, callingUserId, groupId, receiver, - flags, restricted, opPackageName); + flags, restricted, opPackageName, bundle, dialogReceiver); } }); } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index c58c208dea81..d67f2d7922c0 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -23,6 +23,7 @@ import android.app.StatusBarManager; import android.content.ComponentName; import android.content.Context; import android.graphics.Rect; +import android.hardware.fingerprint.IFingerprintDialogReceiver; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -514,6 +515,56 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } @Override + public void showFingerprintDialog(Bundle bundle, IFingerprintDialogReceiver receiver) { + if (mBar != null) { + try { + mBar.showFingerprintDialog(bundle, receiver); + } catch (RemoteException ex) { + } + } + } + + @Override + public void onFingerprintAuthenticated() { + if (mBar != null) { + try { + mBar.onFingerprintAuthenticated(); + } catch (RemoteException ex) { + } + } + } + + @Override + public void onFingerprintHelp(String message) { + if (mBar != null) { + try { + mBar.onFingerprintHelp(message); + } catch (RemoteException ex) { + } + } + } + + @Override + public void onFingerprintError(String error) { + if (mBar != null) { + try { + mBar.onFingerprintError(error); + } catch (RemoteException ex) { + } + } + } + + @Override + public void hideFingerprintDialog() { + if (mBar != null) { + try { + mBar.hideFingerprintDialog(); + } catch (RemoteException ex) { + } + } + } + + @Override public void disable(int what, IBinder token, String pkg) { disableForUser(what, token, pkg, mCurrentUserId); } |