blob: 603cc22968a99b03ba2aaa4f9240102da7646ac8 [file] [log] [blame]
/*
* 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 com.android.server.biometrics.sensors.BiometricSchedulerOperation.STATE_STARTED;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.IBiometricService;
import android.os.Handler;
import android.os.Looper;
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;
@Nullable private StopUserClient<?> mStopUserClient;
private class ClientFinishedCallback implements BaseClientMonitor.Callback {
private final BaseClientMonitor mOwner;
ClientFinishedCallback(BaseClientMonitor owner) {
mOwner = owner;
}
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
mHandler.post(() -> {
if (mOwner != clientMonitor) {
Slog.e(getTag(), "[Wrong client finished], actual: "
+ clientMonitor + ", expected: " + mOwner);
return;
}
Slog.d(getTag(), "[Client finished] " + clientMonitor + ", success: " + success);
if (mCurrentOperation != null && mCurrentOperation.isFor(mOwner)) {
mCurrentOperation = null;
startNextOperationIfIdle();
} else {
// can usually be ignored (hal died, etc.)
Slog.d(getTag(), "operation is already null or different (reset?): "
+ mCurrentOperation);
}
});
}
}
@VisibleForTesting
public UserAwareBiometricScheduler(@NonNull String tag,
@NonNull Handler handler,
@SensorType int sensorType,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@NonNull IBiometricService biometricService,
@NonNull CurrentUserRetriever currentUserRetriever,
@NonNull UserSwitchCallback userSwitchCallback,
@NonNull CoexCoordinator coexCoordinator) {
super(tag, handler, sensorType, gestureAvailabilityDispatcher, biometricService,
LOG_NUM_RECENT_OPERATIONS, coexCoordinator);
mCurrentUserRetriever = currentUserRetriever;
mUserSwitchCallback = userSwitchCallback;
}
public UserAwareBiometricScheduler(@NonNull String tag,
@SensorType int sensorType,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@NonNull CurrentUserRetriever currentUserRetriever,
@NonNull UserSwitchCallback userSwitchCallback) {
this(tag, new Handler(Looper.getMainLooper()), sensorType, gestureAvailabilityDispatcher,
IBiometricService.Stub.asInterface(
ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
currentUserRetriever, userSwitchCallback, CoexCoordinator.getInstance());
}
@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().getTargetUserId();
if (nextUserId == currentUserId) {
super.startNextOperationIfIdle();
} else if (currentUserId == UserHandle.USER_NULL) {
final BaseClientMonitor startClient =
mUserSwitchCallback.getStartUserClient(nextUserId);
final ClientFinishedCallback finishedCallback =
new ClientFinishedCallback(startClient);
Slog.d(getTag(), "[Starting User] " + startClient);
mCurrentOperation = new BiometricSchedulerOperation(
startClient, finishedCallback, STATE_STARTED);
startClient.start(finishedCallback);
} else {
if (mStopUserClient != null) {
Slog.d(getTag(), "[Waiting for StopUser] " + mStopUserClient);
} else {
mStopUserClient = mUserSwitchCallback
.getStopUserClient(currentUserId);
final ClientFinishedCallback finishedCallback =
new ClientFinishedCallback(mStopUserClient);
Slog.d(getTag(), "[Stopping User] current: " + currentUserId
+ ", next: " + nextUserId + ". " + mStopUserClient);
mCurrentOperation = new BiometricSchedulerOperation(
mStopUserClient, finishedCallback, STATE_STARTED);
mStopUserClient.start(finishedCallback);
}
}
}
public void onUserStopped() {
if (mStopUserClient == null) {
Slog.e(getTag(), "Unexpected onUserStopped");
return;
}
Slog.d(getTag(), "[OnUserStopped]: " + mStopUserClient);
mStopUserClient.onUserStopped();
mStopUserClient = null;
}
}