| /* |
| * 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.server.biometrics; |
| |
| import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; |
| import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE; |
| |
| import android.app.ActivityManager; |
| import android.app.ActivityTaskManager; |
| import android.app.AppOpsManager; |
| import android.app.IActivityTaskManager; |
| import android.app.SynchronousUserSwitchObserver; |
| import android.app.TaskStackListener; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.pm.PackageManager; |
| import android.content.pm.UserInfo; |
| import android.hardware.biometrics.BiometricAuthenticator; |
| import android.hardware.biometrics.BiometricConstants; |
| import android.hardware.biometrics.BiometricsProtoEnums; |
| import android.hardware.biometrics.IBiometricNativeHandle; |
| import android.hardware.biometrics.IBiometricService; |
| import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; |
| import android.hardware.biometrics.IBiometricServiceReceiverInternal; |
| import android.hardware.fingerprint.Fingerprint; |
| import android.os.Binder; |
| import android.os.Bundle; |
| import android.os.DeadObjectException; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.IHwBinder; |
| import android.os.IRemoteCallback; |
| import android.os.NativeHandle; |
| import android.os.PowerManager; |
| import android.os.Process; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.os.SystemClock; |
| import android.os.UserHandle; |
| import android.os.UserManager; |
| import android.util.Slog; |
| |
| import com.android.internal.logging.MetricsLogger; |
| import com.android.internal.statusbar.IStatusBarService; |
| import com.android.internal.util.FrameworkStatsLog; |
| import com.android.server.SystemService; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Abstract base class containing all of the business logic for biometric services, e.g. |
| * Fingerprint, Face, Iris. |
| * |
| * @hide |
| */ |
| public abstract class BiometricServiceBase extends SystemService |
| implements IHwBinder.DeathRecipient { |
| |
| protected static final boolean DEBUG = true; |
| |
| private static final boolean CLEANUP_UNKNOWN_TEMPLATES = true; |
| private static final String KEY_LOCKOUT_RESET_USER = "lockout_reset_user"; |
| private static final int MSG_USER_SWITCHING = 10; |
| private static final long CANCEL_TIMEOUT_LIMIT = 3000; // max wait for onCancel() from HAL,in ms |
| |
| private final Context mContext; |
| private final String mKeyguardPackage; |
| private final IActivityTaskManager mActivityTaskManager; |
| private final PowerManager mPowerManager; |
| private final UserManager mUserManager; |
| private final MetricsLogger mMetricsLogger; |
| private final BiometricTaskStackListener mTaskStackListener = new BiometricTaskStackListener(); |
| private final ResetClientStateRunnable mResetClientState = new ResetClientStateRunnable(); |
| private final ArrayList<LockoutResetMonitor> mLockoutMonitors = new ArrayList<>(); |
| |
| protected final IStatusBarService mStatusBarService; |
| protected final Map<Integer, Long> mAuthenticatorIds = |
| Collections.synchronizedMap(new HashMap<>()); |
| protected final AppOpsManager mAppOps; |
| protected final H mHandler = new H(); |
| |
| private final IBinder mToken = new Binder(); // Used for internal enumeration |
| private final ArrayList<UserTemplate> mUnknownHALTemplates = new ArrayList<>(); |
| |
| private IBiometricService mBiometricService; |
| private ClientMonitor mCurrentClient; |
| private ClientMonitor mPendingClient; |
| private PerformanceStats mPerformanceStats; |
| protected int mCurrentUserId = UserHandle.USER_NULL; |
| protected long mHalDeviceId; |
| // Tracks if the current authentication makes use of CryptoObjects. |
| protected boolean mIsCrypto; |
| // Normal authentications are tracked by mPerformanceMap. |
| protected HashMap<Integer, PerformanceStats> mPerformanceMap = new HashMap<>(); |
| // Transactions that make use of CryptoObjects are tracked by mCryptoPerformaceMap. |
| protected HashMap<Integer, PerformanceStats> mCryptoPerformanceMap = new HashMap<>(); |
| protected int mHALDeathCount; |
| |
| protected class PerformanceStats { |
| public int accept; // number of accepted biometrics |
| public int reject; // number of rejected biometrics |
| public int acquire; // total number of acquisitions. Should be >= accept+reject due to poor |
| // image acquisition in some cases (too fast, too slow, dirty sensor, etc.) |
| public int lockout; // total number of lockouts |
| public int permanentLockout; // total number of permanent lockouts |
| } |
| |
| /** |
| * @return the log tag. |
| */ |
| protected abstract String getTag(); |
| |
| /** |
| * @return wrapper for the HAL |
| */ |
| protected abstract DaemonWrapper getDaemonWrapper(); |
| |
| /** |
| * @return the biometric utilities for a specific implementation. |
| */ |
| protected abstract BiometricUtils getBiometricUtils(); |
| |
| /** |
| * @return the metrics constants for a biometric implementation. |
| */ |
| protected abstract Constants getConstants(); |
| |
| /** |
| * @param userId |
| * @return true if the enrollment limit has been reached. |
| */ |
| protected abstract boolean hasReachedEnrollmentLimit(int userId); |
| |
| /** |
| * Notifies the HAL that the user has changed. |
| * @param userId |
| * @param clientPackage |
| */ |
| protected abstract void updateActiveGroup(int userId, String clientPackage); |
| |
| /** |
| * @return The protected intent to reset lockout for a specific biometric. |
| */ |
| protected abstract String getLockoutResetIntent(); |
| |
| /** |
| * @return The permission the sender is required to have in order for the lockout reset intent |
| * to be received by the BiometricService implementation. |
| */ |
| protected abstract String getLockoutBroadcastPermission(); |
| |
| /** |
| * @return The HAL ID. |
| */ |
| protected abstract long getHalDeviceId(); |
| |
| /** |
| * @param userId |
| * @return Returns true if the user has any enrolled biometrics. |
| */ |
| protected abstract boolean hasEnrolledBiometrics(int userId); |
| |
| /** |
| * @return Returns the MANAGE_* permission string, which is required for enrollment, removal |
| * etc. |
| */ |
| protected abstract String getManageBiometricPermission(); |
| |
| /** |
| * Checks if the caller has permission to use the biometric service - throws a SecurityException |
| * if not. |
| */ |
| protected abstract void checkUseBiometricPermission(); |
| |
| /** |
| * Checks if the caller passes the app ops check |
| */ |
| protected abstract boolean checkAppOps(int uid, String opPackageName); |
| |
| protected abstract List<? extends BiometricAuthenticator.Identifier> getEnrolledTemplates( |
| int userId); |
| |
| /** |
| * Notifies clients of any change in the biometric state (active / idle). This is mainly for |
| * Fingerprint navigation gestures. |
| * @param isActive |
| */ |
| protected void notifyClientActiveCallbacks(boolean isActive) {} |
| |
| protected abstract int statsModality(); |
| |
| /** |
| * @return one of the AuthenticationClient LOCKOUT constants |
| */ |
| protected abstract int getLockoutMode(); |
| |
| protected abstract class AuthenticationClientImpl extends AuthenticationClient { |
| |
| // Used to check if the public API that was invoked was from FingerprintManager. Only |
| // to be overridden by FingerprintService. |
| protected boolean isFingerprint() { |
| return false; |
| } |
| |
| public AuthenticationClientImpl(Context context, DaemonWrapper daemon, long halDeviceId, |
| IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId, |
| boolean restricted, String owner, int cookie, boolean requireConfirmation, |
| IBiometricNativeHandle windowId) { |
| super(context, getConstants(), daemon, halDeviceId, token, listener, targetUserId, |
| groupId, opId, restricted, owner, cookie, requireConfirmation, windowId); |
| } |
| |
| @Override |
| protected int statsClient() { |
| if (isKeyguard(getOwnerString())) { |
| return BiometricsProtoEnums.CLIENT_KEYGUARD; |
| } else if (isBiometricPrompt()) { |
| return BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT; |
| } else if (isFingerprint()) { |
| return BiometricsProtoEnums.CLIENT_FINGERPRINT_MANAGER; |
| } else { |
| return BiometricsProtoEnums.CLIENT_UNKNOWN; |
| } |
| } |
| |
| @Override |
| public void onStart() { |
| try { |
| mActivityTaskManager.registerTaskStackListener(mTaskStackListener); |
| } catch (RemoteException e) { |
| Slog.e(getTag(), "Could not register task stack listener", e); |
| } |
| } |
| |
| @Override |
| public void onStop() { |
| try { |
| mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); |
| } catch (RemoteException e) { |
| Slog.e(getTag(), "Could not unregister task stack listener", e); |
| } |
| } |
| |
| @Override |
| public void notifyUserActivity() { |
| userActivity(); |
| } |
| |
| @Override |
| public int handleFailedAttempt() { |
| final int lockoutMode = getLockoutMode(); |
| if (lockoutMode == AuthenticationClient.LOCKOUT_PERMANENT) { |
| mPerformanceStats.permanentLockout++; |
| } else if (lockoutMode == AuthenticationClient.LOCKOUT_TIMED) { |
| mPerformanceStats.lockout++; |
| } |
| |
| // Failing multiple times will continue to push out the lockout time |
| if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) { |
| return lockoutMode; |
| } |
| return AuthenticationClient.LOCKOUT_NONE; |
| } |
| } |
| |
| protected abstract class EnrollClientImpl extends EnrollClient { |
| |
| public EnrollClientImpl(Context context, DaemonWrapper daemon, long halDeviceId, |
| IBinder token, ServiceListener listener, int userId, int groupId, |
| byte[] cryptoToken, boolean restricted, String owner, |
| final int[] disabledFeatures, int timeoutSec, IBiometricNativeHandle windowId) { |
| super(context, getConstants(), daemon, halDeviceId, token, listener, |
| userId, groupId, cryptoToken, restricted, owner, getBiometricUtils(), |
| disabledFeatures, timeoutSec, windowId); |
| } |
| |
| @Override |
| public void notifyUserActivity() { |
| userActivity(); |
| } |
| } |
| |
| /** |
| * An internal class to help clean up unknown templates in HAL and Framework |
| */ |
| private final class InternalRemovalClient extends RemovalClient { |
| InternalRemovalClient(Context context, |
| DaemonWrapper daemon, long halDeviceId, IBinder token, |
| ServiceListener listener, int templateId, int groupId, int userId, |
| boolean restricted, String owner) { |
| super(context, getConstants(), daemon, halDeviceId, token, listener, templateId, groupId, |
| userId, restricted, owner, getBiometricUtils()); |
| } |
| |
| @Override |
| protected int statsModality() { |
| return BiometricServiceBase.this.statsModality(); |
| } |
| } |
| |
| /** |
| * Internal class to help clean up unknown templates in the HAL and Framework |
| */ |
| private final class InternalEnumerateClient extends EnumerateClient { |
| |
| private BiometricUtils mUtils; |
| // List of templates that are known to the Framework. Remove from this list when enumerate |
| // returns a template that contains a match. |
| private List<? extends BiometricAuthenticator.Identifier> mEnrolledList; |
| // List of templates to remove from the HAL |
| private List<BiometricAuthenticator.Identifier> mUnknownHALTemplates = new ArrayList<>(); |
| |
| InternalEnumerateClient(Context context, |
| DaemonWrapper daemon, long halDeviceId, IBinder token, |
| ServiceListener listener, int groupId, int userId, boolean restricted, |
| String owner, List<? extends BiometricAuthenticator.Identifier> enrolledList, |
| BiometricUtils utils) { |
| super(context, getConstants(), daemon, halDeviceId, token, listener, groupId, userId, |
| restricted, owner); |
| mEnrolledList = enrolledList; |
| mUtils = utils; |
| } |
| |
| private void handleEnumeratedTemplate(BiometricAuthenticator.Identifier identifier) { |
| if (identifier == null) { |
| return; |
| } |
| Slog.v(getTag(), "handleEnumeratedTemplate: " + identifier.getBiometricId()); |
| boolean matched = false; |
| for (int i = 0; i < mEnrolledList.size(); i++) { |
| if (mEnrolledList.get(i).getBiometricId() == identifier.getBiometricId()) { |
| mEnrolledList.remove(i); |
| matched = true; |
| break; |
| } |
| } |
| |
| // TemplateId 0 means no templates in HAL |
| if (!matched && identifier.getBiometricId() != 0) { |
| mUnknownHALTemplates.add(identifier); |
| } |
| Slog.v(getTag(), "Matched: " + matched); |
| } |
| |
| private void doTemplateCleanup() { |
| if (mEnrolledList == null) { |
| return; |
| } |
| |
| // At this point, mEnrolledList only contains templates known to the framework and |
| // not the HAL. |
| for (int i = 0; i < mEnrolledList.size(); i++) { |
| BiometricAuthenticator.Identifier identifier = mEnrolledList.get(i); |
| Slog.e(getTag(), "doTemplateCleanup(): Removing dangling template from framework: " |
| + identifier.getBiometricId() + " " |
| + identifier.getName()); |
| mUtils.removeBiometricForUser(getContext(), |
| getTargetUserId(), identifier.getBiometricId()); |
| FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED, |
| statsModality(), |
| BiometricsProtoEnums.ISSUE_UNKNOWN_TEMPLATE_ENROLLED_FRAMEWORK); |
| } |
| mEnrolledList.clear(); |
| } |
| |
| public List<BiometricAuthenticator.Identifier> getUnknownHALTemplates() { |
| return mUnknownHALTemplates; |
| } |
| |
| @Override |
| public boolean onEnumerationResult(BiometricAuthenticator.Identifier identifier, |
| int remaining) { |
| handleEnumeratedTemplate(identifier); |
| if (remaining == 0) { |
| doTemplateCleanup(); |
| } |
| return remaining == 0; |
| } |
| |
| @Override |
| protected int statsModality() { |
| return BiometricServiceBase.this.statsModality(); |
| } |
| } |
| |
| /** |
| * Wraps the callback interface from Service -> Manager |
| */ |
| protected interface ServiceListener { |
| default void onEnrollResult(BiometricAuthenticator.Identifier identifier, |
| int remaining) throws RemoteException {}; |
| |
| void onAcquired(long deviceId, int acquiredInfo, int vendorCode) throws RemoteException; |
| |
| default void onAuthenticationSucceeded(long deviceId, |
| BiometricAuthenticator.Identifier biometric, int userId) throws RemoteException { |
| throw new UnsupportedOperationException("Stub!"); |
| } |
| |
| default void onAuthenticationSucceededInternal(boolean requireConfirmation, byte[] token) |
| throws RemoteException { |
| throw new UnsupportedOperationException("Stub!"); |
| } |
| |
| default void onAuthenticationFailed(long deviceId) throws RemoteException { |
| throw new UnsupportedOperationException("Stub!"); |
| } |
| |
| default void onAuthenticationFailedInternal() |
| throws RemoteException { |
| throw new UnsupportedOperationException("Stub!"); |
| } |
| |
| void onError(long deviceId, int error, int vendorCode, int cookie) throws RemoteException; |
| |
| default void onRemoved(BiometricAuthenticator.Identifier identifier, |
| int remaining) throws RemoteException {}; |
| |
| default void onEnumerated(BiometricAuthenticator.Identifier identifier, |
| int remaining) throws RemoteException {}; |
| } |
| |
| /** |
| * Wraps the callback interface from Service -> BiometricPrompt |
| */ |
| protected abstract class BiometricServiceListener implements ServiceListener { |
| private IBiometricServiceReceiverInternal mWrapperReceiver; |
| |
| public BiometricServiceListener(IBiometricServiceReceiverInternal wrapperReceiver) { |
| mWrapperReceiver = wrapperReceiver; |
| } |
| |
| public IBiometricServiceReceiverInternal getWrapperReceiver() { |
| return mWrapperReceiver; |
| } |
| |
| @Override |
| public void onAuthenticationSucceededInternal(boolean requireConfirmation, byte[] token) |
| throws RemoteException { |
| if (getWrapperReceiver() != null) { |
| getWrapperReceiver().onAuthenticationSucceeded(requireConfirmation, token); |
| } |
| } |
| |
| @Override |
| public void onAuthenticationFailedInternal() |
| throws RemoteException { |
| if (getWrapperReceiver() != null) { |
| getWrapperReceiver().onAuthenticationFailed(); |
| } |
| } |
| } |
| |
| /** |
| * Wraps a portion of the interface from Service -> Daemon that is used by the ClientMonitor |
| * subclasses. |
| */ |
| protected interface DaemonWrapper { |
| int ERROR_ESRCH = 3; // Likely HAL is dead. see errno.h. |
| int authenticate(long operationId, int groupId, NativeHandle windowId) |
| throws RemoteException; |
| int cancel() throws RemoteException; |
| int remove(int groupId, int biometricId) throws RemoteException; |
| int enumerate() throws RemoteException; |
| int enroll(byte[] token, int groupId, int timeout, |
| ArrayList<Integer> disabledFeatures, NativeHandle windowId) throws RemoteException; |
| void resetLockout(byte[] token) throws RemoteException; |
| } |
| |
| /** |
| * Handler which all subclasses should post events to. |
| */ |
| protected final class H extends Handler { |
| @Override |
| public void handleMessage(android.os.Message msg) { |
| switch (msg.what) { |
| case MSG_USER_SWITCHING: |
| handleUserSwitching(msg.arg1); |
| break; |
| |
| default: |
| Slog.w(getTag(), "Unknown message:" + msg.what); |
| } |
| } |
| } |
| |
| private final Runnable mOnTaskStackChangedRunnable = new Runnable() { |
| @Override |
| public void run() { |
| try { |
| if (!(mCurrentClient instanceof AuthenticationClient)) { |
| return; |
| } |
| final String currentClient = mCurrentClient.getOwnerString(); |
| if (isKeyguard(currentClient)) { |
| return; // Keyguard is always allowed |
| } |
| List<ActivityManager.RunningTaskInfo> runningTasks = |
| mActivityTaskManager.getTasks(1); |
| if (!runningTasks.isEmpty()) { |
| final String topPackage = runningTasks.get(0).topActivity.getPackageName(); |
| if (!topPackage.contentEquals(currentClient) |
| && !mCurrentClient.isAlreadyDone()) { |
| Slog.e(getTag(), "Stopping background authentication, top: " |
| + topPackage + " currentClient: " + currentClient); |
| mCurrentClient.stop(false /* initiatedByClient */); |
| } |
| } |
| } catch (RemoteException e) { |
| Slog.e(getTag(), "Unable to get running tasks", e); |
| } |
| } |
| }; |
| |
| private final class BiometricTaskStackListener extends TaskStackListener { |
| @Override |
| public void onTaskStackChanged() { |
| mHandler.post(mOnTaskStackChangedRunnable); |
| } |
| } |
| |
| private final class ResetClientStateRunnable implements Runnable { |
| @Override |
| public void run() { |
| /** |
| * Warning: if we get here, the driver never confirmed our call to cancel the current |
| * operation (authenticate, enroll, remove, enumerate, etc), which is |
| * really bad. The result will be a 3-second delay in starting each new client. |
| * If you see this on a device, make certain the driver notifies with |
| * {@link BiometricConstants#BIOMETRIC_ERROR_CANCELED} in response to cancel() |
| * once it has successfully switched to the IDLE state in the HAL. |
| * Additionally,{@link BiometricConstants#BIOMETRIC_ERROR_CANCELED} should only be sent |
| * in response to an actual cancel() call. |
| */ |
| Slog.w(getTag(), "Client " |
| + (mCurrentClient != null ? mCurrentClient.getOwnerString() : "null") |
| + " failed to respond to cancel, starting client " |
| + (mPendingClient != null ? mPendingClient.getOwnerString() : "null")); |
| |
| FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED, |
| statsModality(), BiometricsProtoEnums.ISSUE_CANCEL_TIMED_OUT); |
| |
| mCurrentClient = null; |
| startClient(mPendingClient, false); |
| } |
| } |
| |
| |
| |
| private final class LockoutResetMonitor implements IBinder.DeathRecipient { |
| private static final long WAKELOCK_TIMEOUT_MS = 2000; |
| private final IBiometricServiceLockoutResetCallback mCallback; |
| private final PowerManager.WakeLock mWakeLock; |
| |
| public LockoutResetMonitor(IBiometricServiceLockoutResetCallback callback) { |
| mCallback = callback; |
| mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, |
| "lockout reset callback"); |
| try { |
| mCallback.asBinder().linkToDeath(LockoutResetMonitor.this, 0); |
| } catch (RemoteException e) { |
| Slog.w(getTag(), "caught remote exception in linkToDeath", e); |
| } |
| } |
| |
| public void sendLockoutReset() { |
| if (mCallback != null) { |
| try { |
| mWakeLock.acquire(WAKELOCK_TIMEOUT_MS); |
| mCallback.onLockoutReset(getHalDeviceId(), new IRemoteCallback.Stub() { |
| @Override |
| public void sendResult(Bundle data) throws RemoteException { |
| releaseWakelock(); |
| } |
| }); |
| } catch (DeadObjectException e) { |
| Slog.w(getTag(), "Death object while invoking onLockoutReset: ", e); |
| mHandler.post(mRemoveCallbackRunnable); |
| } catch (RemoteException e) { |
| Slog.w(getTag(), "Failed to invoke onLockoutReset: ", e); |
| releaseWakelock(); |
| } |
| } |
| } |
| |
| private final Runnable mRemoveCallbackRunnable = new Runnable() { |
| @Override |
| public void run() { |
| releaseWakelock(); |
| removeLockoutResetCallback(LockoutResetMonitor.this); |
| } |
| }; |
| |
| @Override |
| public void binderDied() { |
| Slog.e(getTag(), "Lockout reset callback binder died"); |
| mHandler.post(mRemoveCallbackRunnable); |
| } |
| |
| private void releaseWakelock() { |
| if (mWakeLock.isHeld()) { |
| mWakeLock.release(); |
| } |
| } |
| } |
| |
| /** |
| * Container for enumerated templates. Used to keep track when cleaning up unknown |
| * templates. |
| */ |
| private final class UserTemplate { |
| final BiometricAuthenticator.Identifier mIdentifier; |
| final int mUserId; |
| UserTemplate(BiometricAuthenticator.Identifier identifier, int userId) { |
| this.mIdentifier = identifier; |
| this.mUserId = userId; |
| } |
| } |
| |
| /** |
| * Initializes the system service. |
| * <p> |
| * Subclasses must define a single argument constructor that accepts the context |
| * and passes it to super. |
| * </p> |
| * |
| * @param context The system server context. |
| */ |
| public BiometricServiceBase(Context context) { |
| super(context); |
| mContext = context; |
| mStatusBarService = IStatusBarService.Stub.asInterface( |
| ServiceManager.getService(Context.STATUS_BAR_SERVICE)); |
| mKeyguardPackage = ComponentName.unflattenFromString(context.getResources().getString( |
| com.android.internal.R.string.config_keyguardComponent)).getPackageName(); |
| mAppOps = context.getSystemService(AppOpsManager.class); |
| mActivityTaskManager = ((ActivityTaskManager) context.getSystemService( |
| Context.ACTIVITY_TASK_SERVICE)).getService(); |
| mPowerManager = mContext.getSystemService(PowerManager.class); |
| mUserManager = UserManager.get(mContext); |
| mMetricsLogger = new MetricsLogger(); |
| } |
| |
| @Override |
| public void onStart() { |
| listenForUserSwitches(); |
| } |
| |
| @Override |
| public void serviceDied(long cookie) { |
| Slog.e(getTag(), "HAL died"); |
| mMetricsLogger.count(getConstants().tagHalDied(), 1); |
| mHALDeathCount++; |
| mCurrentUserId = UserHandle.USER_NULL; |
| |
| // All client lifecycle must be managed on the handler. |
| mHandler.post(() -> { |
| handleError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, |
| 0 /*vendorCode */); |
| }); |
| |
| FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED, |
| statsModality(), BiometricsProtoEnums.ISSUE_HAL_DEATH); |
| } |
| |
| protected ClientMonitor getCurrentClient() { |
| return mCurrentClient; |
| } |
| |
| protected ClientMonitor getPendingClient() { |
| return mPendingClient; |
| } |
| |
| /** |
| * Callback handlers from the daemon. The caller must put this on a handler. |
| */ |
| |
| protected void handleAcquired(long deviceId, int acquiredInfo, int vendorCode) { |
| ClientMonitor client = mCurrentClient; |
| if (client != null && client.onAcquired(acquiredInfo, vendorCode)) { |
| removeClient(client); |
| } |
| if (mPerformanceStats != null && getLockoutMode() == AuthenticationClient.LOCKOUT_NONE |
| && client instanceof AuthenticationClient) { |
| // ignore enrollment acquisitions or acquisitions when we're locked out |
| mPerformanceStats.acquire++; |
| } |
| } |
| |
| protected void handleAuthenticated(BiometricAuthenticator.Identifier identifier, |
| ArrayList<Byte> token) { |
| ClientMonitor client = mCurrentClient; |
| final boolean authenticated = identifier.getBiometricId() != 0; |
| |
| if (client != null && client.onAuthenticated(identifier, authenticated, token)) { |
| removeClient(client); |
| } |
| if (authenticated) { |
| mPerformanceStats.accept++; |
| } else { |
| mPerformanceStats.reject++; |
| } |
| } |
| |
| protected void handleEnrollResult(BiometricAuthenticator.Identifier identifier, |
| int remaining) { |
| ClientMonitor client = mCurrentClient; |
| if (client != null && client.onEnrollResult(identifier, remaining)) { |
| removeClient(client); |
| // When enrollment finishes, update this group's authenticator id, as the HAL has |
| // already generated a new authenticator id when the new biometric is enrolled. |
| if (identifier instanceof Fingerprint) { |
| updateActiveGroup(((Fingerprint)identifier).getGroupId(), null); |
| } |
| } |
| } |
| |
| protected void handleError(long deviceId, int error, int vendorCode) { |
| final ClientMonitor client = mCurrentClient; |
| |
| if (DEBUG) Slog.v(getTag(), "handleError(client=" |
| + (client != null ? client.getOwnerString() : "null") + ", error = " + error + ")"); |
| |
| if (client instanceof InternalRemovalClient |
| || client instanceof InternalEnumerateClient) { |
| clearEnumerateState(); |
| } |
| |
| if (client != null && client.onError(deviceId, error, vendorCode)) { |
| removeClient(client); |
| } |
| |
| if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) { |
| mHandler.removeCallbacks(mResetClientState); |
| if (mPendingClient != null) { |
| if (DEBUG) Slog.v(getTag(), "start pending client " + |
| mPendingClient.getOwnerString()); |
| startClient(mPendingClient, false); |
| mPendingClient = null; |
| } |
| } |
| } |
| |
| protected void handleRemoved(BiometricAuthenticator.Identifier identifier, |
| final int remaining) { |
| if (DEBUG) Slog.w(getTag(), "Removed: fid=" + identifier.getBiometricId() |
| + ", dev=" + identifier.getDeviceId() |
| + ", rem=" + remaining); |
| |
| ClientMonitor client = mCurrentClient; |
| if (client != null && client.onRemoved(identifier, remaining)) { |
| removeClient(client); |
| // When the last biometric of a group is removed, update the authenticator id |
| int userId = mCurrentUserId; |
| if (identifier instanceof Fingerprint) { |
| userId = ((Fingerprint) identifier).getGroupId(); |
| } |
| if (!hasEnrolledBiometrics(userId)) { |
| updateActiveGroup(userId, null); |
| } |
| } |
| |
| if (client instanceof InternalRemovalClient && !mUnknownHALTemplates.isEmpty()) { |
| startCleanupUnknownHALTemplates(); |
| } else if (client instanceof InternalRemovalClient) { |
| clearEnumerateState(); |
| } |
| } |
| |
| protected void handleEnumerate(BiometricAuthenticator.Identifier identifier, int remaining) { |
| ClientMonitor client = getCurrentClient(); |
| |
| client.onEnumerationResult(identifier, remaining); |
| |
| // All templates in the HAL for this user were enumerated |
| if (remaining == 0) { |
| if (client instanceof InternalEnumerateClient) { |
| List<BiometricAuthenticator.Identifier> unknownHALTemplates = |
| ((InternalEnumerateClient) client).getUnknownHALTemplates(); |
| |
| if (!unknownHALTemplates.isEmpty()) { |
| Slog.w(getTag(), "Adding " + unknownHALTemplates.size() |
| + " templates for deletion"); |
| } |
| for (int i = 0; i < unknownHALTemplates.size(); i++) { |
| mUnknownHALTemplates.add(new UserTemplate(unknownHALTemplates.get(i), |
| client.getTargetUserId())); |
| } |
| removeClient(client); |
| startCleanupUnknownHALTemplates(); |
| } else { |
| removeClient(client); |
| } |
| } |
| } |
| |
| /** |
| * Calls from the Manager. These are still on the calling binder's thread. |
| */ |
| |
| protected void enrollInternal(EnrollClientImpl client, int userId) { |
| if (hasReachedEnrollmentLimit(userId)) { |
| return; |
| } |
| |
| // Group ID is arbitrarily set to parent profile user ID. It just represents |
| // the default biometrics for the user. |
| if (!isCurrentUserOrProfile(userId)) { |
| return; |
| } |
| |
| mHandler.post(() -> { |
| startClient(client, true /* initiatedByClient */); |
| }); |
| } |
| |
| protected void cancelEnrollmentInternal(IBinder token) { |
| mHandler.post(() -> { |
| ClientMonitor client = mCurrentClient; |
| if (client instanceof EnrollClient && client.getToken() == token) { |
| if (DEBUG) Slog.v(getTag(), "Cancelling enrollment"); |
| client.stop(client.getToken() == token); |
| } |
| }); |
| } |
| |
| protected void authenticateInternal(AuthenticationClientImpl client, long opId, |
| String opPackageName) { |
| final int callingUid = Binder.getCallingUid(); |
| final int callingPid = Binder.getCallingPid(); |
| final int callingUserId = UserHandle.getCallingUserId(); |
| authenticateInternal(client, opId, opPackageName, callingUid, callingPid, callingUserId); |
| } |
| |
| protected void authenticateInternal(AuthenticationClientImpl client, long opId, |
| String opPackageName, int callingUid, int callingPid, int callingUserId) { |
| if (!canUseBiometric(opPackageName, true /* foregroundOnly */, callingUid, callingPid, |
| callingUserId)) { |
| if (DEBUG) Slog.v(getTag(), "authenticate(): reject " + opPackageName); |
| return; |
| } |
| |
| mHandler.post(() -> { |
| mMetricsLogger.histogram(getConstants().tagAuthToken(), opId != 0L ? 1 : 0); |
| |
| // Get performance stats object for this user. |
| HashMap<Integer, PerformanceStats> pmap |
| = (opId == 0) ? mPerformanceMap : mCryptoPerformanceMap; |
| PerformanceStats stats = pmap.get(mCurrentUserId); |
| if (stats == null) { |
| stats = new PerformanceStats(); |
| pmap.put(mCurrentUserId, stats); |
| } |
| mPerformanceStats = stats; |
| mIsCrypto = (opId != 0); |
| |
| startAuthentication(client, opPackageName); |
| }); |
| } |
| |
| protected void cancelAuthenticationInternal(final IBinder token, final String opPackageName) { |
| final int callingUid = Binder.getCallingUid(); |
| final int callingPid = Binder.getCallingPid(); |
| final int callingUserId = UserHandle.getCallingUserId(); |
| cancelAuthenticationInternal(token, opPackageName, callingUid, callingPid, callingUserId, |
| true /* fromClient */); |
| } |
| |
| protected void cancelAuthenticationInternal(final IBinder token, final String opPackageName, |
| int callingUid, int callingPid, int callingUserId, boolean fromClient) { |
| if (fromClient) { |
| // Only check this if cancel was called from the client (app). If cancel was called |
| // from BiometricService, it means the dialog was dismissed due to user interaction. |
| if (!canUseBiometric(opPackageName, true /* foregroundOnly */, callingUid, callingPid, |
| callingUserId)) { |
| if (DEBUG) Slog.v(getTag(), "cancelAuthentication(): reject " + opPackageName); |
| return; |
| } |
| } |
| |
| mHandler.post(() -> { |
| ClientMonitor client = mCurrentClient; |
| if (client instanceof AuthenticationClient) { |
| if (client.getToken() == token || !fromClient) { |
| if (DEBUG) Slog.v(getTag(), "Stopping client " + client.getOwnerString() |
| + ", fromClient: " + fromClient); |
| // If cancel was from BiometricService, it means the dialog was dismissed |
| // and authentication should be canceled. |
| client.stop(client.getToken() == token); |
| } else { |
| if (DEBUG) Slog.v(getTag(), "Can't stop client " + client.getOwnerString() |
| + " since tokens don't match. fromClient: " + fromClient); |
| } |
| } else if (client != null) { |
| if (DEBUG) Slog.v(getTag(), "Can't cancel non-authenticating client " |
| + client.getOwnerString()); |
| } |
| }); |
| } |
| |
| protected void setActiveUserInternal(int userId) { |
| mHandler.post(() -> { |
| if (DEBUG) { |
| Slog.d(getTag(), "setActiveUser(" + userId + ")"); |
| } |
| updateActiveGroup(userId, null /* clientPackage */); |
| }); |
| } |
| |
| protected void removeInternal(RemovalClient client) { |
| mHandler.post(() -> { |
| startClient(client, true /* initiatedByClient */); |
| }); |
| } |
| |
| protected void enumerateInternal(EnumerateClient client) { |
| mHandler.post(() -> { |
| startClient(client, true /* initiatedByClient */); |
| }); |
| } |
| |
| // Should be done on a handler thread - not on the Binder's thread. |
| private void startAuthentication(AuthenticationClientImpl client, String opPackageName) { |
| if (DEBUG) Slog.v(getTag(), "startAuthentication(" + opPackageName + ")"); |
| |
| int lockoutMode = getLockoutMode(); |
| if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) { |
| Slog.v(getTag(), "In lockout mode(" + lockoutMode + ") ; disallowing authentication"); |
| int errorCode = lockoutMode == AuthenticationClient.LOCKOUT_TIMED ? |
| BiometricConstants.BIOMETRIC_ERROR_LOCKOUT : |
| BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT; |
| if (!client.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */)) { |
| Slog.w(getTag(), "Cannot send permanent lockout message to client"); |
| } |
| return; |
| } |
| startClient(client, true /* initiatedByClient */); |
| } |
| |
| protected void addLockoutResetCallback(IBiometricServiceLockoutResetCallback callback) { |
| mHandler.post(() -> { |
| final LockoutResetMonitor monitor = new LockoutResetMonitor(callback); |
| if (!mLockoutMonitors.contains(monitor)) { |
| mLockoutMonitors.add(monitor); |
| } |
| }); |
| } |
| |
| /** |
| * Helper methods. |
| */ |
| |
| /** |
| * @param opPackageName name of package for caller |
| * @param requireForeground only allow this call while app is in the foreground |
| * @return true if caller can use the biometric API |
| */ |
| protected boolean canUseBiometric(String opPackageName, boolean requireForeground, int uid, |
| int pid, int userId) { |
| checkUseBiometricPermission(); |
| |
| |
| if (Binder.getCallingUid() == Process.SYSTEM_UID) { |
| return true; // System process (BiometricService, etc) is always allowed |
| } |
| if (isKeyguard(opPackageName)) { |
| return true; // Keyguard is always allowed |
| } |
| if (!isCurrentUserOrProfile(userId)) { |
| Slog.w(getTag(), "Rejecting " + opPackageName + "; not a current user or profile"); |
| return false; |
| } |
| if (!checkAppOps(uid, opPackageName)) { |
| Slog.w(getTag(), "Rejecting " + opPackageName + "; permission denied"); |
| return false; |
| } |
| |
| if (requireForeground && !(isForegroundActivity(uid, pid) || isCurrentClient( |
| opPackageName))) { |
| Slog.w(getTag(), "Rejecting " + opPackageName + "; not in foreground"); |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * @param opPackageName package of the caller |
| * @return true if this is the same client currently using the biometric |
| */ |
| private boolean isCurrentClient(String opPackageName) { |
| return mCurrentClient != null && mCurrentClient.getOwnerString().equals(opPackageName); |
| } |
| |
| /** |
| * @return true if this is keyguard package |
| */ |
| private boolean isKeyguard(String clientPackage) { |
| return mKeyguardPackage.equals(clientPackage); |
| } |
| |
| private boolean isForegroundActivity(int uid, int pid) { |
| try { |
| final List<ActivityManager.RunningAppProcessInfo> procs = |
| ActivityManager.getService().getRunningAppProcesses(); |
| if (procs == null) { |
| Slog.e(getTag(), "Processes null, defaulting to true"); |
| return true; |
| } |
| |
| int N = procs.size(); |
| for (int i = 0; i < N; i++) { |
| ActivityManager.RunningAppProcessInfo proc = procs.get(i); |
| if (proc.pid == pid && proc.uid == uid |
| && proc.importance <= IMPORTANCE_FOREGROUND_SERVICE) { |
| return true; |
| } |
| } |
| } catch (RemoteException e) { |
| Slog.w(getTag(), "am.getRunningAppProcesses() failed"); |
| } |
| return false; |
| } |
| |
| /** |
| * Calls the HAL to switch states to the new task. If there's already a current task, |
| * it calls cancel() and sets mPendingClient to begin when the current task finishes |
| * ({@link BiometricConstants#BIOMETRIC_ERROR_CANCELED}). |
| * |
| * @param newClient the new client that wants to connect |
| * @param initiatedByClient true for authenticate, remove and enroll |
| */ |
| private void startClient(ClientMonitor newClient, boolean initiatedByClient) { |
| ClientMonitor currentClient = mCurrentClient; |
| if (currentClient != null) { |
| if (DEBUG) Slog.v(getTag(), "request stop current client " + |
| currentClient.getOwnerString()); |
| // This check only matters for FingerprintService, since enumerate may call back |
| // multiple times. |
| if (currentClient instanceof InternalEnumerateClient |
| || currentClient instanceof InternalRemovalClient) { |
| // This condition means we're currently running internal diagnostics to |
| // remove extra templates in the hardware and/or the software |
| // TODO: design an escape hatch in case client never finishes |
| if (newClient != null) { |
| Slog.w(getTag(), "Internal cleanup in progress but trying to start client " |
| + newClient.getClass().getSuperclass().getSimpleName() |
| + "(" + newClient.getOwnerString() + ")" |
| + ", initiatedByClient = " + initiatedByClient); |
| } |
| } else { |
| currentClient.stop(initiatedByClient); |
| |
| // Only post the reset runnable for non-cleanup clients. Cleanup clients should |
| // never be forcibly stopped since they ensure synchronization between HAL and |
| // framework. Thus, we should instead just start the pending client once cleanup |
| // finishes instead of using the reset runnable. |
| mHandler.removeCallbacks(mResetClientState); |
| mHandler.postDelayed(mResetClientState, CANCEL_TIMEOUT_LIMIT); |
| } |
| mPendingClient = newClient; |
| } else if (newClient != null) { |
| // For BiometricPrompt clients, do not start until |
| // <Biometric>Service#startPreparedClient is called. BiometricService waits until all |
| // modalities are ready before initiating authentication. |
| if (newClient instanceof AuthenticationClient) { |
| AuthenticationClient client = (AuthenticationClient) newClient; |
| if (client.isBiometricPrompt()) { |
| if (DEBUG) Slog.v(getTag(), "Returning cookie: " + client.getCookie()); |
| mCurrentClient = newClient; |
| if (mBiometricService == null) { |
| mBiometricService = IBiometricService.Stub.asInterface( |
| ServiceManager.getService(Context.BIOMETRIC_SERVICE)); |
| } |
| try { |
| mBiometricService.onReadyForAuthentication(client.getCookie(), |
| client.getRequireConfirmation(), client.getTargetUserId()); |
| } catch (RemoteException e) { |
| Slog.e(getTag(), "Remote exception", e); |
| } |
| return; |
| } |
| } |
| |
| // We are not a BiometricPrompt client, start the client immediately |
| mCurrentClient = newClient; |
| startCurrentClient(mCurrentClient.getCookie()); |
| } |
| } |
| |
| protected void startCurrentClient(int cookie) { |
| if (mCurrentClient == null) { |
| Slog.e(getTag(), "Trying to start null client!"); |
| return; |
| } |
| if (DEBUG) Slog.v(getTag(), "starting client " |
| + mCurrentClient.getClass().getSuperclass().getSimpleName() |
| + "(" + mCurrentClient.getOwnerString() + ")" |
| + " targetUserId: " + mCurrentClient.getTargetUserId() |
| + " currentUserId: " + mCurrentUserId |
| + " cookie: " + cookie + "/" + mCurrentClient.getCookie()); |
| if (cookie != mCurrentClient.getCookie()) { |
| Slog.e(getTag(), "Mismatched cookie"); |
| return; |
| } |
| notifyClientActiveCallbacks(true); |
| mCurrentClient.start(); |
| } |
| |
| protected void removeClient(ClientMonitor client) { |
| if (client != null) { |
| client.destroy(); |
| if (client != mCurrentClient && mCurrentClient != null) { |
| Slog.w(getTag(), "Unexpected client: " + client.getOwnerString() + "expected: " |
| + mCurrentClient.getOwnerString()); |
| } |
| } |
| if (mCurrentClient != null) { |
| if (DEBUG) Slog.v(getTag(), "Done with client: " + client.getOwnerString()); |
| mCurrentClient = null; |
| } |
| if (mPendingClient == null) { |
| notifyClientActiveCallbacks(false); |
| } |
| } |
| |
| /** |
| * Populates existing authenticator ids. To be used only during the start of the service. |
| */ |
| protected void loadAuthenticatorIds() { |
| // This operation can be expensive, so keep track of the elapsed time. Might need to move to |
| // background if it takes too long. |
| long t = System.currentTimeMillis(); |
| mAuthenticatorIds.clear(); |
| for (UserInfo user : UserManager.get(getContext()).getUsers(true /* excludeDying */)) { |
| int userId = getUserOrWorkProfileId(null, user.id); |
| if (!mAuthenticatorIds.containsKey(userId)) { |
| updateActiveGroup(userId, null); |
| } |
| } |
| |
| t = System.currentTimeMillis() - t; |
| if (t > 1000) { |
| Slog.w(getTag(), "loadAuthenticatorIds() taking too long: " + t + "ms"); |
| } |
| } |
| |
| /** |
| * @param clientPackage the package of the caller |
| * @return the profile id |
| */ |
| protected int getUserOrWorkProfileId(String clientPackage, int userId) { |
| if (!isKeyguard(clientPackage) && isWorkProfile(userId)) { |
| return userId; |
| } |
| return getEffectiveUserId(userId); |
| } |
| |
| protected boolean isRestricted() { |
| // Only give privileged apps (like Settings) access to biometric info |
| final boolean restricted = !hasPermission(getManageBiometricPermission()); |
| return restricted; |
| } |
| |
| protected boolean hasPermission(String permission) { |
| return getContext().checkCallingOrSelfPermission(permission) |
| == PackageManager.PERMISSION_GRANTED; |
| } |
| |
| protected void checkPermission(String permission) { |
| getContext().enforceCallingOrSelfPermission(permission, |
| "Must have " + permission + " permission."); |
| } |
| |
| protected boolean isCurrentUserOrProfile(int userId) { |
| UserManager um = UserManager.get(mContext); |
| if (um == null) { |
| Slog.e(getTag(), "Unable to acquire UserManager"); |
| return false; |
| } |
| |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| // Allow current user or profiles of the current user... |
| for (int profileId : um.getEnabledProfileIds(ActivityManager.getCurrentUser())) { |
| if (profileId == userId) { |
| return true; |
| } |
| } |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| |
| return false; |
| } |
| |
| /*** |
| * @param opPackageName the name of the calling package |
| * @return authenticator id for the calling user |
| */ |
| protected long getAuthenticatorId(String opPackageName) { |
| if (isKeyguard(opPackageName)) { |
| // If an app tells us it's keyguard, check that it actually is. |
| checkPermission(USE_BIOMETRIC_INTERNAL); |
| } |
| |
| final int userId = getUserOrWorkProfileId(opPackageName, UserHandle.getCallingUserId()); |
| return mAuthenticatorIds.getOrDefault(userId, 0L); |
| } |
| |
| /** |
| * This method should be called upon connection to the daemon, and when user switches. |
| * @param userId |
| */ |
| protected void doTemplateCleanupForUser(int userId) { |
| if (CLEANUP_UNKNOWN_TEMPLATES) { |
| enumerateUser(userId); |
| } |
| } |
| |
| private void clearEnumerateState() { |
| if (DEBUG) Slog.v(getTag(), "clearEnumerateState()"); |
| mUnknownHALTemplates.clear(); |
| } |
| |
| /** |
| * Remove unknown templates from HAL |
| */ |
| private void startCleanupUnknownHALTemplates() { |
| if (!mUnknownHALTemplates.isEmpty()) { |
| UserTemplate template = mUnknownHALTemplates.get(0); |
| mUnknownHALTemplates.remove(template); |
| boolean restricted = !hasPermission(getManageBiometricPermission()); |
| InternalRemovalClient client = new InternalRemovalClient(getContext(), |
| getDaemonWrapper(), mHalDeviceId, mToken, null /* listener */, |
| template.mIdentifier.getBiometricId(), 0 /* groupId */, template.mUserId, |
| restricted, getContext().getPackageName()); |
| removeInternal(client); |
| FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED, |
| statsModality(), |
| BiometricsProtoEnums.ISSUE_UNKNOWN_TEMPLATE_ENROLLED_HAL); |
| } else { |
| clearEnumerateState(); |
| if (mPendingClient != null) { |
| Slog.d(getTag(), "Enumerate finished, starting pending client"); |
| startClient(mPendingClient, false /* initiatedByClient */); |
| mPendingClient = null; |
| } |
| } |
| } |
| |
| private void enumerateUser(int userId) { |
| if (DEBUG) Slog.v(getTag(), "Enumerating user(" + userId + ")"); |
| |
| final boolean restricted = !hasPermission(getManageBiometricPermission()); |
| final List<? extends BiometricAuthenticator.Identifier> enrolledList = |
| getEnrolledTemplates(userId); |
| |
| InternalEnumerateClient client = new InternalEnumerateClient(getContext(), |
| getDaemonWrapper(), mHalDeviceId, mToken, null /* serviceListener */, userId, |
| userId, restricted, getContext().getOpPackageName(), enrolledList, |
| getBiometricUtils()); |
| enumerateInternal(client); |
| } |
| |
| /** |
| * This method is called when the user switches. Implementations should probably notify the |
| * HAL. |
| */ |
| protected void handleUserSwitching(int userId) { |
| if (getCurrentClient() instanceof InternalRemovalClient |
| || getCurrentClient() instanceof InternalEnumerateClient) { |
| Slog.w(getTag(), "User switched while performing cleanup"); |
| } |
| updateActiveGroup(userId, null); |
| doTemplateCleanupForUser(userId); |
| } |
| |
| protected void notifyLockoutResetMonitors() { |
| for (int i = 0; i < mLockoutMonitors.size(); i++) { |
| mLockoutMonitors.get(i).sendLockoutReset(); |
| } |
| } |
| |
| private void userActivity() { |
| long now = SystemClock.uptimeMillis(); |
| mPowerManager.userActivity(now, PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0); |
| } |
| |
| /** |
| * @param userId |
| * @return true if this is a work profile |
| */ |
| private boolean isWorkProfile(int userId) { |
| UserInfo userInfo = null; |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| userInfo = mUserManager.getUserInfo(userId); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| return userInfo != null && userInfo.isManagedProfile(); |
| } |
| |
| |
| private int getEffectiveUserId(int userId) { |
| UserManager um = UserManager.get(mContext); |
| if (um != null) { |
| final long callingIdentity = Binder.clearCallingIdentity(); |
| userId = um.getCredentialOwnerProfile(userId); |
| Binder.restoreCallingIdentity(callingIdentity); |
| } else { |
| Slog.e(getTag(), "Unable to acquire UserManager"); |
| } |
| return userId; |
| } |
| |
| |
| private void listenForUserSwitches() { |
| try { |
| ActivityManager.getService().registerUserSwitchObserver( |
| new SynchronousUserSwitchObserver() { |
| @Override |
| public void onUserSwitching(int newUserId) throws RemoteException { |
| mHandler.obtainMessage(MSG_USER_SWITCHING, newUserId, 0 /* unused */) |
| .sendToTarget(); |
| } |
| }, getTag()); |
| } catch (RemoteException e) { |
| Slog.w(getTag(), "Failed to listen for user switching event" ,e); |
| } |
| } |
| |
| private void removeLockoutResetCallback( |
| LockoutResetMonitor monitor) { |
| mLockoutMonitors.remove(monitor); |
| } |
| } |