| /* |
| * 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.common; |
| |
| 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.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 implements IBinder.DeathRecipient { |
| protected static final int ERROR_ESRCH = 3; // Likely HAL is dead. See errno.h. |
| protected static final boolean DEBUG = BiometricService.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 BiometricService.DaemonWrapper mDaemon; |
| |
| private IBinder mToken; |
| private BiometricService.ServiceListener mListener; |
| |
| protected final MetricsLogger mMetricsLogger; |
| protected final Metrics mMetrics; |
| |
| protected boolean mAlreadyCancelled; |
| |
| /** |
| * @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, Metrics metrics, BiometricService.DaemonWrapper daemon, |
| long halDeviceId, IBinder token, BiometricService.ServiceListener listener, int userId, |
| int groupId, boolean restricted, String owner) { |
| mContext = context; |
| mMetrics = metrics; |
| mDaemon = daemon; |
| mHalDeviceId = halDeviceId; |
| mToken = token; |
| mListener = listener; |
| mTargetUserId = userId; |
| mGroupId = groupId; |
| mIsRestricted = restricted; |
| mOwner = owner; |
| 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 mMetrics.logTag(); |
| } |
| |
| /** |
| * 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(int biometricId, int groupId); |
| public abstract boolean onRemoved(BiometricAuthenticator.Identifier identifier, |
| int remaining); |
| public abstract boolean onEnumerationResult( |
| BiometricAuthenticator.Identifier identifier, int remaining); |
| |
| /** |
| * 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) { |
| try { |
| if (mListener != null) { |
| 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) { |
| try { |
| if (mListener != null) { |
| mListener.onError(deviceId, error, vendorCode); |
| } |
| } 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() { |
| mToken = null; |
| mListener = null; |
| onError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, |
| 0 /* vendorCode */); |
| } |
| |
| @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 BiometricService.ServiceListener getListener() { |
| return mListener; |
| } |
| |
| public final BiometricService.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); |
| } |
| } |
| } |