265 lines
9.2 KiB
Java
265 lines
9.2 KiB
Java
![]() |
/*
|
||
|
* Copyright (C) 2020 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 android.hardware.biometrics;
|
||
|
|
||
|
import static android.Manifest.permission.TEST_BIOMETRIC;
|
||
|
|
||
|
import android.annotation.NonNull;
|
||
|
import android.annotation.Nullable;
|
||
|
import android.annotation.RequiresPermission;
|
||
|
import android.annotation.TestApi;
|
||
|
import android.content.Context;
|
||
|
import android.os.RemoteException;
|
||
|
import android.util.ArraySet;
|
||
|
import android.util.Log;
|
||
|
|
||
|
import java.util.concurrent.CountDownLatch;
|
||
|
import java.util.concurrent.TimeUnit;
|
||
|
|
||
|
/**
|
||
|
* Common set of interfaces to test biometric-related APIs, including {@link BiometricPrompt} and
|
||
|
* {@link android.hardware.fingerprint.FingerprintManager}.
|
||
|
* @hide
|
||
|
*/
|
||
|
@TestApi
|
||
|
public class BiometricTestSession implements AutoCloseable {
|
||
|
private static final String BASE_TAG = "BiometricTestSession";
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
public interface TestSessionProvider {
|
||
|
@NonNull
|
||
|
ITestSession createTestSession(@NonNull Context context, int sensorId,
|
||
|
@NonNull ITestSessionCallback callback) throws RemoteException;
|
||
|
}
|
||
|
|
||
|
private final Context mContext;
|
||
|
private final int mSensorId;
|
||
|
private final ITestSession mTestSession;
|
||
|
|
||
|
// Keep track of users that were tested, which need to be cleaned up when finishing.
|
||
|
@NonNull private final ArraySet<Integer> mTestedUsers;
|
||
|
|
||
|
// Track the users currently cleaning up, and provide a latch that gets notified when all
|
||
|
// users have finished cleaning up. This is an imperfect system, as there can technically be
|
||
|
// multiple cleanups per user. Theoretically we should track the cleanup's BaseClientMonitor's
|
||
|
// unique ID, but it's complicated to plumb it through. This should be fine for now.
|
||
|
@Nullable private CountDownLatch mCloseLatch;
|
||
|
@NonNull private final ArraySet<Integer> mUsersCleaningUp;
|
||
|
|
||
|
private final ITestSessionCallback mCallback = new ITestSessionCallback.Stub() {
|
||
|
@Override
|
||
|
public void onCleanupStarted(int userId) {
|
||
|
Log.d(getTag(), "onCleanupStarted, sensor: " + mSensorId + ", userId: " + userId);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onCleanupFinished(int userId) {
|
||
|
Log.d(getTag(), "onCleanupFinished, sensor: " + mSensorId
|
||
|
+ ", userId: " + userId
|
||
|
+ ", remaining users: " + mUsersCleaningUp.size());
|
||
|
mUsersCleaningUp.remove(userId);
|
||
|
|
||
|
if (mUsersCleaningUp.isEmpty() && mCloseLatch != null) {
|
||
|
mCloseLatch.countDown();
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
public BiometricTestSession(@NonNull Context context, int sensorId,
|
||
|
@NonNull TestSessionProvider testSessionProvider) throws RemoteException {
|
||
|
mContext = context;
|
||
|
mSensorId = sensorId;
|
||
|
mTestSession = testSessionProvider.createTestSession(context, sensorId, mCallback);
|
||
|
mTestedUsers = new ArraySet<>();
|
||
|
mUsersCleaningUp = new ArraySet<>();
|
||
|
setTestHalEnabled(true);
|
||
|
Log.d(getTag(), "Opening BiometricTestSession");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Switches the specified sensor to use a test HAL. In this mode, the framework will not invoke
|
||
|
* any methods on the real HAL implementation. This allows the framework to test a substantial
|
||
|
* portion of the framework code that would otherwise require human interaction. Note that
|
||
|
* secure pathways such as HAT/Keystore are not testable, since they depend on the TEE or its
|
||
|
* equivalent for the secret key.
|
||
|
*
|
||
|
* @param enabled If true, enable testing with a fake HAL instead of the real HAL.
|
||
|
*/
|
||
|
@RequiresPermission(TEST_BIOMETRIC)
|
||
|
private void setTestHalEnabled(boolean enabled) {
|
||
|
try {
|
||
|
Log.w(getTag(), "setTestHalEnabled, sensor: " + mSensorId + " enabled: " + enabled);
|
||
|
mTestSession.setTestHalEnabled(enabled);
|
||
|
} catch (RemoteException e) {
|
||
|
throw e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Starts the enrollment process. This should generally be used when the test HAL is enabled.
|
||
|
*
|
||
|
* @param userId User that this command applies to.
|
||
|
*/
|
||
|
@RequiresPermission(TEST_BIOMETRIC)
|
||
|
public void startEnroll(int userId) {
|
||
|
try {
|
||
|
mTestedUsers.add(userId);
|
||
|
mTestSession.startEnroll(userId);
|
||
|
} catch (RemoteException e) {
|
||
|
throw e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Finishes the enrollment process. Simulates the HAL's callback.
|
||
|
*
|
||
|
* @param userId User that this command applies to.
|
||
|
*/
|
||
|
@RequiresPermission(TEST_BIOMETRIC)
|
||
|
public void finishEnroll(int userId) {
|
||
|
try {
|
||
|
mTestedUsers.add(userId);
|
||
|
mTestSession.finishEnroll(userId);
|
||
|
} catch (RemoteException e) {
|
||
|
throw e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Simulates a successful authentication, but does not provide a valid HAT.
|
||
|
*
|
||
|
* @param userId User that this command applies to.
|
||
|
*/
|
||
|
@RequiresPermission(TEST_BIOMETRIC)
|
||
|
public void acceptAuthentication(int userId) {
|
||
|
try {
|
||
|
mTestSession.acceptAuthentication(userId);
|
||
|
} catch (RemoteException e) {
|
||
|
throw e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Simulates a rejected attempt.
|
||
|
*
|
||
|
* @param userId User that this command applies to.
|
||
|
*/
|
||
|
@RequiresPermission(TEST_BIOMETRIC)
|
||
|
public void rejectAuthentication(int userId) {
|
||
|
try {
|
||
|
mTestSession.rejectAuthentication(userId);
|
||
|
} catch (RemoteException e) {
|
||
|
throw e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Simulates an acquired message from the HAL.
|
||
|
*
|
||
|
* @param userId User that this command applies to.
|
||
|
* @param acquireInfo See
|
||
|
* {@link BiometricPrompt.AuthenticationCallback#onAuthenticationAcquired(int)} and
|
||
|
* {@link FingerprintManager.AuthenticationCallback#onAuthenticationAcquired(int)}
|
||
|
*/
|
||
|
@RequiresPermission(TEST_BIOMETRIC)
|
||
|
public void notifyAcquired(int userId, int acquireInfo) {
|
||
|
try {
|
||
|
mTestSession.notifyAcquired(userId, acquireInfo);
|
||
|
} catch (RemoteException e) {
|
||
|
throw e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Simulates an error message from the HAL.
|
||
|
*
|
||
|
* @param userId User that this command applies to.
|
||
|
* @param errorCode See
|
||
|
* {@link BiometricPrompt.AuthenticationCallback#onAuthenticationError(int, CharSequence)} and
|
||
|
* {@link FingerprintManager.AuthenticationCallback#onAuthenticationError(int, CharSequence)}
|
||
|
*/
|
||
|
@RequiresPermission(TEST_BIOMETRIC)
|
||
|
public void notifyError(int userId, int errorCode) {
|
||
|
try {
|
||
|
mTestSession.notifyError(userId, errorCode);
|
||
|
} catch (RemoteException e) {
|
||
|
throw e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Matches the framework's cached enrollments against the HAL's enrollments. Any enrollment
|
||
|
* that isn't known by both sides are deleted. This should generally be used when the test
|
||
|
* HAL is disabled (e.g. to clean up after a test).
|
||
|
*
|
||
|
* @param userId User that this command applies to.
|
||
|
*/
|
||
|
@RequiresPermission(TEST_BIOMETRIC)
|
||
|
public void cleanupInternalState(int userId) {
|
||
|
try {
|
||
|
if (mUsersCleaningUp.contains(userId)) {
|
||
|
Log.w(getTag(), "Cleanup already in progress for user: " + userId);
|
||
|
}
|
||
|
|
||
|
mUsersCleaningUp.add(userId);
|
||
|
mTestSession.cleanupInternalState(userId);
|
||
|
} catch (RemoteException e) {
|
||
|
throw e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
@RequiresPermission(TEST_BIOMETRIC)
|
||
|
public void close() {
|
||
|
Log.d(getTag(), "Close, mTestedUsers size; " + mTestedUsers.size());
|
||
|
// Cleanup can be performed using the test HAL, since it always responds to enumerate with
|
||
|
// zero enrollments.
|
||
|
if (!mTestedUsers.isEmpty()) {
|
||
|
mCloseLatch = new CountDownLatch(1);
|
||
|
for (int user : mTestedUsers) {
|
||
|
cleanupInternalState(user);
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
Log.d(getTag(), "Awaiting latch...");
|
||
|
mCloseLatch.await(3, TimeUnit.SECONDS);
|
||
|
Log.d(getTag(), "Finished awaiting");
|
||
|
} catch (InterruptedException e) {
|
||
|
Log.e(getTag(), "Latch interrupted", e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!mUsersCleaningUp.isEmpty()) {
|
||
|
// TODO(b/186600837): this seems common on multi sensor devices
|
||
|
Log.e(getTag(), "Cleanup not finished before shutdown - pending: "
|
||
|
+ mUsersCleaningUp.size());
|
||
|
}
|
||
|
|
||
|
// Disable the test HAL after the sensor becomes idle.
|
||
|
setTestHalEnabled(false);
|
||
|
}
|
||
|
|
||
|
private String getTag() {
|
||
|
return BASE_TAG + "_" + mSensorId;
|
||
|
}
|
||
|
}
|