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 android.content.Context;
  import android.hardware.biometrics.BiometricAuthenticator;
  import android.hardware.biometrics.BiometricConstants;
  import android.media.AudioAttributes;
  import android.os.IBinder;
  import android.os.RemoteException;
  import android.os.VibrationEffect;
  import android.os.Vibrator;
  import android.util.Slog;
  
  import com.android.internal.logging.MetricsLogger;
  
  import java.util.ArrayList;
  import java.util.NoSuchElementException;
  
  /**
   * Abstract base class for keeping track and dispatching events from the biometric's HAL to the
   * the current client.  Subclasses are responsible for coordinating the interaction with
   * the biometric's HAL for the specific action (e.g. authenticate, enroll, enumerate, etc.).
   */
  public abstract class ClientMonitor extends LoggableMonitor implements IBinder.DeathRecipient {
      protected static final int ERROR_ESRCH = 3; // Likely HAL is dead. See errno.h.
      protected static final boolean DEBUG = BiometricServiceBase.DEBUG;
      private static final AudioAttributes FINGERPRINT_SONFICATION_ATTRIBUTES =
              new AudioAttributes.Builder()
                      .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                      .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
                      .build();
  
      private final Context mContext;
      private final long mHalDeviceId;
      private final int mTargetUserId;
      private final int mGroupId;
      // True if client does not have MANAGE_FINGERPRINT permission
      private final boolean mIsRestricted;
      private final String mOwner;
      private final VibrationEffect mSuccessVibrationEffect;
      private final VibrationEffect mErrorVibrationEffect;
      private final BiometricServiceBase.DaemonWrapper mDaemon;
  
      private IBinder mToken;
      private BiometricServiceBase.ServiceListener mListener;
      // Currently only used for authentication client. The cookie generated by BiometricService
      // is never 0.
      private final int mCookie;
  
      protected final MetricsLogger mMetricsLogger;
      protected final Constants mConstants;
  
      protected boolean mAlreadyCancelled;
      protected boolean mAlreadyDone;
  
      /**
       * @param context context of BiometricService
       * @param daemon interface to call back to a specific biometric's daemon
       * @param halDeviceId the HAL device ID of the associated biometric hardware
       * @param token a unique token for the client
       * @param listener recipient of related events (e.g. authentication)
       * @param userId target user id for operation
       * @param groupId groupId for the fingerprint set
       * @param restricted whether or not client has the MANAGE_* permission
       * permission
       * @param owner name of the client that owns this
       */
      public ClientMonitor(Context context, Constants constants,
              BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token,
              BiometricServiceBase.ServiceListener listener, int userId, int groupId,
              boolean restricted, String owner, int cookie) {
          mContext = context;
          mConstants = constants;
          mDaemon = daemon;
          mHalDeviceId = halDeviceId;
          mToken = token;
          mListener = listener;
          mTargetUserId = userId;
          mGroupId = groupId;
          mIsRestricted = restricted;
          mOwner = owner;
          mCookie = cookie;
          mSuccessVibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
          mErrorVibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
          mMetricsLogger = new MetricsLogger();
          try {
              if (token != null) {
                  token.linkToDeath(this, 0);
              }
          } catch (RemoteException e) {
              Slog.w(getLogTag(), "caught remote exception in linkToDeath: ", e);
          }
      }
  
      protected String getLogTag() {
          return mConstants.logTag();
      }
  
      public int getCookie() {
          return mCookie;
      }
  
      /**
       * Contacts the biometric's HAL to start the client.
       * @return 0 on success, errno from driver on failure
       */
      public abstract int start();
  
      /**
       * Contacts the biometric's HAL to stop the client.
       * @param initiatedByClient whether the operation is at the request of a client
       */
      public abstract int stop(boolean initiatedByClient);
  
      /**
       * Method to explicitly poke powermanager on events
       */
      public abstract void notifyUserActivity();
  
      // Event callbacks from driver. Inappropriate calls is flagged/logged by the
      // respective client (e.g. enrolling shouldn't get authenticate events).
      // All of these return 'true' if the operation is completed and it's ok to move
      // to the next client (e.g. authentication accepts or rejects a biometric).
      public abstract boolean onEnrollResult(BiometricAuthenticator.Identifier identifier,
              int remaining);
      public abstract boolean onAuthenticated(BiometricAuthenticator.Identifier identifier,
              boolean authenticated, ArrayList<Byte> token);
      public abstract boolean onRemoved(BiometricAuthenticator.Identifier identifier,
              int remaining);
      public abstract boolean onEnumerationResult(
              BiometricAuthenticator.Identifier identifier, int remaining);
  
      public int[] getAcquireIgnorelist() {
          return new int[0];
      }
      public int[] getAcquireVendorIgnorelist() {
          return new int[0];
      }
  
      private boolean blacklistContains(int acquiredInfo, int vendorCode) {
          if (acquiredInfo == mConstants.acquireVendorCode()) {
              for (int i = 0; i < getAcquireVendorIgnorelist().length; i++) {
                  if (getAcquireVendorIgnorelist()[i] == vendorCode) {
                      if (DEBUG) Slog.v(getLogTag(), "Ignoring vendor message: " + vendorCode);
                      return true;
                  }
              }
          } else {
              for (int i = 0; i < getAcquireIgnorelist().length; i++) {
                  if (getAcquireIgnorelist()[i] == acquiredInfo) {
                      if (DEBUG) Slog.v(getLogTag(), "Ignoring message: " + acquiredInfo);
                      return true;
                  }
              }
          }
          return false;
      }
  
      public boolean isAlreadyDone() {
          return mAlreadyDone;
      }
  
      /**
       * Called when we get notification from the biometric's HAL that an image has been acquired.
       * Common to authenticate and enroll.
       * @param acquiredInfo info about the current image acquisition
       * @return true if client should be removed
       */
      public boolean onAcquired(int acquiredInfo, int vendorCode) {
          super.logOnAcquired(mContext, acquiredInfo, vendorCode, getTargetUserId());
          if (DEBUG) Slog.v(getLogTag(), "Acquired: " + acquiredInfo + " " + vendorCode);
          try {
              if (mListener != null && !blacklistContains(acquiredInfo, vendorCode)) {
                  mListener.onAcquired(getHalDeviceId(), acquiredInfo, vendorCode);
              }
              return false; // acquisition continues...
          } catch (RemoteException e) {
              Slog.w(getLogTag(), "Failed to invoke sendAcquired", e);
              return true;
          } finally {
              // Good scans will keep the device awake
              if (acquiredInfo == BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) {
                  notifyUserActivity();
              }
          }
      }
  
      /**
       * Called when we get notification from the biometric's HAL that an error has occurred with the
       * current operation. Common to authenticate, enroll, enumerate and remove.
       * @param error
       * @return true if client should be removed
       */
      public boolean onError(long deviceId, int error, int vendorCode) {
          super.logOnError(mContext, error, vendorCode, getTargetUserId());
          try {
              if (mListener != null) {
                  mListener.onError(deviceId, error, vendorCode, getCookie());
              }
          } catch (RemoteException e) {
              Slog.w(getLogTag(), "Failed to invoke sendError", e);
          }
          return true; // errors always remove current client
      }
  
      public void destroy() {
          if (mToken != null) {
              try {
                  mToken.unlinkToDeath(this, 0);
              } catch (NoSuchElementException e) {
                  // TODO: remove when duplicate call bug is found
                  Slog.e(getLogTag(), "destroy(): " + this + ":", new Exception("here"));
              }
              mToken = null;
          }
          mListener = null;
      }
  
      @Override
      public void binderDied() {
          // If the current client dies we should cancel the current operation.
          Slog.e(getLogTag(), "Binder died, cancelling client");
          stop(false /* initiatedByClient */);
          mToken = null;
          mListener = null;
      }
  
      @Override
      protected void finalize() throws Throwable {
          try {
              if (mToken != null) {
                  if (DEBUG) Slog.w(getLogTag(), "removing leaked reference: " + mToken);
                  onError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
                          0 /* vendorCode */);
              }
          } finally {
              super.finalize();
          }
      }
  
      public final Context getContext() {
          return mContext;
      }
  
      public final long getHalDeviceId() {
          return mHalDeviceId;
      }
  
      public final String getOwnerString() {
          return mOwner;
      }
  
      public final BiometricServiceBase.ServiceListener getListener() {
          return mListener;
      }
  
      public final BiometricServiceBase.DaemonWrapper getDaemonWrapper() {
          return mDaemon;
      }
  
      public final boolean getIsRestricted() {
          return mIsRestricted;
      }
  
      public final int getTargetUserId() {
          return mTargetUserId;
      }
  
      public final int getGroupId() {
          return mGroupId;
      }
  
      public final IBinder getToken() {
          return mToken;
      }
  
      public final void vibrateSuccess() {
          Vibrator vibrator = mContext.getSystemService(Vibrator.class);
          if (vibrator != null) {
              vibrator.vibrate(mSuccessVibrationEffect, FINGERPRINT_SONFICATION_ATTRIBUTES);
          }
      }
  
      public final void vibrateError() {
          Vibrator vibrator = mContext.getSystemService(Vibrator.class);
          if (vibrator != null) {
              vibrator.vibrate(mErrorVibrationEffect, FINGERPRINT_SONFICATION_ATTRIBUTES);
          }
      }
  }
  