diff --git a/Android.bp b/Android.bp index faad6f32dec43..9f93d399f0741 100644 --- a/Android.bp +++ b/Android.bp @@ -152,9 +152,10 @@ java_defaults { ":libcamera_client_framework_aidl", "core/java/android/hardware/IConsumerIrService.aidl", "core/java/android/hardware/ISerialManager.aidl", + "core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl", + "core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl", "core/java/android/hardware/biometrics/IBiometricService.aidl", "core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl", - "core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl", "core/java/android/hardware/biometrics/IBiometricServiceLockoutResetCallback.aidl", "core/java/android/hardware/display/IDisplayManager.aidl", "core/java/android/hardware/display/IDisplayManagerCallback.aidl", diff --git a/api/current.txt b/api/current.txt index 70ec35d05b31c..fe6b2a635df0a 100755 --- a/api/current.txt +++ b/api/current.txt @@ -15963,7 +15963,10 @@ package android.hardware { package android.hardware.biometrics { public class BiometricManager { - method public boolean hasEnrolledBiometrics(); + method public int canAuthenticate(); + field public static final int ERROR_NONE = 0; // 0x0 + field public static final int ERROR_NO_BIOMETRICS = 11; // 0xb + field public static final int ERROR_UNAVAILABLE = 1; // 0x1 } public class BiometricPrompt { diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java index 6150be361ef24..2a64c2e1e0741 100644 --- a/core/java/android/hardware/biometrics/BiometricConstants.java +++ b/core/java/android/hardware/biometrics/BiometricConstants.java @@ -33,6 +33,13 @@ public interface BiometricConstants { // removal. // + /** + * This was not added here since it would update BiometricPrompt API. But, is used in + * BiometricManager. + * @hide + */ + int BIOMETRIC_ERROR_NONE = 0; + /** * The hardware is unavailable. Try again later. */ diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index eea5f9ba98357..b8739b9fb2f9e 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -17,16 +17,35 @@ package android.hardware.biometrics; import static android.Manifest.permission.USE_BIOMETRIC; +import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; import android.annotation.RequiresPermission; import android.content.Context; import android.os.RemoteException; +import android.util.Slog; /** * A class that contains biometric utilities. For authentication, see {@link BiometricPrompt}. */ public class BiometricManager { + private static final String TAG = "BiometricManager"; + + /** + * No error detected. + */ + public static final int ERROR_NONE = BiometricConstants.BIOMETRIC_ERROR_NONE; + + /** + * The hardware is unavailable. Try again later. + */ + public static final int ERROR_UNAVAILABLE = BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE; + + /** + * The user does not have any biometrics enrolled. + */ + public static final int ERROR_NO_BIOMETRICS = BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS; + private final Context mContext; private final IBiometricService mService; @@ -41,16 +60,42 @@ public class BiometricManager { } /** - * Determine if there is at least one biometric enrolled. + * Determine if biometrics can be used. In other words, determine if {@link BiometricPrompt} + * can be expected to be shown (hardware available, templates enrolled, user-enabled). * - * @return true if at least one biometric is enrolled, false otherwise + * @return Returns {@link #ERROR_NO_BIOMETRICS} if the user does not have any enrolled, or + * {@link #ERROR_UNAVAILABLE} if none are currently supported/enabled. Returns + * {@link #ERROR_NONE} if a biometric can currently be used (enrolled and available). */ @RequiresPermission(USE_BIOMETRIC) - public boolean hasEnrolledBiometrics() { - try { - return mService.hasEnrolledBiometrics(mContext.getOpPackageName()); - } catch (RemoteException e) { - return false; + public int canAuthenticate() { + if (mService != null) { + try { + return mService.canAuthenticate(mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "hasEnrolledBiometrics(): Service not connected"); + return ERROR_UNAVAILABLE; + } + } + + /** + * Listens for changes to biometric eligibility on keyguard from user settings. + * @param callback + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback) { + if (mService != null) { + try { + mService.registerEnabledOnKeyguardCallback(callback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "registerEnabledOnKeyguardCallback(): Service not connected"); } } } diff --git a/core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl b/core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl new file mode 100644 index 0000000000000..d22e7e295b774 --- /dev/null +++ b/core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl @@ -0,0 +1,26 @@ +/* + * 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.biometrics; + +import android.hardware.biometrics.BiometricSourceType; + +/** + * @hide + */ +oneway interface IBiometricEnabledOnKeyguardCallback { + void onChanged(in BiometricSourceType type, boolean enabled); +} \ No newline at end of file diff --git a/core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl index 67c9346da265d..27d25b86b859b 100644 --- a/core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl +++ b/core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl @@ -15,9 +15,6 @@ */ package android.hardware.biometrics; -import android.os.Bundle; -import android.os.UserHandle; - /** * Communication channel from the BiometricPrompt (SysUI) back to AuthenticationClient. * @hide diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl index fd9d5725f921f..51e4ecbf56b08 100644 --- a/core/java/android/hardware/biometrics/IBiometricService.aidl +++ b/core/java/android/hardware/biometrics/IBiometricService.aidl @@ -17,6 +17,7 @@ package android.hardware.biometrics; import android.os.Bundle; +import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.IBiometricPromptReceiver; import android.hardware.biometrics.IBiometricServiceReceiver; @@ -37,6 +38,9 @@ interface IBiometricService { // Cancel authentication for the given sessionId void cancelAuthentication(IBinder token, String opPackageName); - // Returns true if the user has at least one enrolled biometric. - boolean hasEnrolledBiometrics(String opPackageName); + // Checks if biometrics can be used. + int canAuthenticate(String opPackageName); + + // Register callback for when keyguard biometric eligibility changes. + void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback); } \ No newline at end of file diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl index 71abdd246097b..a6e3696eeb10a 100644 --- a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl +++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl @@ -15,10 +15,6 @@ */ package android.hardware.biometrics; -import android.hardware.biometrics.BiometricSourceType; -import android.os.Bundle; -import android.os.UserHandle; - /** * Communication channel from the BiometricService back to BiometricPrompt. * @hide diff --git a/core/java/android/hardware/face/IFaceServiceReceiver.aidl b/core/java/android/hardware/face/IFaceServiceReceiver.aidl index 16fb69021467a..b88574b74c361 100644 --- a/core/java/android/hardware/face/IFaceServiceReceiver.aidl +++ b/core/java/android/hardware/face/IFaceServiceReceiver.aidl @@ -16,8 +16,6 @@ package android.hardware.face; import android.hardware.face.Face; -import android.os.Bundle; -import android.os.UserHandle; /** * Communication channel from the FaceService back to FaceAuthenticationManager. diff --git a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl index 370383f4a909a..cf1c94ea5346a 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl @@ -16,8 +16,6 @@ package android.hardware.fingerprint; import android.hardware.fingerprint.Fingerprint; -import android.os.Bundle; -import android.os.UserHandle; /** * Communication channel from the FingerprintService back to FingerprintManager. diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index f1b53fec07144..c7685f832cbb8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -41,7 +41,6 @@ import android.app.admin.DevicePolicyManager; import android.app.trust.TrustManager; import android.content.BroadcastReceiver; import android.content.ComponentName; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -49,13 +48,14 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.database.ContentObserver; +import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricSourceType; +import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback; import android.hardware.fingerprint.FingerprintManager.AuthenticationResult; import android.media.AudioManager; -import android.net.Uri; import android.os.BatteryManager; import android.os.CancellationSignal; import android.os.Handler; @@ -250,51 +250,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { private static final int HW_UNAVAILABLE_TIMEOUT = 3000; // ms private static final int HW_UNAVAILABLE_RETRY_MAX = 3; - private class SettingObserver extends ContentObserver { - private final Uri FACE_UNLOCK_KEYGUARD_ENABLED = - Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED); - - private final ContentResolver mContentResolver; - - /** - * Creates a content observer. - * - * @param handler The handler to run {@link #onChange} on, or null if none. - */ - public SettingObserver(Handler handler) { - super(handler); - mContentResolver = mContext.getContentResolver(); - updateContentObserver(); - } - - public void updateContentObserver() { - mContentResolver.unregisterContentObserver(this); - mContentResolver.registerContentObserver(FACE_UNLOCK_KEYGUARD_ENABLED, - false /* notifyForDescendents */, - this, - UserHandle.USER_CURRENT); - - // Update the value immediately - onChange(true /* selfChange */, FACE_UNLOCK_KEYGUARD_ENABLED); - } - - @Override - public void onChange(boolean selfChange, Uri uri) { - if (FACE_UNLOCK_KEYGUARD_ENABLED.equals(uri)) { - mFaceSettingEnabledForUser = - Settings.Secure.getIntForUser( - mContentResolver, - Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED, - 1 /* default */, - UserHandle.USER_CURRENT) != 0; - updateBiometricListeningState(); - } - } - } - - private final SettingObserver mSettingObserver; - private boolean mFaceSettingEnabledForUser; - private final Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { @@ -400,6 +355,18 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { } }; + private boolean mFaceSettingEnabledForUser; + private BiometricManager mBiometricManager; + private IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback = + new IBiometricEnabledOnKeyguardCallback.Stub() { + @Override + public void onChanged(BiometricSourceType type, boolean enabled) throws RemoteException { + if (type == BiometricSourceType.FACE) { + mFaceSettingEnabledForUser = enabled; + } + } + }; + private OnSubscriptionsChangedListener mSubscriptionListener = new OnSubscriptionsChangedListener() { @Override @@ -1165,7 +1132,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { private CancellationSignal mFingerprintCancelSignal; private CancellationSignal mFaceCancelSignal; private FingerprintManager mFpm; - private FaceManager mFaceAuthenticationManager; + private FaceManager mFaceManager; /** * When we receive a @@ -1434,7 +1401,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { mSubscriptionManager = SubscriptionManager.from(context); mDeviceProvisioned = isDeviceProvisionedInSettingsDb(); mStrongAuthTracker = new StrongAuthTracker(context); - mSettingObserver = new SettingObserver(mHandler); // Since device can't be un-provisioned, we only need to register a content observer // to update mDeviceProvisioned when we are... @@ -1504,17 +1470,21 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { mFpm = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE); } - if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) { - mFaceAuthenticationManager = - (FaceManager) context.getSystemService(Context.FACE_SERVICE); + mFaceManager = (FaceManager) context.getSystemService(Context.FACE_SERVICE); } + + if (mFpm != null || mFaceManager != null) { + mBiometricManager = context.getSystemService(BiometricManager.class); + mBiometricManager.registerEnabledOnKeyguardCallback(mBiometricEnabledCallback); + } + updateBiometricListeningState(); if (mFpm != null) { mFpm.addLockoutResetCallback(mFingerprintLockoutResetCallback); } - if (mFaceAuthenticationManager != null) { - mFaceAuthenticationManager.addLockoutResetCallback(mFaceLockoutResetCallback); + if (mFaceManager != null) { + mFaceManager.addLockoutResetCallback(mFaceLockoutResetCallback); } ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); @@ -1629,7 +1599,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { mFaceCancelSignal.cancel(); } mFaceCancelSignal = new CancellationSignal(); - mFaceAuthenticationManager.authenticate(null, mFaceCancelSignal, 0, + mFaceManager.authenticate(null, mFaceCancelSignal, 0, mFaceAuthenticationCallback, null); setFaceRunningState(BIOMETRIC_STATE_RUNNING); } @@ -1641,9 +1611,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { } public boolean isUnlockWithFacePossible(int userId) { - return mFaceAuthenticationManager != null && mFaceAuthenticationManager.isHardwareDetected() + return mFaceManager != null && mFaceManager.isHardwareDetected() && !isFaceDisabled(userId) - && mFaceAuthenticationManager.hasEnrolledTemplates(userId); + && mFaceManager.hasEnrolledTemplates(userId); } private void stopListeningForFingerprint() { @@ -1765,7 +1735,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { * Handle {@link #MSG_USER_SWITCH_COMPLETE} */ private void handleUserSwitchComplete(int userId) { - mSettingObserver.updateContentObserver(); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { @@ -2437,7 +2406,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags)); pw.println(" trustManaged=" + getUserTrustIsManaged(userId)); } - if (mFaceAuthenticationManager != null && mFaceAuthenticationManager.isHardwareDetected()) { + if (mFaceManager != null && mFaceManager.isHardwareDetected()) { final int userId = ActivityManager.getCurrentUser(); final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId); pw.println(" Face authentication state (user=" + userId + ")"); @@ -2449,6 +2418,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { pw.println(" possible=" + isUnlockWithFacePossible(userId)); pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags)); pw.println(" trustManaged=" + getUserTrustIsManaged(userId)); + pw.println(" enabledByUser=" + mFaceSettingEnabledForUser); } } } diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 87cf9c434fc0f..5eca48961b854 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -17,13 +17,20 @@ package com.android.server.biometrics; import static android.Manifest.permission.USE_BIOMETRIC; +import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; import static android.Manifest.permission.USE_FINGERPRINT; +import android.app.ActivityManager; import android.app.AppOpsManager; +import android.app.UserSwitchObserver; +import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager; +import android.database.ContentObserver; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; +import android.hardware.biometrics.BiometricSourceType; +import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.IBiometricPromptReceiver; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceReceiver; @@ -31,8 +38,10 @@ import android.hardware.face.FaceManager; import android.hardware.face.IFaceService; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.IFingerprintService; +import android.net.Uri; import android.os.Binder; import android.os.Bundle; +import android.os.DeadObjectException; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -40,12 +49,14 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.provider.Settings; +import android.util.Pair; import android.util.Slog; import com.android.internal.R; import com.android.server.SystemService; import java.util.ArrayList; +import java.util.List; /** * System service that arbitrates the modality for BiometricPrompt to use. @@ -87,6 +98,8 @@ public class BiometricService extends SystemService { private final boolean mHasFeatureFingerprint; private final boolean mHasFeatureIris; private final boolean mHasFeatureFace; + private final SettingObserver mSettingObserver; + private final List mEnabledOnKeyguardCallbacks; private IFingerprintService mFingerprintService; private IFaceService mFaceService; @@ -120,6 +133,107 @@ public class BiometricService extends SystemService { } } + private final class SettingObserver extends ContentObserver { + private final Uri FACE_UNLOCK_KEYGUARD_ENABLED = + Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED); + private final Uri FACE_UNLOCK_APP_ENABLED = + Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_APP_ENABLED); + + private final ContentResolver mContentResolver; + private boolean mFaceEnabledOnKeyguard; + private boolean mFaceEnabledForApps; + + /** + * Creates a content observer. + * + * @param handler The handler to run {@link #onChange} on, or null if none. + */ + SettingObserver(Handler handler) { + super(handler); + mContentResolver = getContext().getContentResolver(); + updateContentObserver(); + } + + void updateContentObserver() { + mContentResolver.unregisterContentObserver(this); + mContentResolver.registerContentObserver(FACE_UNLOCK_KEYGUARD_ENABLED, + false /* notifyForDescendents */, + this /* observer */, + UserHandle.USER_CURRENT); + mContentResolver.registerContentObserver(FACE_UNLOCK_APP_ENABLED, + false /* notifyForDescendents */, + this /* observer */, + UserHandle.USER_CURRENT); + + // Update the value immediately + onChange(true /* selfChange */, FACE_UNLOCK_KEYGUARD_ENABLED); + onChange(true /* selfChange */, FACE_UNLOCK_APP_ENABLED); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + if (FACE_UNLOCK_KEYGUARD_ENABLED.equals(uri)) { + mFaceEnabledOnKeyguard = + Settings.Secure.getIntForUser( + mContentResolver, + Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED, + 1 /* default */, + UserHandle.USER_CURRENT) != 0; + + List callbacks = mEnabledOnKeyguardCallbacks; + for (int i = 0; i < callbacks.size(); i++) { + callbacks.get(i).notify(BiometricSourceType.FACE, mFaceEnabledOnKeyguard); + } + } else if (FACE_UNLOCK_APP_ENABLED.equals(uri)) { + mFaceEnabledForApps = + Settings.Secure.getIntForUser( + mContentResolver, + Settings.Secure.FACE_UNLOCK_APP_ENABLED, + 1 /* default */, + UserHandle.USER_CURRENT) != 0; + } + } + + boolean getFaceEnabledOnKeyguard() { + return mFaceEnabledOnKeyguard; + } + + boolean getFaceEnabledForApps() { + return mFaceEnabledForApps; + } + } + + private final class EnabledOnKeyguardCallback implements IBinder.DeathRecipient { + + private final IBiometricEnabledOnKeyguardCallback mCallback; + + EnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback) { + mCallback = callback; + try { + mCallback.asBinder().linkToDeath(EnabledOnKeyguardCallback.this, 0); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to linkToDeath", e); + } + } + + void notify(BiometricSourceType sourceType, boolean enabled) { + try { + mCallback.onChanged(sourceType, enabled); + } catch (DeadObjectException e) { + Slog.w(TAG, "Death while invoking notify", e); + mEnabledOnKeyguardCallbacks.remove(this); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to invoke onChanged", e); + } + } + + @Override + public void binderDied() { + Slog.e(TAG, "Enabled callback binder died"); + mEnabledOnKeyguardCallbacks.remove(this); + } + } + /** * This is just a pass-through service that wraps Fingerprint, Iris, Face services. This service * should not carry any state. The reality is we need to keep a tiny amount of state so that @@ -131,7 +245,7 @@ public class BiometricService extends SystemService { public void authenticate(IBinder token, long sessionId, int userId, IBiometricServiceReceiver receiver, int flags, String opPackageName, Bundle bundle, IBiometricPromptReceiver dialogReceiver) throws RemoteException { - // Check the USE_BIOMETRIC permission here. In the BiometricService, check do the + // Check the USE_BIOMETRIC permission here. In the BiometricServiceBase, check do the // AppOps and foreground check. checkPermission(); @@ -146,8 +260,38 @@ public class BiometricService extends SystemService { final int callingUserId = UserHandle.getCallingUserId(); mHandler.post(() -> { - mCurrentModality = checkAndGetBiometricModality(receiver); + final Pair result = checkAndGetBiometricModality(); + final int modality = result.first; + final int error = result.second; + // Check for errors, notify callback, and return + if (error != BiometricConstants.BIOMETRIC_ERROR_NONE) { + try { + final String hardwareUnavailable = + getContext().getString(R.string.biometric_error_hw_unavailable); + switch (error) { + case BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT: + receiver.onError(0 /* deviceId */, error, hardwareUnavailable); + break; + case BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE: + receiver.onError(0 /* deviceId */, error, hardwareUnavailable); + break; + case BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS: + receiver.onError(0 /* deviceId */, error, + getErrorString(modality, error, 0 /* vendorCode */)); + break; + default: + Slog.e(TAG, "Unhandled error"); + break; + } + } catch (RemoteException e) { + Slog.e(TAG, "Unable to send error", e); + } + return; + } + + // Actually start authentication + mCurrentModality = modality; try { // No polymorphism :( if (mCurrentModality == BIOMETRIC_FINGERPRINT) { @@ -157,18 +301,9 @@ public class BiometricService extends SystemService { } else if (mCurrentModality == BIOMETRIC_IRIS) { Slog.w(TAG, "Unsupported modality"); } else if (mCurrentModality == BIOMETRIC_FACE) { - // If the user disabled face for apps, return ERROR_HW_UNAVAILABLE - if (isFaceEnabledForApps()) { - receiver.onError(0 /* deviceId */, - BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, - FaceManager.getErrorString(getContext(), - BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, - 0 /* vendorCode */)); - } else { - mFaceService.authenticateFromService(true /* requireConfirmation */, - token, sessionId, userId, receiver, flags, opPackageName, - bundle, dialogReceiver, callingUid, callingPid, callingUserId); - } + mFaceService.authenticateFromService(true /* requireConfirmation */, + token, sessionId, userId, receiver, flags, opPackageName, + bundle, dialogReceiver, callingUid, callingPid, callingUserId); } else { Slog.w(TAG, "Unsupported modality"); } @@ -178,15 +313,6 @@ public class BiometricService extends SystemService { }); } - private boolean isFaceEnabledForApps() { - // TODO: maybe cache this and eliminate duplicated code with KeyguardUpdateMonitor - return Settings.Secure.getIntForUser( - getContext().getContentResolver(), - Settings.Secure.FACE_UNLOCK_APP_ENABLED, - 1 /* default */, - UserHandle.USER_CURRENT) == 0; - } - @Override // Binder call public void cancelAuthentication(IBinder token, String opPackageName) throws RemoteException { @@ -221,31 +347,46 @@ public class BiometricService extends SystemService { } @Override // Binder call - public boolean hasEnrolledBiometrics(String opPackageName) { + public int canAuthenticate(String opPackageName) { checkPermission(); - - if (mAppOps.noteOp(AppOpsManager.OP_USE_BIOMETRIC, Binder.getCallingUid(), - opPackageName) != AppOpsManager.MODE_ALLOWED) { - Slog.w(TAG, "Rejecting " + opPackageName + "; permission denied"); - throw new SecurityException("Permission denied"); - } + checkAppOp(opPackageName, Binder.getCallingUid()); final long ident = Binder.clearCallingIdentity(); - boolean hasEnrolled = false; + int error; try { - // Note: On devices with multi-modal authentication, the selection logic will need - // to be updated. - for (int i = 0; i < mAuthenticators.size(); i++) { - if (mAuthenticators.get(i).getAuthenticator().hasEnrolledTemplates()) { - hasEnrolled = true; - break; - } - } + final Pair result = checkAndGetBiometricModality(); + error = result.second; } finally { Binder.restoreCallingIdentity(ident); } - return hasEnrolled; + return error; } + + @Override + public void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback) + throws RemoteException { + checkInternalPermission(); + mEnabledOnKeyguardCallbacks.add(new EnabledOnKeyguardCallback(callback)); + try { + callback.onChanged(BiometricSourceType.FACE, + mSettingObserver.getFaceEnabledOnKeyguard()); + } catch (RemoteException e) { + Slog.w(TAG, "Remote exception", e); + } + } + } + + private void checkAppOp(String opPackageName, int callingUid) { + if (mAppOps.noteOp(AppOpsManager.OP_USE_BIOMETRIC, callingUid, + opPackageName) != AppOpsManager.MODE_ALLOWED) { + Slog.w(TAG, "Rejecting " + opPackageName + "; permission denied"); + throw new SecurityException("Permission denied"); + } + } + + private void checkInternalPermission() { + getContext().enforceCallingPermission(USE_BIOMETRIC_INTERNAL, + "Must have MANAGE_BIOMETRIC permission"); } private void checkPermission() { @@ -270,11 +411,26 @@ public class BiometricService extends SystemService { mAppOps = context.getSystemService(AppOpsManager.class); mHandler = new Handler(Looper.getMainLooper()); + mEnabledOnKeyguardCallbacks = new ArrayList<>(); + mSettingObserver = new SettingObserver(mHandler); final PackageManager pm = context.getPackageManager(); mHasFeatureFingerprint = pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT); mHasFeatureIris = pm.hasSystemFeature(PackageManager.FEATURE_IRIS); mHasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE); + + try { + ActivityManager.getService().registerUserSwitchObserver( + new UserSwitchObserver() { + @Override + public void onUserSwitchComplete(int newUserId) { + mSettingObserver.updateContentObserver(); + } + }, BiometricService.class.getName() + ); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to register user switch observer", e); + } } @Override @@ -305,65 +461,91 @@ public class BiometricService extends SystemService { * Checks if there are any available biometrics, and returns the modality. This method also * returns errors through the callback (no biometric feature, hardware not detected, no * templates enrolled, etc). This service must not start authentication if errors are sent. + * + * @Returns A pair [Modality, Error] with Modality being one of {@link #BIOMETRIC_NONE}, + * {@link #BIOMETRIC_FINGERPRINT}, {@link #BIOMETRIC_IRIS}, {@link #BIOMETRIC_FACE} + * and the error containing one of the {@link BiometricConstants} errors. */ - private int checkAndGetBiometricModality(IBiometricServiceReceiver receiver) { + private Pair checkAndGetBiometricModality() { int modality = BIOMETRIC_NONE; - final String hardwareUnavailable = - getContext().getString(R.string.biometric_error_hw_unavailable); // No biometric features, send error if (mAuthenticators.isEmpty()) { - try { - receiver.onError(0 /* deviceId */, - BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT, - hardwareUnavailable); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to send error", e); - } - return BIOMETRIC_NONE; + return new Pair<>(BIOMETRIC_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT); } - // Find first authenticator that's both detected and enrolled + // Assuming that authenticators are listed in priority-order, the rest of this function + // will go through and find the first authenticator that's available, enrolled, and enabled. + // The tricky part is returning the correct error. Error strings that are modality-specific + // should also respect the priority-order. + + // Find first authenticator that's detected, enrolled, and enabled. boolean isHardwareDetected = false; boolean hasTemplatesEnrolled = false; + boolean enabledForApps = false; + + int firstHwAvailable = BIOMETRIC_NONE; for (int i = 0; i < mAuthenticators.size(); i++) { - int featureId = mAuthenticators.get(i).getType(); + modality = mAuthenticators.get(i).getType(); BiometricAuthenticator authenticator = mAuthenticators.get(i).getAuthenticator(); if (authenticator.isHardwareDetected()) { isHardwareDetected = true; + if (firstHwAvailable == BIOMETRIC_NONE) { + // Store the first one since we want to return the error in correct priority + // order. + firstHwAvailable = modality; + } if (authenticator.hasEnrolledTemplates()) { hasTemplatesEnrolled = true; - modality = featureId; - break; + if (isEnabledForApp(modality)) { + enabledForApps = true; + break; + } } } } // Check error conditions if (!isHardwareDetected) { - try { - receiver.onError(0 /* deviceId */, - BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, - hardwareUnavailable); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to send error", e); - } - return BIOMETRIC_NONE; - } - if (!hasTemplatesEnrolled) { - try { - receiver.onError(0 /* deviceId */, - BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS, - FaceManager.getErrorString(getContext(), - BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS, - 0 /* vendorCode */)); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to send error", e); - } - return BIOMETRIC_NONE; + return new Pair<>(BIOMETRIC_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); + } else if (!hasTemplatesEnrolled) { + // Return the modality here so the correct error string can be sent. This error is + // preferred over !enabledForApps + return new Pair<>(firstHwAvailable, BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS); + } else if (!enabledForApps) { + return new Pair<>(BIOMETRIC_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); } - return modality; + return new Pair<>(modality, BiometricConstants.BIOMETRIC_ERROR_NONE); + } + + private boolean isEnabledForApp(int modality) { + switch(modality) { + case BIOMETRIC_FINGERPRINT: + return true; + case BIOMETRIC_IRIS: + return true; + case BIOMETRIC_FACE: + return mSettingObserver.getFaceEnabledForApps(); + default: + Slog.w(TAG, "Unsupported modality: " + modality); + return false; + } + } + + private String getErrorString(int type, int error, int vendorCode) { + switch (type) { + case BIOMETRIC_FINGERPRINT: + return FingerprintManager.getErrorString(getContext(), error, vendorCode); + case BIOMETRIC_IRIS: + Slog.w(TAG, "Modality not supported"); + return null; // not supported + case BIOMETRIC_FACE: + return FaceManager.getErrorString(getContext(), error, vendorCode); + default: + Slog.w(TAG, "Unable to get error string for modality: " + type); + return null; + } } private BiometricAuthenticator getAuthenticator(int type) {