content: /*
   * 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.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.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.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 android.util.StatsLog;
  
  import com.android.internal.logging.MetricsLogger;
  import com.android.internal.statusbar.IStatusBarService;
  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) {
              super(context, getConstants(), daemon, halDeviceId, token, listener, targetUserId,
                      groupId, opId, restricted, owner, cookie, requireConfirmation);
          }
  
          @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) {
              super(context, getConstants(), daemon, halDeviceId, token, listener,
                      userId, groupId, cryptoToken, restricted, owner, getBiometricUtils(),
                      disabledFeatures, timeoutSec);
          }
  
          @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());
                  StatsLog.write(StatsLog.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(int cookie, boolean requireConfirmation)
                  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(int cookie, boolean requireConfirmation)
                  throws RemoteException {
              if (getWrapperReceiver() != null) {
                  getWrapperReceiver().onAuthenticationFailed(cookie, requireConfirmation);
              }
          }
      }
  
      /**
       * 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) 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) 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"));
  
              StatsLog.write(StatsLog.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 */);
          });
  
          StatsLog.write(StatsLog.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 {
              List<ActivityManager.RunningAppProcessInfo> procs =
                      ActivityManager.getService().getRunningAppProcesses();
              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) {
          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);
              StatsLog.write(StatsLog.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);
      }
  }
  