2/n: Add UserAwareBiometricScheduler

Bug: 181984005
Test: atest com.android.server.biometrics
Change-Id: I67beecf1aee2631856da3541ca1e0d158050ac65
This commit is contained in:
Kevin Chyn
2021-03-21 19:13:17 -07:00
parent 38c3e3ad5e
commit 7d11db35c9
7 changed files with 452 additions and 6 deletions

View File

@@ -178,4 +178,6 @@ enum ClientMonitorEnum {
CM_DETECT_INTERACTION = 13;
CM_INVALIDATION_REQUESTER = 14;
CM_INVALIDATE = 15;
CM_STOP_USER = 16;
CM_START_USER = 17;
}

View File

@@ -197,7 +197,7 @@ public abstract class BaseClientMonitor extends LoggableMonitor
return mListener;
}
public final int getTargetUserId() {
public int getTargetUserId() {
return mTargetUserId;
}

View File

@@ -55,7 +55,7 @@ public class BiometricScheduler {
private static final String BASE_TAG = "BiometricScheduler";
// Number of recent operations to keep in our logs for dumpsys
private static final int LOG_NUM_RECENT_OPERATIONS = 50;
protected static final int LOG_NUM_RECENT_OPERATIONS = 50;
/**
* Contains all the necessary information for a HAL operation.
@@ -196,10 +196,10 @@ public class BiometricScheduler {
}
}
@NonNull private final String mBiometricTag;
@NonNull protected final String mBiometricTag;
@Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
@NonNull private final IBiometricService mBiometricService;
@NonNull private final Handler mHandler = new Handler(Looper.getMainLooper());
@NonNull protected final Handler mHandler = new Handler(Looper.getMainLooper());
@NonNull private final InternalCallback mInternalCallback;
@VisibleForTesting @NonNull final Deque<Operation> mPendingOperations;
@VisibleForTesting @Nullable Operation mCurrentOperation;
@@ -294,11 +294,11 @@ public class BiometricScheduler {
return mInternalCallback;
}
private String getTag() {
protected String getTag() {
return BASE_TAG + "/" + mBiometricTag;
}
private void startNextOperationIfIdle() {
protected void startNextOperationIfIdle() {
if (mCurrentOperation != null) {
Slog.v(getTag(), "Not idle, current operation: " + mCurrentOperation);
return;
@@ -310,6 +310,7 @@ public class BiometricScheduler {
mCurrentOperation = mPendingOperations.poll();
final BaseClientMonitor currentClient = mCurrentOperation.mClientMonitor;
Slog.d(getTag(), "[Polled] " + mCurrentOperation);
// If the operation at the front of the queue has been marked for cancellation, send
// ERROR_CANCELED. No need to start this client.

View File

@@ -0,0 +1,50 @@
/*
* 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.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.IBinder;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.BiometricsProto;
public abstract class StartUserClient<T> extends HalClientMonitor<T> {
public interface UserStartedCallback {
void onUserStarted(int newUserId);
}
@NonNull @VisibleForTesting
protected final UserStartedCallback mUserStartedCallback;
public StartUserClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
@Nullable IBinder token, int userId, int sensorId,
@NonNull UserStartedCallback callback) {
super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(),
0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
mUserStartedCallback = callback;
}
@Override
public int getProtoEnum() {
return BiometricsProto.CM_START_USER;
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.IBinder;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.BiometricsProto;
public abstract class StopUserClient<T> extends HalClientMonitor<T> {
public interface UserStoppedCallback {
void onUserStopped();
}
@NonNull @VisibleForTesting
protected final UserStoppedCallback mUserStoppedCallback;
public StopUserClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
@Nullable IBinder token, int userId, int sensorId,
@NonNull UserStoppedCallback callback) {
super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(),
0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
mUserStoppedCallback = callback;
}
@Override
public int getProtoEnum() {
return BiometricsProto.CM_STOP_USER;
}
}

View File

@@ -0,0 +1,123 @@
/*
* 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.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.IBiometricService;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
/**
* A user-aware scheduler that requests user-switches based on scheduled operation's targetUserId.
*/
public class UserAwareBiometricScheduler extends BiometricScheduler {
private static final String BASE_TAG = "UaBiometricScheduler";
/**
* Interface to retrieve the owner's notion of the current userId. Note that even though
* the scheduler can determine this based on its history of processed clients, we should still
* query the owner since it may be cleared due to things like HAL death, etc.
*/
public interface CurrentUserRetriever {
int getCurrentUserId();
}
public interface UserSwitchCallback {
@NonNull StopUserClient<?> getStopUserClient(int userId);
@NonNull StartUserClient<?> getStartUserClient(int newUserId);
}
@NonNull private final CurrentUserRetriever mCurrentUserRetriever;
@NonNull private final UserSwitchCallback mUserSwitchCallback;
@NonNull @VisibleForTesting final ClientFinishedCallback mClientFinishedCallback;
@VisibleForTesting
class ClientFinishedCallback implements BaseClientMonitor.Callback {
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
mHandler.post(() -> {
Slog.d(getTag(), "[Client finished] " + clientMonitor + ", success: " + success);
startNextOperationIfIdle();
});
}
}
@VisibleForTesting
UserAwareBiometricScheduler(@NonNull String tag,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@NonNull IBiometricService biometricService,
@NonNull CurrentUserRetriever currentUserRetriever,
@NonNull UserSwitchCallback userSwitchCallback) {
super(tag, gestureAvailabilityDispatcher, biometricService, LOG_NUM_RECENT_OPERATIONS);
mCurrentUserRetriever = currentUserRetriever;
mUserSwitchCallback = userSwitchCallback;
mClientFinishedCallback = new ClientFinishedCallback();
}
public UserAwareBiometricScheduler(@NonNull String tag,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@NonNull CurrentUserRetriever currentUserRetriever,
@NonNull UserSwitchCallback userSwitchCallback) {
this(tag, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface(
ServiceManager.getService(Context.BIOMETRIC_SERVICE)), currentUserRetriever,
userSwitchCallback);
}
@Override
protected String getTag() {
return BASE_TAG + "/" + mBiometricTag;
}
@Override
protected void startNextOperationIfIdle() {
if (mCurrentOperation != null) {
Slog.v(getTag(), "Not idle, current operation: " + mCurrentOperation);
return;
}
if (mPendingOperations.isEmpty()) {
Slog.d(getTag(), "No operations, returning to idle");
return;
}
final int currentUserId = mCurrentUserRetriever.getCurrentUserId();
final int nextUserId = mPendingOperations.getFirst().mClientMonitor.getTargetUserId();
if (nextUserId == currentUserId) {
super.startNextOperationIfIdle();
} else if (currentUserId == UserHandle.USER_NULL) {
Slog.d(getTag(), "User switch required, current user null, next: " + nextUserId);
final BaseClientMonitor startClient =
mUserSwitchCallback.getStartUserClient(nextUserId);
startClient.start(mClientFinishedCallback);
} else {
final BaseClientMonitor stopClient = mUserSwitchCallback
.getStopUserClient(currentUserId);
Slog.d(getTag(), "User switch required, current: " + currentUserId
+ ", next: " + nextUserId + ". " + stopClient);
stopClient.start(mClientFinishedCallback);
}
}
}

View File

@@ -0,0 +1,220 @@
/*
* 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 static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.hardware.biometrics.IBiometricService;
import android.os.Binder;
import android.os.IBinder;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@Presubmit
@SmallTest
public class UserAwareBiometricSchedulerTest {
private static final String TAG = "BiometricSchedulerTest";
private static final int TEST_SENSOR_ID = 0;
private UserAwareBiometricScheduler mScheduler;
private IBinder mToken;
@Mock
private Context mContext;
@Mock
private IBiometricService mBiometricService;
private TestUserStartedCallback mUserStartedCallback;
private TestUserStoppedCallback mUserStoppedCallback;
private int mCurrentUserId = UserHandle.USER_NULL;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mToken = new Binder();
mUserStartedCallback = new TestUserStartedCallback();
mUserStoppedCallback = new TestUserStoppedCallback();
mScheduler = new UserAwareBiometricScheduler(TAG,
null /* gestureAvailabilityDispatcher */,
mBiometricService,
() -> mCurrentUserId,
new UserAwareBiometricScheduler.UserSwitchCallback() {
@NonNull
@Override
public StopUserClient<?> getStopUserClient(int userId) {
return new TestStopUserClient(mContext, Object::new, mToken, userId,
TEST_SENSOR_ID, mUserStoppedCallback);
}
@NonNull
@Override
public StartUserClient<?> getStartUserClient(int newUserId) {
return new TestStartUserClient(mContext, Object::new, mToken, newUserId,
TEST_SENSOR_ID, mUserStartedCallback);
}
});
}
@Test
public void testScheduleOperation_whenNoUser() {
mCurrentUserId = UserHandle.USER_NULL;
final int nextUserId = 0;
BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
when(nextClient.getTargetUserId()).thenReturn(nextUserId);
mScheduler.scheduleClientMonitor(nextClient);
verify(nextClient, never()).start(any());
assertEquals(0, mUserStoppedCallback.numInvocations);
assertEquals(1, mUserStartedCallback.numInvocations);
waitForIdle();
verify(nextClient).start(any());
}
@Test
public void testScheduleOperation_whenSameUser() {
mCurrentUserId = 10;
BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
when(nextClient.getTargetUserId()).thenReturn(mCurrentUserId);
mScheduler.scheduleClientMonitor(nextClient);
waitForIdle();
verify(nextClient).start(any());
assertEquals(0, mUserStoppedCallback.numInvocations);
assertEquals(0, mUserStartedCallback.numInvocations);
}
@Test
public void testScheduleOperation_whenDifferentUser() {
mCurrentUserId = 10;
final int nextUserId = 11;
BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
when(nextClient.getTargetUserId()).thenReturn(nextUserId);
mScheduler.scheduleClientMonitor(nextClient);
waitForIdle();
assertEquals(1, mUserStoppedCallback.numInvocations);
waitForIdle();
assertEquals(1, mUserStartedCallback.numInvocations);
waitForIdle();
verify(nextClient).start(any());
}
private static void waitForIdle() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
private class TestUserStoppedCallback implements StopUserClient.UserStoppedCallback {
int numInvocations;
@Override
public void onUserStopped() {
numInvocations++;
mCurrentUserId = UserHandle.USER_NULL;
}
}
private class TestUserStartedCallback implements StartUserClient.UserStartedCallback {
int numInvocations;
@Override
public void onUserStarted(int newUserId) {
numInvocations++;
mCurrentUserId = newUserId;
}
}
private static class TestStopUserClient extends StopUserClient<Object> {
public TestStopUserClient(@NonNull Context context,
@NonNull LazyDaemon<Object> lazyDaemon, @Nullable IBinder token, int userId,
int sensorId, @NonNull UserStoppedCallback callback) {
super(context, lazyDaemon, token, userId, sensorId, callback);
}
@Override
protected void startHalOperation() {
}
@Override
public void start(@NonNull Callback callback) {
super.start(callback);
mUserStoppedCallback.onUserStopped();
callback.onClientFinished(this, true /* success */);
}
@Override
public void unableToStart() {
}
}
private static class TestStartUserClient extends StartUserClient<Object> {
public TestStartUserClient(@NonNull Context context,
@NonNull LazyDaemon<Object> lazyDaemon, @Nullable IBinder token, int userId,
int sensorId, @NonNull UserStartedCallback callback) {
super(context, lazyDaemon, token, userId, sensorId, callback);
}
@Override
protected void startHalOperation() {
}
@Override
public void start(@NonNull Callback callback) {
super.start(callback);
mUserStartedCallback.onUserStarted(getTargetUserId());
callback.onClientFinished(this, true /* success */);
}
@Override
public void unableToStart() {
}
}
}