4/n: Make CoexCoordinator multi-sensor-aware

CoexCoordinator now knows whenever specific types of sensors are
started/stopped.

Note that this change is still a no-op and does not affect any
existing behavior.

Bug: 193089985
Test: atest com.android.server.biometrics
Test: inspect logs
Change-Id: I6f1980ba02febfa0a866e2684fdfc23a1ca664e1
This commit is contained in:
Kevin Chyn
2021-07-16 16:55:15 -07:00
parent 89315a9565
commit 164f70d4f1
12 changed files with 150 additions and 24 deletions

View File

@@ -22,6 +22,7 @@ import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.IBiometricService;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -48,8 +49,11 @@ import java.util.Locale;
/**
* A scheduler for biometric HAL operations. Maintains a queue of {@link BaseClientMonitor}
* operations, without caring about its implementation details. Operations may perform one or more
* operations, without caring about its implementation details. Operations may perform zero or more
* interactions with the HAL before finishing.
*
* We currently assume (and require) that each biometric sensor have its own instance of a
* {@link BiometricScheduler}. See {@link CoexCoordinator}.
*/
public class BiometricScheduler {
@@ -57,6 +61,55 @@ public class BiometricScheduler {
// Number of recent operations to keep in our logs for dumpsys
protected static final int LOG_NUM_RECENT_OPERATIONS = 50;
/**
* Unknown sensor type. This should never be used, and is a sign that something is wrong during
* initialization.
*/
public static final int SENSOR_TYPE_UNKNOWN = 0;
/**
* Face authentication.
*/
public static final int SENSOR_TYPE_FACE = 1;
/**
* Any UDFPS type. See {@link FingerprintSensorPropertiesInternal#isAnyUdfpsType()}.
*/
public static final int SENSOR_TYPE_UDFPS = 2;
/**
* Any other fingerprint sensor. We can add additional definitions in the future when necessary.
*/
public static final int SENSOR_TYPE_FP_OTHER = 3;
@IntDef({SENSOR_TYPE_UNKNOWN, SENSOR_TYPE_FACE, SENSOR_TYPE_UDFPS, SENSOR_TYPE_FP_OTHER})
@Retention(RetentionPolicy.SOURCE)
public @interface SensorType {}
public static @SensorType int sensorTypeFromFingerprintProperties(
@NonNull FingerprintSensorPropertiesInternal props) {
if (props.isAnyUdfpsType()) {
return SENSOR_TYPE_UDFPS;
}
return SENSOR_TYPE_FP_OTHER;
}
public static String sensorTypeToString(@SensorType int sensorType) {
switch (sensorType) {
case SENSOR_TYPE_UNKNOWN:
return "Unknown";
case SENSOR_TYPE_FACE:
return "Face";
case SENSOR_TYPE_UDFPS:
return "Udfps";
case SENSOR_TYPE_FP_OTHER:
return "OtherFp";
default:
return "UnknownUnknown";
}
}
/**
* Contains all the necessary information for a HAL operation.
*/
@@ -207,6 +260,7 @@ public class BiometricScheduler {
}
@NonNull protected final String mBiometricTag;
private final @SensorType int mSensorType;
@Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
@NonNull private final IBiometricService mBiometricService;
@NonNull protected final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -218,6 +272,7 @@ public class BiometricScheduler {
private int mTotalOperationsHandled;
private final int mRecentOperationsLimit;
@NonNull private final List<Integer> mRecentOperations;
@NonNull private final CoexCoordinator mCoexCoordinator;
// Internal callback, notified when an operation is complete. Notifies the requester
// that the operation is complete, before performing internal scheduler work (such as
@@ -226,6 +281,12 @@ public class BiometricScheduler {
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
Slog.d(getTag(), "[Started] " + clientMonitor);
if (clientMonitor instanceof AuthenticationClient) {
mCoexCoordinator.addAuthenticationClient(mSensorType,
(AuthenticationClient<?>) clientMonitor);
}
if (mCurrentOperation.mClientCallback != null) {
mCurrentOperation.mClientCallback.onClientStarted(clientMonitor);
}
@@ -248,6 +309,11 @@ public class BiometricScheduler {
}
Slog.d(getTag(), "[Finishing] " + clientMonitor + ", success: " + success);
if (clientMonitor instanceof AuthenticationClient) {
mCoexCoordinator.removeAuthenticationClient(mSensorType,
(AuthenticationClient<?>) clientMonitor);
}
mCurrentOperation.mState = Operation.STATE_FINISHED;
if (mCurrentOperation.mClientCallback != null) {
@@ -271,10 +337,12 @@ public class BiometricScheduler {
}
@VisibleForTesting
BiometricScheduler(@NonNull String tag,
BiometricScheduler(@NonNull String tag, @SensorType int sensorType,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@NonNull IBiometricService biometricService, int recentOperationsLimit) {
@NonNull IBiometricService biometricService, int recentOperationsLimit,
@NonNull CoexCoordinator coexCoordinator) {
mBiometricTag = tag;
mSensorType = sensorType;
mInternalCallback = new InternalCallback();
mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher;
mPendingOperations = new ArrayDeque<>();
@@ -282,6 +350,7 @@ public class BiometricScheduler {
mCrashStates = new ArrayDeque<>();
mRecentOperationsLimit = recentOperationsLimit;
mRecentOperations = new ArrayList<>();
mCoexCoordinator = coexCoordinator;
}
/**
@@ -290,10 +359,11 @@ public class BiometricScheduler {
* @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures
* (such as fingerprint swipe).
*/
public BiometricScheduler(@NonNull String tag,
public BiometricScheduler(@NonNull String tag, @SensorType int sensorType,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
this(tag, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface(
ServiceManager.getService(Context.BIOMETRIC_SERVICE)), LOG_NUM_RECENT_OPERATIONS);
this(tag, sensorType, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface(
ServiceManager.getService(Context.BIOMETRIC_SERVICE)), LOG_NUM_RECENT_OPERATIONS,
CoexCoordinator.getInstance());
}
/**
@@ -645,6 +715,7 @@ public class BiometricScheduler {
public void dump(PrintWriter pw) {
pw.println("Dump of BiometricScheduler " + getTag());
pw.println("Type: " + mSensorType);
pw.println("Current operation: " + mCurrentOperation);
pw.println("Pending operations: " + mPendingOperations.size());
for (Operation operation : mPendingOperations) {

View File

@@ -16,7 +16,13 @@
package com.android.server.biometrics.sensors;
import static com.android.server.biometrics.sensors.BiometricScheduler.sensorTypeToString;
import android.annotation.NonNull;
import android.util.Slog;
import java.util.HashMap;
import java.util.Map;
/**
* Singleton that contains the core logic for determining if haptics and authentication callbacks
@@ -27,6 +33,7 @@ import android.annotation.NonNull;
public class CoexCoordinator {
private static final String TAG = "BiometricCoexCoordinator";
private static final boolean DEBUG = true;
/**
* Callback interface notifying the owner of "results" from the CoexCoordinator's business
@@ -47,10 +54,6 @@ public class CoexCoordinator {
private static CoexCoordinator sInstance;
private CoexCoordinator() {
// Singleton
}
@NonNull
static CoexCoordinator getInstance() {
if (sInstance == null) {
@@ -59,6 +62,43 @@ public class CoexCoordinator {
return sInstance;
}
// SensorType to AuthenticationClient map
private final Map<Integer, AuthenticationClient<?>> mClientMap;
private CoexCoordinator() {
// Singleton
mClientMap = new HashMap<>();
}
public void addAuthenticationClient(@BiometricScheduler.SensorType int sensorType,
@NonNull AuthenticationClient<?> client) {
if (DEBUG) {
Slog.d(TAG, "addAuthenticationClient(" + sensorTypeToString(sensorType) + ")"
+ ", client: " + client);
}
if (mClientMap.containsKey(sensorType)) {
Slog.w(TAG, "Overwriting existing client: " + mClientMap.get(sensorType)
+ " with new client: " + client);
}
mClientMap.put(sensorType, client);
}
public void removeAuthenticationClient(@BiometricScheduler.SensorType int sensorType,
@NonNull AuthenticationClient<?> client) {
if (DEBUG) {
Slog.d(TAG, "removeAuthenticationClient(" + sensorTypeToString(sensorType) + ")"
+ ", client: " + client);
}
if (!mClientMap.containsKey(sensorType)) {
Slog.e(TAG, "sensorType: " + sensorType + " does not exist in map. Client: " + client);
return;
}
mClientMap.remove(sensorType);
}
public void onAuthenticationSucceeded(@NonNull AuthenticationClient<?> client,
@NonNull Callback callback) {
if (client.isBiometricPrompt()) {

View File

@@ -83,24 +83,26 @@ public class UserAwareBiometricScheduler extends BiometricScheduler {
}
@VisibleForTesting
UserAwareBiometricScheduler(@NonNull String tag,
UserAwareBiometricScheduler(@NonNull String tag, @SensorType int sensorType,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@NonNull IBiometricService biometricService,
@NonNull CurrentUserRetriever currentUserRetriever,
@NonNull UserSwitchCallback userSwitchCallback) {
super(tag, gestureAvailabilityDispatcher, biometricService, LOG_NUM_RECENT_OPERATIONS);
@NonNull UserSwitchCallback userSwitchCallback,
@NonNull CoexCoordinator coexCoordinator) {
super(tag, sensorType, gestureAvailabilityDispatcher, biometricService,
LOG_NUM_RECENT_OPERATIONS, coexCoordinator);
mCurrentUserRetriever = currentUserRetriever;
mUserSwitchCallback = userSwitchCallback;
}
public UserAwareBiometricScheduler(@NonNull String tag,
public UserAwareBiometricScheduler(@NonNull String tag, @SensorType int sensorType,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@NonNull CurrentUserRetriever currentUserRetriever,
@NonNull UserSwitchCallback userSwitchCallback) {
this(tag, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface(
this(tag, sensorType, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface(
ServiceManager.getService(Context.BIOMETRIC_SERVICE)), currentUserRetriever,
userSwitchCallback);
userSwitchCallback, CoexCoordinator.getInstance());
}
@Override

View File

@@ -494,7 +494,8 @@ public class Sensor {
mToken = new Binder();
mHandler = handler;
mSensorProperties = sensorProperties;
mScheduler = new UserAwareBiometricScheduler(tag, null /* gestureAvailabilityDispatcher */,
mScheduler = new UserAwareBiometricScheduler(tag, BiometricScheduler.SENSOR_TYPE_FACE,
null /* gestureAvailabilityDispatcher */,
() -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL,
new UserAwareBiometricScheduler.UserSwitchCallback() {
@NonNull

View File

@@ -355,7 +355,8 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
public Face10(@NonNull Context context, @NonNull FaceSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
this(context, sensorProps, lockoutResetDispatcher,
new BiometricScheduler(TAG, null /* gestureAvailabilityTracker */));
new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE,
null /* gestureAvailabilityTracker */));
}
@Override

View File

@@ -449,7 +449,9 @@ class Sensor {
mHandler = handler;
mSensorProperties = sensorProperties;
mLockoutCache = new LockoutCache();
mScheduler = new UserAwareBiometricScheduler(tag, gestureAvailabilityDispatcher,
mScheduler = new UserAwareBiometricScheduler(tag,
BiometricScheduler.sensorTypeFromFingerprintProperties(mSensorProperties),
gestureAvailabilityDispatcher,
() -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL,
new UserAwareBiometricScheduler.UserSwitchCallback() {
@NonNull

View File

@@ -352,7 +352,9 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
final Handler handler = new Handler(Looper.getMainLooper());
final BiometricScheduler scheduler =
new BiometricScheduler(TAG, gestureAvailabilityDispatcher);
new BiometricScheduler(TAG,
BiometricScheduler.sensorTypeFromFingerprintProperties(sensorProps),
gestureAvailabilityDispatcher);
final HalResultController controller = new HalResultController(sensorProps.sensorId,
context, handler,
scheduler);

View File

@@ -138,7 +138,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage
TestableBiometricScheduler(@NonNull String tag,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
super(tag, gestureAvailabilityDispatcher);
super(tag, BiometricScheduler.SENSOR_TYPE_FP_OTHER, gestureAvailabilityDispatcher);
mInternalCallback = new TestableInternalCallback();
}

View File

@@ -75,8 +75,9 @@ public class BiometricSchedulerTest {
public void setUp() {
MockitoAnnotations.initMocks(this);
mToken = new Binder();
mScheduler = new BiometricScheduler(TAG, null /* gestureAvailabilityTracker */,
mBiometricService, LOG_NUM_RECENT_OPERATIONS);
mScheduler = new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_UNKNOWN,
null /* gestureAvailabilityTracker */, mBiometricService, LOG_NUM_RECENT_OPERATIONS,
CoexCoordinator.getInstance());
}
@Test

View File

@@ -74,6 +74,7 @@ public class UserAwareBiometricSchedulerTest {
mUserStoppedCallback = new TestUserStoppedCallback();
mScheduler = new UserAwareBiometricScheduler(TAG,
BiometricScheduler.SENSOR_TYPE_UNKNOWN,
null /* gestureAvailabilityDispatcher */,
mBiometricService,
() -> mCurrentUserId,
@@ -92,7 +93,8 @@ public class UserAwareBiometricSchedulerTest {
return new TestStartUserClient(mContext, Object::new, mToken, newUserId,
TEST_SENSOR_ID, mUserStartedCallback, mStartOperationsFinish);
}
});
},
CoexCoordinator.getInstance());
}
@Test

View File

@@ -32,6 +32,7 @@ import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
@@ -78,6 +79,7 @@ public class SensorTest {
when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService);
mScheduler = new UserAwareBiometricScheduler(TAG,
BiometricScheduler.SENSOR_TYPE_FACE,
null /* gestureAvailabilityDispatcher */,
() -> USER_ID,
mUserSwitchCallback);

View File

@@ -32,6 +32,7 @@ import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
@@ -78,6 +79,7 @@ public class SensorTest {
when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService);
mScheduler = new UserAwareBiometricScheduler(TAG,
BiometricScheduler.SENSOR_TYPE_FP_OTHER,
null /* gestureAvailabilityDispatcher */,
() -> USER_ID,
mUserSwitchCallback);