1/n: Add ClientMonitors for authenticatorId invalidation
InvalidationRequesterClient (for requesting other clients to invalidate) will be fully implemented in subsequent CL's. InvalidationClient (for requesting HALs to invalidate authenticatorId) should be complete at this point. Also updates BiometricUtils to persist a "invalidationInProgress" flag on-disk for each <sensorId, userId> pair. Bug: 159667191 Test: No effect on existing devices Test: Existing enrollments not affected Change-Id: I9eeb802b556ba86568cf41b456a4ceab4b896ef0
This commit is contained in:
@@ -56,9 +56,9 @@ public abstract class AcquisitionClient<T> extends ClientMonitor<T> implements I
|
||||
public AcquisitionClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
|
||||
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
|
||||
@NonNull String owner, int cookie, int sensorId, int statsModality,
|
||||
int statsAction, int statsClient, boolean shouldLogMetrics) {
|
||||
int statsAction, int statsClient) {
|
||||
super(context, lazyDaemon, token, listener, userId, owner, cookie, sensorId, statsModality,
|
||||
statsAction, statsClient, shouldLogMetrics);
|
||||
statsAction, statsClient);
|
||||
mPowerManager = context.getSystemService(PowerManager.class);
|
||||
mSuccessVibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
|
||||
mErrorVibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
|
||||
|
||||
@@ -66,8 +66,7 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
|
||||
int statsModality, int statsClient, @Nullable TaskStackListener taskStackListener,
|
||||
@NonNull LockoutTracker lockoutTracker) {
|
||||
super(context, lazyDaemon, token, listener, targetUserId, owner, cookie, sensorId,
|
||||
statsModality, BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
|
||||
true /* shouldLogMetrics */);
|
||||
statsModality, BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
|
||||
mIsStrongBiometric = isStrongBiometric;
|
||||
mOperationId = operationId;
|
||||
mRequireConfirmation = requireConfirmation;
|
||||
|
||||
@@ -21,8 +21,10 @@ import android.content.Context;
|
||||
import android.hardware.biometrics.BiometricAuthenticator;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Environment;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.Slog;
|
||||
import android.util.TypedXmlPullParser;
|
||||
import android.util.TypedXmlSerializer;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
@@ -35,6 +37,7 @@ import org.xmlpull.v1.XmlPullParserException;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -46,17 +49,16 @@ import java.util.List;
|
||||
public abstract class BiometricUserState<T extends BiometricAuthenticator.Identifier> {
|
||||
private static final String TAG = "UserState";
|
||||
|
||||
private static final String TAG_INVALIDATION = "authenticatorIdInvalidation_tag";
|
||||
private static final String ATTR_INVALIDATION = "authenticatorIdInvalidation_attr";
|
||||
|
||||
@GuardedBy("this")
|
||||
protected final ArrayList<T> mBiometrics = new ArrayList<>();
|
||||
protected boolean mInvalidationInProgress;
|
||||
protected final Context mContext;
|
||||
protected final File mFile;
|
||||
|
||||
private final Runnable mWriteStateRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
doWriteState();
|
||||
}
|
||||
};
|
||||
private final Runnable mWriteStateRunnable = this::doWriteStateInternal;
|
||||
|
||||
/**
|
||||
* @return The tag for the biometrics. There may be multiple instances of a biometric within.
|
||||
@@ -73,10 +75,40 @@ public abstract class BiometricUserState<T extends BiometricAuthenticator.Identi
|
||||
*/
|
||||
protected abstract ArrayList<T> getCopy(ArrayList<T> array);
|
||||
|
||||
protected abstract void doWriteState(@NonNull TypedXmlSerializer serializer) throws Exception;
|
||||
|
||||
/**
|
||||
* @return Writes the cached data to persistent storage.
|
||||
* @Writes the cached data to persistent storage.
|
||||
*/
|
||||
protected abstract void doWriteState();
|
||||
private void doWriteStateInternal() {
|
||||
AtomicFile destination = new AtomicFile(mFile);
|
||||
|
||||
FileOutputStream out = null;
|
||||
|
||||
try {
|
||||
out = destination.startWrite();
|
||||
TypedXmlSerializer serializer = Xml.resolveSerializer(out);
|
||||
serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
|
||||
serializer.startDocument(null, true);
|
||||
|
||||
// Store the authenticatorId
|
||||
serializer.startTag(null, TAG_INVALIDATION);
|
||||
serializer.attributeBoolean(null, ATTR_INVALIDATION, mInvalidationInProgress);
|
||||
serializer.endTag(null, TAG_INVALIDATION);
|
||||
|
||||
// Do any additional serialization that subclasses may require
|
||||
doWriteState(serializer);
|
||||
|
||||
serializer.endDocument();
|
||||
destination.finishWrite(out);
|
||||
} catch (Throwable t) {
|
||||
Slog.wtf(TAG, "Failed to write settings, restoring backup", t);
|
||||
destination.failWrite(out);
|
||||
throw new IllegalStateException("Failed to write to file: " + mFile.toString(), t);
|
||||
} finally {
|
||||
IoUtils.closeQuietly(out);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
@@ -93,6 +125,19 @@ public abstract class BiometricUserState<T extends BiometricAuthenticator.Identi
|
||||
}
|
||||
}
|
||||
|
||||
public void setInvalidationInProgress(boolean invalidationInProgress) {
|
||||
synchronized (this) {
|
||||
mInvalidationInProgress = invalidationInProgress;
|
||||
scheduleWriteStateLocked();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInvalidationInProgress() {
|
||||
synchronized (this) {
|
||||
return mInvalidationInProgress;
|
||||
}
|
||||
}
|
||||
|
||||
public void addBiometric(T identifier) {
|
||||
synchronized (this) {
|
||||
mBiometrics.add(identifier);
|
||||
@@ -202,6 +247,8 @@ public abstract class BiometricUserState<T extends BiometricAuthenticator.Identi
|
||||
String tagName = parser.getName();
|
||||
if (tagName.equals(getBiometricsTag())) {
|
||||
parseBiometricsLocked(parser);
|
||||
} else if (tagName.equals(TAG_INVALIDATION)) {
|
||||
mInvalidationInProgress = parser.getAttributeBoolean(null, ATTR_INVALIDATION);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,4 +31,6 @@ public interface BiometricUtils<T extends BiometricAuthenticator.Identifier> {
|
||||
void removeBiometricForUser(Context context, int userId, int biometricId);
|
||||
void renameBiometricForUser(Context context, int userId, int biometricId, CharSequence name);
|
||||
CharSequence getUniqueName(Context context, int userId);
|
||||
void setInvalidationInProgress(Context context, int userId, boolean inProgress);
|
||||
boolean isInvalidationInProgress(Context context, int userId);
|
||||
}
|
||||
@@ -105,8 +105,8 @@ public abstract class ClientMonitor<T> extends LoggableMonitor implements IBinde
|
||||
public ClientMonitor(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
|
||||
@Nullable IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
|
||||
@NonNull String owner, int cookie, int sensorId, int statsModality, int statsAction,
|
||||
int statsClient, boolean shouldLogMetrics) {
|
||||
super(statsModality, statsAction, statsClient, shouldLogMetrics);
|
||||
int statsClient) {
|
||||
super(statsModality, statsAction, statsClient);
|
||||
mSequentialId = sCount++;
|
||||
mContext = context;
|
||||
mLazyDaemon = lazyDaemon;
|
||||
|
||||
@@ -49,10 +49,10 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> {
|
||||
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
|
||||
@NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils utils,
|
||||
int timeoutSec, int statsModality, int sensorId,
|
||||
boolean shouldVibrate, boolean shouldLogMetrics) {
|
||||
boolean shouldVibrate) {
|
||||
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
|
||||
statsModality, BiometricsProtoEnums.ACTION_ENROLL,
|
||||
BiometricsProtoEnums.CLIENT_UNKNOWN, shouldLogMetrics);
|
||||
BiometricsProtoEnums.CLIENT_UNKNOWN);
|
||||
mBiometricUtils = utils;
|
||||
mHardwareAuthToken = Arrays.copyOf(hardwareAuthToken, hardwareAuthToken.length);
|
||||
mTimeoutSec = timeoutSec;
|
||||
|
||||
@@ -32,7 +32,7 @@ public abstract class GenerateChallengeClient<T> extends ClientMonitor<T> {
|
||||
@NonNull String owner, int sensorId) {
|
||||
super(context, lazyDaemon, token, listener, 0 /* userId */, owner, 0 /* cookie */, sensorId,
|
||||
BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
|
||||
BiometricsProtoEnums.CLIENT_UNKNOWN, true /* shouldLogMetrics */);
|
||||
BiometricsProtoEnums.CLIENT_UNKNOWN);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -107,8 +107,7 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide
|
||||
@NonNull Map<Integer, Long> authenticatorIds) {
|
||||
super(context, lazyDaemon, null /* token */, null /* ClientMonitorCallbackConverter */,
|
||||
userId, owner, 0 /* cookie */, sensorId, statsModality,
|
||||
BiometricsProtoEnums.ACTION_ENUMERATE, BiometricsProtoEnums.CLIENT_UNKNOWN,
|
||||
true /* shouldLogMetrics */);
|
||||
BiometricsProtoEnums.ACTION_ENUMERATE, BiometricsProtoEnums.CLIENT_UNKNOWN);
|
||||
mBiometricUtils = utils;
|
||||
mAuthenticatorIds = authenticatorIds;
|
||||
mEnrolledList = enrolledList;
|
||||
|
||||
@@ -51,7 +51,7 @@ public abstract class InternalEnumerateClient<T> extends ClientMonitor<T>
|
||||
// is all done internally.
|
||||
super(context, lazyDaemon, token, null /* ClientMonitorCallbackConverter */, userId, owner,
|
||||
0 /* cookie */, sensorId, statsModality, BiometricsProtoEnums.ACTION_ENUMERATE,
|
||||
BiometricsProtoEnums.CLIENT_UNKNOWN, true /* shouldLogMetrics */);
|
||||
BiometricsProtoEnums.CLIENT_UNKNOWN);
|
||||
mEnrolledList = enrolledList;
|
||||
mUtils = utils;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.content.Context;
|
||||
import android.hardware.biometrics.BiometricAuthenticator;
|
||||
import android.hardware.biometrics.BiometricsProtoEnums;
|
||||
|
||||
/**
|
||||
* ClientMonitor subclass for requesting authenticatorId invalidation. See
|
||||
* {@link InvalidationRequesterClient} for more info.
|
||||
*/
|
||||
public abstract class InvalidationClient<S extends BiometricAuthenticator.Identifier, T>
|
||||
extends ClientMonitor<T> {
|
||||
|
||||
private final BiometricUtils<S> mUtils;
|
||||
|
||||
public InvalidationClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
|
||||
int userId, int sensorId, @NonNull BiometricUtils<S> utils) {
|
||||
super(context, lazyDaemon, null /* token */, null /* listener */, userId,
|
||||
context.getOpPackageName(), 0 /* cookie */, sensorId,
|
||||
BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
|
||||
BiometricsProtoEnums.CLIENT_UNKNOWN);
|
||||
mUtils = utils;
|
||||
}
|
||||
|
||||
public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
|
||||
// TODO: Update framework w/ newAuthenticatorId
|
||||
mCallback.onClientFinished(this, true /* success */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(@NonNull Callback callback) {
|
||||
super.start(callback);
|
||||
|
||||
startHalOperation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unableToStart() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.content.Context;
|
||||
import android.hardware.biometrics.BiometricManager;
|
||||
import android.hardware.biometrics.BiometricsProtoEnums;
|
||||
|
||||
/**
|
||||
* ClientMonitor subclass responsible for coordination of authenticatorId invalidation of other
|
||||
* sensors. See {@link InvalidationClient} for the ClientMonitor subclass responsible for initiating
|
||||
* the invalidation with individual HALs. AuthenticatorId invalidation is required on devices with
|
||||
* multiple strong biometric sensors.
|
||||
*
|
||||
* The public Keystore and Biometric APIs are biometric-tied, not modality-tied, meaning that keys
|
||||
* are unlockable by "any/all strong biometrics on the device", and not "only a specific strong
|
||||
* sensor". The Keystore API allows for creation of biometric-tied keys that are invalidated upon
|
||||
* new biometric enrollment. See
|
||||
* {@link android.security.keystore.KeyGenParameterSpec.Builder#setInvalidatedByBiometricEnrollment}
|
||||
*
|
||||
* This has been supported on single-sensor devices by the various getAuthenticatorId APIs on the
|
||||
* HIDL and AIDL biometric HAL interfaces, where:
|
||||
* 1) authenticatorId is requested and stored during key generation
|
||||
* 2) authenticatorId is contained within the HAT when biometric authentication succeeds
|
||||
* 3) authenticatorId is automatically changed (below the framework) whenever a new biometric
|
||||
* enrollment occurs.
|
||||
*
|
||||
* For multi-biometric devices, this will be done the following way:
|
||||
* 1) New enrollment added for Sensor1. Sensor1's HAL/TEE updates its authenticatorId automatically
|
||||
* when enrollment completes
|
||||
* 2) Framework marks Sensor1 as "invalidationInProgress". See
|
||||
* {@link BiometricUtils#setInvalidationInProgress(Context, int, boolean)}
|
||||
* 3) After all other sensors have finished invalidation, the framework will clear the invalidation
|
||||
* flag for Sensor1.
|
||||
* 4) New keys that are generated will include all new authenticatorIds
|
||||
*
|
||||
* The above is robust to incomplete invalidation. For example, when system boots or after user
|
||||
* switches, the framework can check if any sensor has the "invalidationInProgress" flag set. If so,
|
||||
* the framework should re-start the invalidation process described above.
|
||||
*/
|
||||
public abstract class InvalidationRequesterClient<T> extends ClientMonitor<T> {
|
||||
|
||||
private final BiometricManager mBiometricManager;
|
||||
|
||||
public InvalidationRequesterClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
|
||||
int userId, int sensorId) {
|
||||
super(context, lazyDaemon, null /* token */, null /* listener */, userId,
|
||||
context.getOpPackageName(), 0 /* cookie */, sensorId,
|
||||
BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
|
||||
BiometricsProtoEnums.CLIENT_UNKNOWN);
|
||||
mBiometricManager = context.getSystemService(BiometricManager.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(@NonNull Callback callback) {
|
||||
super.start(callback);
|
||||
|
||||
// TODO(b/159667191): Request BiometricManager/BiometricService to invalidate
|
||||
// authenticatorIds. Be sure to invoke BiometricUtils#setInvalidationInProgress(true)
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unableToStart() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startHalOperation() {
|
||||
// No HAL operations necessary
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ public abstract class LoggableMonitor {
|
||||
private final int mStatsAction;
|
||||
private final int mStatsClient;
|
||||
private long mFirstAcquireTimeMs;
|
||||
private boolean mShouldLogMetrics;
|
||||
private boolean mShouldLogMetrics = true;
|
||||
|
||||
/**
|
||||
* Only valid for AuthenticationClient.
|
||||
@@ -52,14 +52,15 @@ public abstract class LoggableMonitor {
|
||||
* @param statsModality One of {@link BiometricsProtoEnums} MODALITY_* constants.
|
||||
* @param statsAction One of {@link BiometricsProtoEnums} ACTION_* constants.
|
||||
* @param statsClient One of {@link BiometricsProtoEnums} CLIENT_* constants.
|
||||
* @param shouldLogMetrics If set to false, metrics will not be reported to statsd.
|
||||
*/
|
||||
public LoggableMonitor(int statsModality, int statsAction, int statsClient,
|
||||
boolean shouldLogMetrics) {
|
||||
public LoggableMonitor(int statsModality, int statsAction, int statsClient) {
|
||||
mStatsModality = statsModality;
|
||||
mStatsAction = statsAction;
|
||||
mStatsClient = statsClient;
|
||||
mShouldLogMetrics = shouldLogMetrics;
|
||||
}
|
||||
|
||||
protected void setShouldLog(boolean shouldLog) {
|
||||
mShouldLogMetrics = shouldLog;
|
||||
}
|
||||
|
||||
public int getStatsClient() {
|
||||
|
||||
@@ -45,7 +45,7 @@ public abstract class RemovalClient<S extends BiometricAuthenticator.Identifier,
|
||||
int sensorId, @NonNull Map<Integer, Long> authenticatorIds, int statsModality) {
|
||||
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
|
||||
statsModality, BiometricsProtoEnums.ACTION_REMOVE,
|
||||
BiometricsProtoEnums.CLIENT_UNKNOWN, true /* shouldLogMetrics */);
|
||||
BiometricsProtoEnums.CLIENT_UNKNOWN);
|
||||
mBiometricId = biometricId;
|
||||
mBiometricUtils = utils;
|
||||
mAuthenticatorIds = authenticatorIds;
|
||||
|
||||
@@ -27,8 +27,7 @@ public abstract class RevokeChallengeClient<T> extends ClientMonitor<T> {
|
||||
@NonNull IBinder token, @NonNull String owner, int sensorId) {
|
||||
super(context, lazyDaemon, token, null /* listener */, 0 /* userId */, owner,
|
||||
0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
|
||||
BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN,
|
||||
true /* shouldLogMetrics */);
|
||||
BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,23 +16,18 @@
|
||||
|
||||
package com.android.server.biometrics.sensors.face;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.hardware.face.Face;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.Slog;
|
||||
import android.util.TypedXmlPullParser;
|
||||
import android.util.TypedXmlSerializer;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.server.biometrics.sensors.BiometricUserState;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -75,46 +70,26 @@ public class FaceUserState extends BiometricUserState<Face> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doWriteState() {
|
||||
AtomicFile destination = new AtomicFile(mFile);
|
||||
|
||||
ArrayList<Face> faces;
|
||||
protected void doWriteState(@NonNull TypedXmlSerializer serializer) throws Exception {
|
||||
final ArrayList<Face> faces;
|
||||
|
||||
synchronized (this) {
|
||||
faces = getCopy(mBiometrics);
|
||||
}
|
||||
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
out = destination.startWrite();
|
||||
serializer.startTag(null, TAG_FACES);
|
||||
|
||||
TypedXmlSerializer serializer = Xml.resolveSerializer(out);
|
||||
serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
|
||||
serializer.startDocument(null, true);
|
||||
serializer.startTag(null, TAG_FACES);
|
||||
|
||||
final int count = faces.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
Face f = faces.get(i);
|
||||
serializer.startTag(null, TAG_FACE);
|
||||
serializer.attributeInt(null, ATTR_FACE_ID, f.getBiometricId());
|
||||
serializer.attribute(null, ATTR_NAME, f.getName().toString());
|
||||
serializer.attributeLong(null, ATTR_DEVICE_ID, f.getDeviceId());
|
||||
serializer.endTag(null, TAG_FACE);
|
||||
}
|
||||
|
||||
serializer.endTag(null, TAG_FACES);
|
||||
serializer.endDocument();
|
||||
destination.finishWrite(out);
|
||||
|
||||
// Any error while writing is fatal.
|
||||
} catch (Throwable t) {
|
||||
Slog.wtf(TAG, "Failed to write settings, restoring backup", t);
|
||||
destination.failWrite(out);
|
||||
throw new IllegalStateException("Failed to write faces", t);
|
||||
} finally {
|
||||
IoUtils.closeQuietly(out);
|
||||
final int count = faces.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
Face f = faces.get(i);
|
||||
serializer.startTag(null, TAG_FACE);
|
||||
serializer.attributeInt(null, ATTR_FACE_ID, f.getBiometricId());
|
||||
serializer.attribute(null, ATTR_NAME, f.getName().toString());
|
||||
serializer.attributeLong(null, ATTR_DEVICE_ID, f.getDeviceId());
|
||||
serializer.endTag(null, TAG_FACE);
|
||||
}
|
||||
|
||||
serializer.endTag(null, TAG_FACES);
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
|
||||
@@ -18,7 +18,6 @@ package com.android.server.biometrics.sensors.face;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.hardware.biometrics.BiometricAuthenticator;
|
||||
import android.hardware.face.Face;
|
||||
import android.text.TextUtils;
|
||||
import android.util.SparseArray;
|
||||
@@ -115,6 +114,16 @@ public class FaceUtils implements BiometricUtils<Face> {
|
||||
return getStateForUser(context, userId).getUniqueName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInvalidationInProgress(Context context, int userId, boolean inProgress) {
|
||||
getStateForUser(context, userId).setInvalidationInProgress(inProgress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInvalidationInProgress(Context context, int userId) {
|
||||
return getStateForUser(context, userId).isInvalidationInProgress();
|
||||
}
|
||||
|
||||
private FaceUserState getStateForUser(Context ctx, int userId) {
|
||||
synchronized (this) {
|
||||
FaceUserState state = mUserStates.get(userId);
|
||||
|
||||
@@ -62,7 +62,7 @@ public class FaceEnrollClient extends EnrollClient<ISession> {
|
||||
@Nullable NativeHandle previewSurface, int sensorId, int maxTemplatesPerUser) {
|
||||
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, opPackageName, utils,
|
||||
timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId,
|
||||
false /* shouldVibrate */, true /* shouldLogMetrics */);
|
||||
false /* shouldVibrate */);
|
||||
mEnrollIgnoreList = getContext().getResources()
|
||||
.getIntArray(R.array.config_face_acquire_enroll_ignorelist);
|
||||
mEnrollIgnoreListVendor = getContext().getResources()
|
||||
|
||||
@@ -38,8 +38,7 @@ class FaceGetAuthenticatorIdClient extends ClientMonitor<ISession> {
|
||||
Map<Integer, Long> authenticatorIds) {
|
||||
super(context, lazyDaemon, null /* token */, null /* listener */, userId, opPackageName,
|
||||
0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_FACE,
|
||||
BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN,
|
||||
true /* shouldLogMetrics */);
|
||||
BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
|
||||
mAuthenticatorIds = authenticatorIds;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.face.aidl;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.biometrics.face.ISession;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.hardware.face.Face;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.server.biometrics.sensors.InvalidationClient;
|
||||
import com.android.server.biometrics.sensors.face.FaceUtils;
|
||||
|
||||
public class FaceInvalidationClient extends InvalidationClient<Face, ISession> {
|
||||
private static final String TAG = "FaceInvalidationClient";
|
||||
|
||||
public FaceInvalidationClient(@NonNull Context context,
|
||||
@NonNull LazyDaemon<ISession> lazyDaemon, int userId, int sensorId,
|
||||
@NonNull FaceUtils utils) {
|
||||
super(context, lazyDaemon, userId, sensorId, utils);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startHalOperation() {
|
||||
try {
|
||||
getFreshDaemon().invalidateAuthenticatorId(mSequentialId);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Remote exception", e);
|
||||
mCallback.onClientFinished(this, false /* success */);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,8 +50,7 @@ public class FaceResetLockoutClient extends ClientMonitor<ISession> {
|
||||
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
|
||||
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
|
||||
0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
|
||||
BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN,
|
||||
true /* shouldLogMetrics */);
|
||||
BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
|
||||
mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
|
||||
mLockoutCache = lockoutTracker;
|
||||
mLockoutResetDispatcher = lockoutResetDispatcher;
|
||||
|
||||
@@ -59,7 +59,7 @@ public class FaceEnrollClient extends EnrollClient<IBiometricsFace> {
|
||||
@Nullable NativeHandle surfaceHandle, int sensorId) {
|
||||
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
|
||||
timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId,
|
||||
false /* shouldVibrate */, true /* shouldLogMetrics */);
|
||||
false /* shouldVibrate */);
|
||||
mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length);
|
||||
mSurfaceHandle = surfaceHandle;
|
||||
mEnrollIgnoreList = getContext().getResources()
|
||||
|
||||
@@ -47,7 +47,7 @@ public class FaceGetFeatureClient extends ClientMonitor<IBiometricsFace> {
|
||||
@NonNull String owner, int sensorId, int feature, int faceId) {
|
||||
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
|
||||
BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
|
||||
BiometricsProtoEnums.CLIENT_UNKNOWN, true /* shouldLogMetrics */);
|
||||
BiometricsProtoEnums.CLIENT_UNKNOWN);
|
||||
mFeature = feature;
|
||||
mFaceId = faceId;
|
||||
|
||||
|
||||
@@ -42,8 +42,7 @@ public class FaceResetLockoutClient extends ClientMonitor<IBiometricsFace> {
|
||||
@NonNull byte[] hardwareAuthToken) {
|
||||
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
|
||||
0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
|
||||
BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN,
|
||||
true /* shouldLogMetrics */);
|
||||
BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
|
||||
|
||||
mHardwareAuthToken = new ArrayList<>();
|
||||
for (byte b : hardwareAuthToken) {
|
||||
|
||||
@@ -49,7 +49,7 @@ public class FaceSetFeatureClient extends ClientMonitor<IBiometricsFace> {
|
||||
byte[] hardwareAuthToken, int faceId) {
|
||||
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
|
||||
BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
|
||||
BiometricsProtoEnums.CLIENT_UNKNOWN, true /* shouldLogMetrics */);
|
||||
BiometricsProtoEnums.CLIENT_UNKNOWN);
|
||||
mFeature = feature;
|
||||
mEnabled = enabled;
|
||||
mFaceId = faceId;
|
||||
|
||||
@@ -43,8 +43,7 @@ public class FaceUpdateActiveUserClient extends ClientMonitor<IBiometricsFace> {
|
||||
@NonNull Map<Integer, Long> authenticatorIds) {
|
||||
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
|
||||
0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
|
||||
BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN,
|
||||
true /* shouldLogMetrics */);
|
||||
BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
|
||||
mCurrentUserId = currentUserId;
|
||||
mHasEnrolledBiometrics = hasEnrolledBIometrics;
|
||||
mAuthenticatorIds = authenticatorIds;
|
||||
|
||||
@@ -16,23 +16,18 @@
|
||||
|
||||
package com.android.server.biometrics.sensors.fingerprint;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.hardware.fingerprint.Fingerprint;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.Slog;
|
||||
import android.util.TypedXmlPullParser;
|
||||
import android.util.TypedXmlSerializer;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.server.biometrics.sensors.BiometricUserState;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -76,47 +71,27 @@ public class FingerprintUserState extends BiometricUserState<Fingerprint> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doWriteState() {
|
||||
AtomicFile destination = new AtomicFile(mFile);
|
||||
|
||||
ArrayList<Fingerprint> fingerprints;
|
||||
protected void doWriteState(@NonNull TypedXmlSerializer serializer) throws Exception {
|
||||
final ArrayList<Fingerprint> fingerprints;
|
||||
|
||||
synchronized (this) {
|
||||
fingerprints = getCopy(mBiometrics);
|
||||
}
|
||||
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
out = destination.startWrite();
|
||||
serializer.startTag(null, TAG_FINGERPRINTS);
|
||||
|
||||
TypedXmlSerializer serializer = Xml.resolveSerializer(out);
|
||||
serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
|
||||
serializer.startDocument(null, true);
|
||||
serializer.startTag(null, TAG_FINGERPRINTS);
|
||||
|
||||
final int count = fingerprints.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
Fingerprint fp = fingerprints.get(i);
|
||||
serializer.startTag(null, TAG_FINGERPRINT);
|
||||
serializer.attributeInt(null, ATTR_FINGER_ID, fp.getBiometricId());
|
||||
serializer.attribute(null, ATTR_NAME, fp.getName().toString());
|
||||
serializer.attributeInt(null, ATTR_GROUP_ID, fp.getGroupId());
|
||||
serializer.attributeLong(null, ATTR_DEVICE_ID, fp.getDeviceId());
|
||||
serializer.endTag(null, TAG_FINGERPRINT);
|
||||
}
|
||||
|
||||
serializer.endTag(null, TAG_FINGERPRINTS);
|
||||
serializer.endDocument();
|
||||
destination.finishWrite(out);
|
||||
|
||||
// Any error while writing is fatal.
|
||||
} catch (Throwable t) {
|
||||
Slog.wtf(TAG, "Failed to write settings, restoring backup", t);
|
||||
destination.failWrite(out);
|
||||
throw new IllegalStateException("Failed to write fingerprints", t);
|
||||
} finally {
|
||||
IoUtils.closeQuietly(out);
|
||||
final int count = fingerprints.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
Fingerprint fp = fingerprints.get(i);
|
||||
serializer.startTag(null, TAG_FINGERPRINT);
|
||||
serializer.attributeInt(null, ATTR_FINGER_ID, fp.getBiometricId());
|
||||
serializer.attribute(null, ATTR_NAME, fp.getName().toString());
|
||||
serializer.attributeInt(null, ATTR_GROUP_ID, fp.getGroupId());
|
||||
serializer.attributeLong(null, ATTR_DEVICE_ID, fp.getDeviceId());
|
||||
serializer.endTag(null, TAG_FINGERPRINT);
|
||||
}
|
||||
|
||||
serializer.endTag(null, TAG_FINGERPRINTS);
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
|
||||
@@ -118,6 +118,16 @@ public class FingerprintUtils implements BiometricUtils<Fingerprint> {
|
||||
return getStateForUser(context, userId).getUniqueName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInvalidationInProgress(Context context, int userId, boolean inProgress) {
|
||||
getStateForUser(context, userId).setInvalidationInProgress(inProgress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInvalidationInProgress(Context context, int userId) {
|
||||
return getStateForUser(context, userId).isInvalidationInProgress();
|
||||
}
|
||||
|
||||
private FingerprintUserState getStateForUser(Context ctx, int userId) {
|
||||
synchronized (this) {
|
||||
FingerprintUserState state = mUserStates.get(userId);
|
||||
|
||||
@@ -51,7 +51,7 @@ class FingerprintDetectClient extends AcquisitionClient<ISession> {
|
||||
int statsClient) {
|
||||
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
|
||||
BiometricsProtoEnums.MODALITY_FINGERPRINT, BiometricsProtoEnums.ACTION_AUTHENTICATE,
|
||||
statsClient, true /* shouldLogMetrics */);
|
||||
statsClient);
|
||||
mIsStrongBiometric = isStrongBiometric;
|
||||
mUdfpsOverlayController = udfpsOverlayController;
|
||||
}
|
||||
|
||||
@@ -55,9 +55,10 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps {
|
||||
boolean shouldLogMetrics) {
|
||||
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
|
||||
0 /* timeoutSec */, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
|
||||
true /* shouldVibrate */, shouldLogMetrics);
|
||||
true /* shouldVibrate */);
|
||||
mUdfpsOverlayController = udfpsOvelayController;
|
||||
mMaxTemplatesPerUser = maxTemplatesPerUser;
|
||||
setShouldLog(shouldLogMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -38,8 +38,7 @@ class FingerprintGetAuthenticatorIdClient extends ClientMonitor<ISession> {
|
||||
int sensorId, Map<Integer, Long> authenticatorIds) {
|
||||
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
|
||||
0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_FINGERPRINT,
|
||||
BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN,
|
||||
true /* shouldLogMetrics */);
|
||||
BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
|
||||
mAuthenticatorIds = authenticatorIds;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.fingerprint.aidl;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.hardware.biometrics.fingerprint.ISession;
|
||||
import android.hardware.fingerprint.Fingerprint;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.server.biometrics.sensors.InvalidationClient;
|
||||
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
|
||||
|
||||
public class FingerprintInvalidationClient extends InvalidationClient<Fingerprint, ISession> {
|
||||
private static final String TAG = "FingerprintInvalidationClient";
|
||||
|
||||
public FingerprintInvalidationClient(@NonNull Context context,
|
||||
@NonNull LazyDaemon<ISession> lazyDaemon, int userId, int sensorId,
|
||||
@NonNull FingerprintUtils utils) {
|
||||
super(context, lazyDaemon, userId, sensorId, utils);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startHalOperation() {
|
||||
try {
|
||||
getFreshDaemon().invalidateAuthenticatorId(mSequentialId);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Remote exception", e);
|
||||
mCallback.onClientFinished(this, false /* success */);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,8 +50,7 @@ class FingerprintResetLockoutClient extends ClientMonitor<ISession> {
|
||||
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
|
||||
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
|
||||
0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
|
||||
BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN,
|
||||
true /* shouldLogMetrics */);
|
||||
BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
|
||||
mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
|
||||
mLockoutCache = lockoutTracker;
|
||||
mLockoutResetDispatcher = lockoutResetDispatcher;
|
||||
|
||||
@@ -56,7 +56,7 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint>
|
||||
boolean isStrongBiometric, int statsClient) {
|
||||
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
|
||||
BiometricsProtoEnums.MODALITY_FINGERPRINT, BiometricsProtoEnums.ACTION_AUTHENTICATE,
|
||||
statsClient, true /* shouldLogMetrics */);
|
||||
statsClient);
|
||||
mUdfpsOverlayController = udfpsOverlayController;
|
||||
mIsStrongBiometric = isStrongBiometric;
|
||||
}
|
||||
|
||||
@@ -56,8 +56,9 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint
|
||||
boolean shouldLogMetrics) {
|
||||
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
|
||||
timeoutSec, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
|
||||
true /* shouldVibrate */, shouldLogMetrics);
|
||||
true /* shouldVibrate */);
|
||||
mUdfpsOverlayController = udfpsOverlayController;
|
||||
setShouldLog(shouldLogMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -50,8 +50,7 @@ public class FingerprintUpdateActiveUserClient extends ClientMonitor<IBiometrics
|
||||
@NonNull Map<Integer, Long> authenticatorIds) {
|
||||
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
|
||||
0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
|
||||
BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN,
|
||||
true /* shouldLogMetrics */);
|
||||
BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
|
||||
mCurrentUserId = currentUserId;
|
||||
mHasEnrolledBiometrics = hasEnrolledBiometrics;
|
||||
mAuthenticatorIds = authenticatorIds;
|
||||
|
||||
@@ -218,7 +218,7 @@ public class BiometricSchedulerTest {
|
||||
@NonNull LazyDaemon<Object> lazyDaemon, int cookie) {
|
||||
super(context, lazyDaemon, token /* token */, null /* listener */, 0 /* userId */,
|
||||
TAG, cookie, TEST_SENSOR_ID, 0 /* statsModality */,
|
||||
0 /* statsAction */, 0 /* statsClient */, true /* shouldLogMetrics */);
|
||||
0 /* statsAction */, 0 /* statsClient */);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user