diff options
6 files changed, 138 insertions, 6 deletions
diff --git a/api/current.txt b/api/current.txt index bb6dbeba2e7a..602ec415d99e 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5101,7 +5101,7 @@ package android.app { } public class KeyguardManager { - method public android.content.Intent createConfirmDeviceCredentialIntent(CharSequence, CharSequence); + method @Deprecated public android.content.Intent createConfirmDeviceCredentialIntent(CharSequence, CharSequence); method @Deprecated @RequiresPermission(android.Manifest.permission.DISABLE_KEYGUARD) public void exitKeyguardSecurely(android.app.KeyguardManager.OnKeyguardExitResult); method @Deprecated public boolean inKeyguardRestrictedInputMode(); method public boolean isDeviceLocked(); @@ -16498,6 +16498,7 @@ package android.hardware.biometrics { ctor public BiometricPrompt.Builder(android.content.Context); method public android.hardware.biometrics.BiometricPrompt build(); method public android.hardware.biometrics.BiometricPrompt.Builder setDescription(@NonNull CharSequence); + method public android.hardware.biometrics.BiometricPrompt.Builder setEnableFallback(boolean); method public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(@NonNull CharSequence, @NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener); method public android.hardware.biometrics.BiometricPrompt.Builder setRequireConfirmation(boolean); method public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(@NonNull CharSequence); diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 75c90542ebce..f522d71afd08 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -29,6 +29,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.hardware.biometrics.BiometricPrompt; import android.os.Binder; import android.os.Build; import android.os.Handler; @@ -87,6 +88,12 @@ public class KeyguardManager { "android.app.action.CONFIRM_FRP_CREDENTIAL"; /** + * @hide + */ + public static final String EXTRA_BIOMETRIC_PROMPT_BUNDLE = + "android.app.extra.BIOMETRIC_PROMPT_BUNDLE"; + + /** * A CharSequence dialog title to show to the user when used with a * {@link #ACTION_CONFIRM_DEVICE_CREDENTIAL}. * @hide @@ -118,15 +125,19 @@ public class KeyguardManager { public static final int RESULT_ALTERNATE = 1; /** - * Get an intent to prompt the user to confirm credentials (pin, pattern or password) - * for the current user of the device. The caller is expected to launch this activity using - * {@link android.app.Activity#startActivityForResult(Intent, int)} and check for + * @deprecated see {@link BiometricPrompt.Builder#setEnableFallback(boolean)} + * + * Get an intent to prompt the user to confirm credentials (pin, pattern, password or biometrics + * if enrolled) for the current user of the device. The caller is expected to launch this + * activity using {@link android.app.Activity#startActivityForResult(Intent, int)} and check for * {@link android.app.Activity#RESULT_OK} if the user successfully completes the challenge. * * @return the intent for launching the activity or null if no password is required. **/ + @Deprecated @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) - public Intent createConfirmDeviceCredentialIntent(CharSequence title, CharSequence description) { + public Intent createConfirmDeviceCredentialIntent(CharSequence title, + CharSequence description) { if (!isDeviceSecure()) return null; Intent intent = new Intent(ACTION_CONFIRM_DEVICE_CREDENTIAL); intent.putExtra(EXTRA_TITLE, title); diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 8aac1bfa1f8d..5afe1a9309f4 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -171,5 +171,39 @@ public class BiometricManager { Slog.w(TAG, "resetTimeout(): Service not connected"); } } + + /** + * TODO(b/123378871): Remove when moved. + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void onConfirmDeviceCredentialSuccess() { + if (mService != null) { + try { + mService.onConfirmDeviceCredentialSuccess(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "onConfirmDeviceCredentialSuccess(): Service not connected"); + } + } + + /** + * TODO(b/123378871): Remove when moved. + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void onConfirmDeviceCredentialError(int error, String message) { + if (mService != null) { + try { + mService.onConfirmDeviceCredentialError(error, message); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "onConfirmDeviceCredentialError(): Service not connected"); + } + } } diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index c69b68e4360a..ec62abaab6a2 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -77,6 +77,10 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * @hide */ public static final String KEY_REQUIRE_CONFIRMATION = "require_confirmation"; + /** + * @hide + */ + public static final String KEY_ENABLE_FALLBACK = "enable_fallback"; /** * Error/help message will show for this amount of time. @@ -242,6 +246,18 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** + * The user will first be prompted to authenticate with biometrics, but also given the + * option to authenticate with their device PIN, pattern, or password. + * @param enable When true, the prompt will fall back to ask for the user's device + * credentials (PIN, pattern, or password). + * @return + */ + public Builder setEnableFallback(boolean enable) { + mBundle.putBoolean(KEY_ENABLE_FALLBACK, enable); + return this; + } + + /** * Creates a {@link BiometricPrompt}. * @return a {@link BiometricPrompt} * @throws IllegalArgumentException if any of the required fields are not set. @@ -250,11 +266,15 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan final CharSequence title = mBundle.getCharSequence(KEY_TITLE); final CharSequence negative = mBundle.getCharSequence(KEY_NEGATIVE_TEXT); final boolean useDefaultTitle = mBundle.getBoolean(KEY_USE_DEFAULT_TITLE); + final boolean enableFallback = mBundle.getBoolean(KEY_ENABLE_FALLBACK); if (TextUtils.isEmpty(title) && !useDefaultTitle) { throw new IllegalArgumentException("Title must be set and non-empty"); - } else if (TextUtils.isEmpty(negative)) { + } else if (TextUtils.isEmpty(negative) && !enableFallback) { throw new IllegalArgumentException("Negative text must be set and non-empty"); + } else if (!TextUtils.isEmpty(negative) && enableFallback) { + throw new IllegalArgumentException("Can't have both negative button behavior" + + " and fallback enabled"); } return new BiometricPrompt(mContext, mBundle, mPositiveButtonInfo, mNegativeButtonInfo); } @@ -514,6 +534,9 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan if (callback == null) { throw new IllegalArgumentException("Must supply a callback"); } + if (mBundle.getBoolean(KEY_ENABLE_FALLBACK)) { + throw new IllegalArgumentException("Fallback not supported with crypto"); + } authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId()); } diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl index de828f2d6757..e4336d171ab6 100644 --- a/core/java/android/hardware/biometrics/IBiometricService.aidl +++ b/core/java/android/hardware/biometrics/IBiometricService.aidl @@ -51,4 +51,12 @@ interface IBiometricService { // Reset the timeout when user authenticates with strong auth (e.g. PIN, pattern or password) void resetTimeout(in byte [] token); + + // TODO(b/123378871): Remove when moved. + // CDCA needs to send results to BiometricService if it was invoked using BiometricPrompt's + // setEnableFallback method, since there's no way for us to intercept onActivityResult. + // CDCA is launched from BiometricService (startActivityAsUser) instead of *ForResult. + void onConfirmDeviceCredentialSuccess(); + // TODO(b/123378871): Remove when moved. + void onConfirmDeviceCredentialError(int error, String message); } diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 41fedc5fab45..15d66e6e646f 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -29,10 +29,12 @@ import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.AppOpsManager; import android.app.IActivityTaskManager; +import android.app.KeyguardManager; import android.app.TaskStackListener; import android.app.UserSwitchObserver; import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; import android.database.ContentObserver; import android.hardware.biometrics.BiometricAuthenticator; @@ -377,6 +379,13 @@ public class BiometricService extends SystemService { new BiometricTaskStackListener(); private final Random mRandom = new Random(); + // TODO(b/123378871): Remove when moved. + // When BiometricPrompt#setEnableFallback is set to true, we need to store the client (app) + // receiver. BiometricService internally launches CDCA which invokes BiometricService to + // start authentication (normal path). When auth is success/rejected, CDCA will use an aidl + // method to poke BiometricService - the result will then be forwarded to this receiver. + private IBiometricServiceReceiver mConfirmDeviceCredentialReceiver; + // The current authentication session, null if idle/done. We need to track both the current // and pending sessions since errors may be sent to either. private AuthSession mCurrentAuthSession; @@ -705,6 +714,22 @@ public class BiometricService extends SystemService { } } + // Launch CDC instead if necessary. CDC will return results through an AIDL call, since + // we can't get activity results. Store the receiver somewhere so we can forward the + // result back to the client. + // TODO(b/123378871): Remove when moved. + if (bundle.getBoolean(BiometricPrompt.KEY_ENABLE_FALLBACK)) { + mConfirmDeviceCredentialReceiver = receiver; + final KeyguardManager kgm = getContext().getSystemService(KeyguardManager.class); + // Use this so we don't need to duplicate logic.. + final Intent intent = kgm.createConfirmDeviceCredentialIntent(null /* title */, + null /* description */); + // Then give it the bundle to do magic behavior.. + intent.putExtra(KeyguardManager.EXTRA_BIOMETRIC_PROMPT_BUNDLE, bundle); + getContext().startActivityAsUser(intent, UserHandle.CURRENT); + return; + } + mHandler.post(() -> { final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId); final int modality = result.first; @@ -745,6 +770,36 @@ public class BiometricService extends SystemService { }); } + @Override // Binder call + public void onConfirmDeviceCredentialSuccess() { + checkInternalPermission(); + if (mConfirmDeviceCredentialReceiver == null) { + Slog.w(TAG, "onCDCASuccess null!"); + return; + } + try { + mConfirmDeviceCredentialReceiver.onAuthenticationSucceeded(); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException", e); + } + mConfirmDeviceCredentialReceiver = null; + } + + @Override // Binder call + public void onConfirmDeviceCredentialError(int error, String message) { + checkInternalPermission(); + if (mConfirmDeviceCredentialReceiver == null) { + Slog.w(TAG, "onCDCAError null! Error: " + error + " " + message); + return; + } + try { + mConfirmDeviceCredentialReceiver.onError(error, message); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException", e); + } + mConfirmDeviceCredentialReceiver = null; + } + /** * authenticate() (above) which is called from BiometricPrompt determines which * modality/modalities to start authenticating with. authenticateInternal() should only be |