Merge "2/n: Add UserAwareBiometricScheduler" into sc-dev
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -197,7 +197,7 @@ public abstract class BaseClientMonitor extends LoggableMonitor
|
||||
return mListener;
|
||||
}
|
||||
|
||||
public final int getTargetUserId() {
|
||||
public int getTargetUserId() {
|
||||
return mTargetUserId;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user