From 3a0187194ae34218f636b25d5ad3093b3e95846d Mon Sep 17 00:00:00 2001 From: Kevin Chyn Date: Mon, 8 Oct 2018 15:40:05 -0700 Subject: [PATCH] Add protected BiometricPrompt API to allow default title ConfirmDeviceCredentials sometimes shows up without a title. Since CC is now using BiometricPrompt, we need at least a private API that allows the client to have the dialog show a default title. Bug: 111461540 Test: Manual test with modified BiometricPromptDemo, to launch CC with empty string Change-Id: I02a3c9327635c04f201f76754f7c0e1135e5ff36 --- .../hardware/biometrics/BiometricPrompt.java | 19 +++++++++++++++- core/res/res/values/strings.xml | 3 +++ core/res/res/values/symbols.xml | 1 + .../systemui/biometrics/BiometricDialogView.java | 4 +++- .../server/biometrics/AuthenticationClient.java | 11 +++++++++ .../server/biometrics/BiometricService.java | 9 +++++++- .../server/biometrics/BiometricServiceBase.java | 26 ++++++++++++++++++++++ 7 files changed, 70 insertions(+), 3 deletions(-) diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index 83998cc1c66a..7952c4104d1f 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -17,6 +17,7 @@ package android.hardware.biometrics; import static android.Manifest.permission.USE_BIOMETRIC; +import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; import android.annotation.CallbackExecutor; import android.annotation.NonNull; @@ -55,6 +56,10 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan /** * @hide */ + public static final String KEY_USE_DEFAULT_TITLE = "use_default_title"; + /** + * @hide + */ public static final String KEY_SUBTITLE = "subtitle"; /** * @hide @@ -131,6 +136,17 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** + * For internal use currently. Only takes effect if title is null/empty. Shows a default + * modality-specific title. + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public Builder setUseDefaultTitle() { + mBundle.putBoolean(KEY_USE_DEFAULT_TITLE, true); + return this; + } + + /** * Optional: Set the subtitle to display. * @param subtitle * @return @@ -206,8 +222,9 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan public BiometricPrompt build() { final CharSequence title = mBundle.getCharSequence(KEY_TITLE); final CharSequence negative = mBundle.getCharSequence(KEY_NEGATIVE_TEXT); + final boolean useDefaultTitle = mBundle.getBoolean(KEY_USE_DEFAULT_TITLE); - if (TextUtils.isEmpty(title)) { + if (TextUtils.isEmpty(title) && !useDefaultTitle) { 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"); diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 6d0127a4bba1..5c14e3952d55 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1418,6 +1418,9 @@ Allows the app to read locations from your media collection. + + Application %s wants to authenticate. + Biometric hardware unavailable diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 72ae0d61654a..ea44ab2387a7 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2394,6 +2394,7 @@ + diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java index c90861e4af52..7d77929556a5 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java @@ -200,7 +200,9 @@ public abstract class BiometricDialogView extends LinearLayout { mLastState = STATE_NONE; updateState(STATE_AUTHENTICATING); - title.setText(mBundle.getCharSequence(BiometricPrompt.KEY_TITLE)); + CharSequence titleText = mBundle.getCharSequence(BiometricPrompt.KEY_TITLE); + + title.setText(titleText); title.setSelected(true); positive.setVisibility(View.INVISIBLE); diff --git a/services/core/java/com/android/server/biometrics/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/AuthenticationClient.java index bdbb0a40d797..61836fdd7316 100644 --- a/services/core/java/com/android/server/biometrics/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/AuthenticationClient.java @@ -27,6 +27,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.security.KeyStore; +import android.text.TextUtils; import android.util.Slog; import com.android.internal.statusbar.IStatusBarService; @@ -205,6 +206,16 @@ public abstract class AuthenticationClient extends ClientMonitor { return super.onError(deviceId, error, vendorCode); } + public void setTitleIfEmpty(CharSequence title) { + if (TextUtils.isEmpty(mBundle.getCharSequence(BiometricPrompt.KEY_TITLE))) { + mBundle.putCharSequence(BiometricPrompt.KEY_TITLE, title); + } + } + + public boolean isBiometricPrompt() { + return mBundle != null; + } + private void notifyClientAuthenticationSucceeded(BiometricAuthenticator.Identifier identifier) throws RemoteException { final BiometricServiceBase.ServiceListener listener = getListener(); diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 2b59386d7e87..25d5133dfc63 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -29,6 +29,7 @@ import android.content.pm.PackageManager; import android.database.ContentObserver; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; +import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.IBiometricPromptReceiver; @@ -255,6 +256,12 @@ public class BiometricService extends SystemService { return; } + // Check the usage of this in system server. Need to remove this check if it becomes + // a public API. + if (bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false)) { + checkInternalPermission(); + } + final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); final int callingUserId = UserHandle.getCallingUserId(); @@ -400,7 +407,7 @@ public class BiometricService extends SystemService { private void checkInternalPermission() { getContext().enforceCallingPermission(USE_BIOMETRIC_INTERNAL, - "Must have MANAGE_BIOMETRIC permission"); + "Must have USE_BIOMETRIC_INTERNAL permission"); } private void checkPermission() { diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java index 71bf56085452..74d742af5b84 100644 --- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java +++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java @@ -16,6 +16,7 @@ package com.android.server.biometrics; +import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE; import android.app.ActivityManager; @@ -55,6 +56,7 @@ import android.util.Slog; import android.util.SparseBooleanArray; import android.util.SparseIntArray; +import com.android.internal.R; import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.server.SystemService; @@ -704,6 +706,30 @@ public abstract class BiometricServiceBase extends SystemService } mHandler.post(() -> { + if (client.isBiometricPrompt()) { + try { + final List procs = + ActivityManager.getService().getRunningAppProcesses(); + for (int i = 0; i < procs.size(); i++) { + final ActivityManager.RunningAppProcessInfo info = procs.get(i); + if (info.uid == callingUid && info.importance == IMPORTANCE_FOREGROUND) { + PackageManager pm = getContext().getPackageManager(); + final CharSequence label = pm.getApplicationLabel( + pm.getApplicationInfo(info.processName, + PackageManager.GET_META_DATA)); + final String title = getContext() + .getString(R.string.biometric_dialog_default_title, label); + client.setTitleIfEmpty(title); + break; + } + } + } catch (RemoteException e) { + Slog.e(getTag(), "Unable to get application name", e); + } catch (PackageManager.NameNotFoundException e) { + Slog.e(getTag(), "Unable to get application name", e); + } + } + mMetricsLogger.histogram(getMetrics().tagAuthToken(), opId != 0L ? 1 : 0); // Get performance stats object for this user. -- 2.11.0