blob: b4c82f2ed799041a93269c505ec6ed7cfcd4923a [file] [log] [blame]
/*
* Copyright (C) 2020 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.sensors;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.util.Slog;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.biometrics.Utils;
/**
* Abstract class that adds logging functionality to the ClientMonitor classes.
*/
public abstract class LoggableMonitor {
public static final String TAG = "Biometrics/LoggableMonitor";
public static final boolean DEBUG = false;
final int mStatsModality;
private final int mStatsAction;
private final int mStatsClient;
@NonNull private final SensorManager mSensorManager;
private long mFirstAcquireTimeMs;
private boolean mLightSensorEnabled = false;
private boolean mShouldLogMetrics = true;
/**
* Probe for loggable attributes that can be continuously monitored, such as ambient light.
*
* Disable probes when the sensors are in states that are not interesting for monitoring
* purposes to save power.
*/
protected interface Probe {
/** Ensure the probe is actively sampling for new data. */
void enable();
/** Stop sampling data. */
void disable();
}
/**
* Client monitor callback that exposes a probe.
*
* Disables the probe when the operation completes.
*/
protected static class CallbackWithProbe<T extends Probe>
implements BaseClientMonitor.Callback {
private final boolean mStartWithClient;
private final T mProbe;
public CallbackWithProbe(@NonNull T probe, boolean startWithClient) {
mProbe = probe;
mStartWithClient = startWithClient;
}
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
if (mStartWithClient) {
mProbe.enable();
}
}
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
mProbe.disable();
}
@NonNull
public T getProbe() {
return mProbe;
}
}
private class ALSProbe implements Probe {
@Override
public void enable() {
setLightSensorLoggingEnabled(getAmbientLightSensor(mSensorManager));
}
@Override
public void disable() {
setLightSensorLoggingEnabled(null);
}
}
// report only the most recent value
// consider com.android.server.display.utils.AmbientFilter or similar if need arises
private volatile float mLastAmbientLux = 0;
private final SensorEventListener mLightSensorListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
mLastAmbientLux = event.values[0];
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// Not used.
}
};
/**
* @param context system_server context
* @param statsModality One of {@link BiometricsProtoEnums} MODALITY_* constants.
* @param statsAction One of {@link BiometricsProtoEnums} ACTION_* constants.
* @param statsClient One of {@link BiometricsProtoEnums} CLIENT_* constants.
*/
public LoggableMonitor(@NonNull Context context, int statsModality, int statsAction,
int statsClient) {
mStatsModality = statsModality;
mStatsAction = statsAction;
mStatsClient = statsClient;
mSensorManager = context.getSystemService(SensorManager.class);
}
/**
* Only valid for AuthenticationClient.
* @return true if the client is authenticating for a crypto operation.
*/
protected boolean isCryptoOperation() {
return false;
}
protected void setShouldLog(boolean shouldLog) {
mShouldLogMetrics = shouldLog;
}
public int getStatsClient() {
return mStatsClient;
}
private boolean shouldSkipLogging() {
boolean shouldSkipLogging = (mStatsModality == BiometricsProtoEnums.MODALITY_UNKNOWN
|| mStatsAction == BiometricsProtoEnums.ACTION_UNKNOWN);
if (mStatsModality == BiometricsProtoEnums.MODALITY_UNKNOWN) {
Slog.w(TAG, "Unknown field detected: MODALITY_UNKNOWN, will not report metric");
}
if (mStatsAction == BiometricsProtoEnums.ACTION_UNKNOWN) {
Slog.w(TAG, "Unknown field detected: ACTION_UNKNOWN, will not report metric");
}
if (mStatsClient == BiometricsProtoEnums.CLIENT_UNKNOWN) {
Slog.w(TAG, "Unknown field detected: CLIENT_UNKNOWN");
}
return shouldSkipLogging;
}
protected final void logOnAcquired(Context context, int acquiredInfo, int vendorCode,
int targetUserId) {
if (!mShouldLogMetrics) {
return;
}
final boolean isFace = mStatsModality == BiometricsProtoEnums.MODALITY_FACE;
final boolean isFingerprint = mStatsModality == BiometricsProtoEnums.MODALITY_FINGERPRINT;
if (isFace || isFingerprint) {
if ((isFingerprint && acquiredInfo == FingerprintManager.FINGERPRINT_ACQUIRED_START)
|| (isFace && acquiredInfo == FaceManager.FACE_ACQUIRED_START)) {
mFirstAcquireTimeMs = System.currentTimeMillis();
}
} else if (acquiredInfo == BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) {
if (mFirstAcquireTimeMs == 0) {
mFirstAcquireTimeMs = System.currentTimeMillis();
}
}
if (DEBUG) {
Slog.v(TAG, "Acquired! Modality: " + mStatsModality
+ ", User: " + targetUserId
+ ", IsCrypto: " + isCryptoOperation()
+ ", Action: " + mStatsAction
+ ", Client: " + mStatsClient
+ ", AcquiredInfo: " + acquiredInfo
+ ", VendorCode: " + vendorCode);
}
if (shouldSkipLogging()) {
return;
}
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ACQUIRED,
mStatsModality,
targetUserId,
isCryptoOperation(),
mStatsAction,
mStatsClient,
acquiredInfo,
vendorCode,
Utils.isDebugEnabled(context, targetUserId),
-1 /* sensorId */);
}
protected final void logOnError(Context context, int error, int vendorCode, int targetUserId) {
if (!mShouldLogMetrics) {
return;
}
final long latency = mFirstAcquireTimeMs != 0
? (System.currentTimeMillis() - mFirstAcquireTimeMs) : -1;
if (DEBUG) {
Slog.v(TAG, "Error! Modality: " + mStatsModality
+ ", User: " + targetUserId
+ ", IsCrypto: " + isCryptoOperation()
+ ", Action: " + mStatsAction
+ ", Client: " + mStatsClient
+ ", Error: " + error
+ ", VendorCode: " + vendorCode
+ ", Latency: " + latency);
} else {
Slog.v(TAG, "Error latency: " + latency);
}
if (shouldSkipLogging()) {
return;
}
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED,
mStatsModality,
targetUserId,
isCryptoOperation(),
mStatsAction,
mStatsClient,
error,
vendorCode,
Utils.isDebugEnabled(context, targetUserId),
sanitizeLatency(latency),
-1 /* sensorId */);
}
protected final void logOnAuthenticated(Context context, boolean authenticated,
boolean requireConfirmation, int targetUserId, boolean isBiometricPrompt) {
if (!mShouldLogMetrics) {
return;
}
int authState = FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__UNKNOWN;
if (!authenticated) {
authState = FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__REJECTED;
} else {
// Authenticated
if (isBiometricPrompt && requireConfirmation) {
authState = FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__PENDING_CONFIRMATION;
} else {
authState = FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED;
}
}
// Only valid if we have a first acquired time, otherwise set to -1
final long latency = mFirstAcquireTimeMs != 0
? (System.currentTimeMillis() - mFirstAcquireTimeMs)
: -1;
if (DEBUG) {
Slog.v(TAG, "Authenticated! Modality: " + mStatsModality
+ ", User: " + targetUserId
+ ", IsCrypto: " + isCryptoOperation()
+ ", Client: " + mStatsClient
+ ", RequireConfirmation: " + requireConfirmation
+ ", State: " + authState
+ ", Latency: " + latency
+ ", Lux: " + mLastAmbientLux);
} else {
Slog.v(TAG, "Authentication latency: " + latency);
}
if (shouldSkipLogging()) {
return;
}
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED,
mStatsModality,
targetUserId,
isCryptoOperation(),
mStatsClient,
requireConfirmation,
authState,
sanitizeLatency(latency),
Utils.isDebugEnabled(context, targetUserId),
-1 /* sensorId */,
mLastAmbientLux /* ambientLightLux */);
}
protected final void logOnEnrolled(int targetUserId, long latency, boolean enrollSuccessful) {
if (!mShouldLogMetrics) {
return;
}
if (DEBUG) {
Slog.v(TAG, "Enrolled! Modality: " + mStatsModality
+ ", User: " + targetUserId
+ ", Client: " + mStatsClient
+ ", Latency: " + latency
+ ", Lux: " + mLastAmbientLux
+ ", Success: " + enrollSuccessful);
} else {
Slog.v(TAG, "Enroll latency: " + latency);
}
if (shouldSkipLogging()) {
return;
}
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ENROLLED,
mStatsModality,
targetUserId,
sanitizeLatency(latency),
enrollSuccessful,
-1, /* sensorId */
mLastAmbientLux /* ambientLightLux */);
}
private long sanitizeLatency(long latency) {
if (latency < 0) {
Slog.w(TAG, "found a negative latency : " + latency);
return -1;
}
return latency;
}
/**
* Get a callback to start/stop ALS capture when client runs.
*
* If the probe should not run for the entire operation, do not set startWithClient and
* start/stop the problem when needed.
*
* @param startWithClient if probe should start automatically when the operation starts.
*/
@NonNull
protected CallbackWithProbe<Probe> createALSCallback(boolean startWithClient) {
return new CallbackWithProbe<>(new ALSProbe(), startWithClient);
}
/** The sensor to use for ALS logging. */
@Nullable
protected Sensor getAmbientLightSensor(@NonNull SensorManager sensorManager) {
return mShouldLogMetrics ? sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT) : null;
}
private void setLightSensorLoggingEnabled(@Nullable Sensor lightSensor) {
if (DEBUG) {
Slog.v(TAG, "capturing ambient light using: "
+ (lightSensor != null ? lightSensor : "[disabled]"));
}
if (lightSensor != null) {
if (!mLightSensorEnabled) {
mLightSensorEnabled = true;
mLastAmbientLux = 0;
mSensorManager.registerListener(mLightSensorListener, lightSensor,
SensorManager.SENSOR_DELAY_NORMAL);
}
} else {
mLightSensorEnabled = false;
mLastAmbientLux = 0;
mSensorManager.unregisterListener(mLightSensorListener);
}
}
}