/*
|
* 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);
|
}
|
}
|
}
|