Merge changes from topic "udfps-transition" into sc-dev

* changes:
  Hide Udfps Overlay together with system transitions
  Slight cleanup for UdfpsController
This commit is contained in:
Kevin Chyn
2021-03-09 02:26:15 +00:00
committed by Android (Google) Code Review
13 changed files with 272 additions and 54 deletions

View File

@@ -15,6 +15,8 @@
*/
package android.hardware.fingerprint;
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
/**
* Interface for interacting with the under-display fingerprint sensor (UDFPS) overlay.
* @hide
@@ -28,7 +30,7 @@ oneway interface IUdfpsOverlayController {
const int REASON_AUTH_FPM_OTHER = 5; // Other FingerprintManager usage
// Shows the overlay.
void showUdfpsOverlay(int sensorId, int reason);
void showUdfpsOverlay(int sensorId, int reason, IUdfpsOverlayControllerCallback callback);
// Hides the overlay.
void hideUdfpsOverlay(int sensorId);

View File

@@ -0,0 +1,24 @@
/*
* 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 android.hardware.fingerprint;
/**
* @hide
*/
oneway interface IUdfpsOverlayControllerCallback {
// Notify system_server if the user cancels a UDFPS-related operation (enroll, auth)
void onUserCanceled();
}

View File

@@ -20,7 +20,10 @@ import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkNotNull;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.graphics.Point;
@@ -29,6 +32,8 @@ import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.SystemClock;
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
import android.os.RemoteException;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
@@ -91,11 +96,8 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
private long mTouchLogTime;
@Nullable private UdfpsView mView;
// Indicates whether the overlay has been requested.
private boolean mIsOverlayRequested;
// Reason the overlay has been requested. See IUdfpsOverlayController for definitions.
private int mRequestReason;
@Nullable UdfpsEnrollHelper mEnrollHelper;
// The current request from FingerprintService. Null if no current request.
@Nullable ServerRequest mServerRequest;
// The fingerprint AOD trigger doesn't provide an ACTION_UP/ACTION_CANCEL event to tell us when
// to turn off high brightness mode. To get around this limitation, the state of the AOD
@@ -104,39 +106,84 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
private boolean mIsAodInterruptActive;
@Nullable private Runnable mCancelAodTimeoutAction;
/**
* Keeps track of state within a single FingerprintService request. Note that this state
* persists across configuration changes, etc, since it is considered a single request.
*
* TODO: Perhaps we can move more global variables into here
*/
private static class ServerRequest {
// Reason the overlay has been requested. See IUdfpsOverlayController for definitions.
final int mRequestReason;
@NonNull final IUdfpsOverlayControllerCallback mCallback;
@Nullable final UdfpsEnrollHelper mEnrollHelper;
ServerRequest(int requestReason, @NonNull IUdfpsOverlayControllerCallback callback,
@Nullable UdfpsEnrollHelper enrollHelper) {
mRequestReason = requestReason;
mCallback = callback;
mEnrollHelper = enrollHelper;
}
void onEnrollmentProgress(int remaining) {
if (mEnrollHelper != null) {
mEnrollHelper.onEnrollmentProgress(remaining);
}
}
void onEnrollmentHelp() {
if (mEnrollHelper != null) {
mEnrollHelper.onEnrollmentHelp();
}
}
void onUserCanceled() {
try {
mCallback.onUserCanceled();
} catch (RemoteException e) {
Log.e(TAG, "Remote exception", e);
}
}
}
public class UdfpsOverlayController extends IUdfpsOverlayController.Stub {
@Override
public void showUdfpsOverlay(int sensorId, int reason) {
public void showUdfpsOverlay(int sensorId, int reason,
@NonNull IUdfpsOverlayControllerCallback callback) {
final UdfpsEnrollHelper enrollHelper;
if (reason == IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR
|| reason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING) {
mEnrollHelper = new UdfpsEnrollHelper(mContext, reason);
enrollHelper = new UdfpsEnrollHelper(mContext, reason);
} else {
mEnrollHelper = null;
enrollHelper = null;
}
UdfpsController.this.showOverlay(reason);
mServerRequest = new ServerRequest(reason, callback, enrollHelper);
updateOverlay();
}
@Override
public void hideUdfpsOverlay(int sensorId) {
UdfpsController.this.hideOverlay();
mServerRequest = null;
updateOverlay();
}
@Override
public void onEnrollmentProgress(int sensorId, int remaining) {
if (mEnrollHelper == null) {
Log.e(TAG, "onEnrollProgress received but helper is null");
if (mServerRequest == null) {
Log.e(TAG, "onEnrollProgress received but serverRequest is null");
return;
}
mEnrollHelper.onEnrollmentProgress(remaining);
mServerRequest.onEnrollmentProgress(remaining);
}
@Override
public void onEnrollmentHelp(int sensorId) {
if (mEnrollHelper == null) {
Log.e(TAG, "onEnrollmentHelp received but helper is null");
if (mServerRequest == null) {
Log.e(TAG, "onEnrollmentHelp received but serverRequest is null");
return;
}
mEnrollHelper.onEnrollmentHelp();
mServerRequest.onEnrollmentHelp();
}
@Override
@@ -165,6 +212,19 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
return (float) Math.sqrt(Math.pow(vx, 2.0) + Math.pow(vy, 2.0));
}
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (mServerRequest != null
&& Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
Log.d(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received");
mServerRequest.onUserCanceled();
mServerRequest = null;
updateOverlay();
}
}
};
@SuppressLint("ClickableViewAccessibility")
private final UdfpsView.OnTouchListener mOnTouchListener = (view, event) -> {
UdfpsView udfpsView = (UdfpsView) view;
@@ -280,6 +340,10 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
mCoreLayoutParams.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
mFingerprintManager.setUdfpsOverlayController(new UdfpsOverlayController());
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
context.registerReceiver(mBroadcastReceiver, filter);
}
@Nullable
@@ -314,27 +378,9 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
mSensorProps.sensorLocationY + mSensorProps.sensorRadius);
}
private void showOverlay(int reason) {
if (mIsOverlayRequested) {
return;
}
mIsOverlayRequested = true;
mRequestReason = reason;
updateOverlay();
}
private void hideOverlay() {
if (!mIsOverlayRequested) {
return;
}
mIsOverlayRequested = false;
mRequestReason = IUdfpsOverlayController.REASON_UNKNOWN;
updateOverlay();
}
private void updateOverlay() {
if (mIsOverlayRequested) {
showUdfpsOverlay(mRequestReason);
if (mServerRequest != null) {
showUdfpsOverlay(mServerRequest.mRequestReason);
} else {
hideUdfpsOverlay();
}
@@ -427,7 +473,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
case IUdfpsOverlayController.REASON_ENROLL_ENROLLING: {
final UdfpsAnimationViewEnroll view = (UdfpsAnimationViewEnroll)
inflater.inflate(R.layout.udfps_animation_view_enroll, null, false);
view.setEnrollHelper(mEnrollHelper);
view.setEnrollHelper(mServerRequest.mEnrollHelper);
return view;
}

View File

@@ -31,6 +31,7 @@ import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
import android.os.PowerManager;
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
@@ -89,6 +90,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
private StatusBarStateController mStatusBarStateController;
@Mock
private StatusBar mStatusBar;
@Mock
private IUdfpsOverlayControllerCallback mUdfpsOverlayControllerCallback;
private FakeExecutor mFgExecutor;
@@ -152,7 +155,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Test
public void dozeTimeTick() throws RemoteException {
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD);
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
mUdfpsController.dozeTimeTick();
verify(mUdfpsView).dozeTimeTick();
@@ -161,7 +164,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Test
public void showUdfpsOverlay_addsViewToWindow() throws RemoteException {
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD);
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
verify(mWindowManager).addView(eq(mUdfpsView), any());
}
@@ -169,7 +172,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Test
public void hideUdfpsOverlay_removesViewFromWindow() throws RemoteException {
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD);
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
mFgExecutor.runAllReady();
verify(mWindowManager).removeView(eq(mUdfpsView));
@@ -183,7 +186,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
// GIVEN that the overlay is showing
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD);
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
// WHEN ACTION_DOWN is received
verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
@@ -205,7 +208,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
public void aodInterrupt() throws RemoteException {
// GIVEN that the overlay is showing
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD);
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
// WHEN fingerprint is requested because of AOD interrupt
mUdfpsController.onAodInterrupt(0, 0, 2f, 3f);
@@ -221,7 +224,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
public void cancelAodInterrupt() throws RemoteException {
// GIVEN AOD interrupt
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD);
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
// WHEN it is cancelled
@@ -234,7 +237,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
public void aodInterruptTimeout() throws RemoteException {
// GIVEN AOD interrupt
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD);
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
// WHEN it times out
@@ -247,7 +250,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Test
public void registersAndUnregistersViewForCallbacks() throws RemoteException {
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD);
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
verify(mStatusBarStateController).addCallback(mUdfpsController.mStatusBarStateListener);
verify(mStatusBar).addExpansionChangedListener(

View File

@@ -80,6 +80,18 @@ public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implement
onErrorInternal(errorCode, vendorCode, true /* finish */);
}
/**
* Notifies the caller that the operation was canceled by the user. Note that the actual
* operation still needs to wait for the HAL to send ERROR_CANCELED.
*/
public void onUserCanceled() {
// Send USER_CANCELED, but do not finish. Wait for the HAL to respond with ERROR_CANCELED,
// which then finishes the AcquisitionClient's lifecycle.
onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED, 0 /* vendorCode */,
false /* finish */);
stopHalOperation();
}
protected void onErrorInternal(int errorCode, int vendorCode, boolean finish) {
// In some cases, the framework will send an error to the caller before a true terminal
// case (success, failure, or error) is received from the HAL (e.g. versions of fingerprint

View File

@@ -22,9 +22,12 @@ import android.content.Context;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
import android.os.RemoteException;
import android.util.Slog;
import com.android.server.biometrics.sensors.AcquisitionClient;
/**
* Contains helper methods for under-display fingerprint HIDL.
*/
@@ -77,12 +80,22 @@ public class UdfpsHelper {
}
public static void showUdfpsOverlay(int sensorId, int reason,
@Nullable IUdfpsOverlayController udfpsOverlayController) {
@Nullable IUdfpsOverlayController udfpsOverlayController,
@NonNull AcquisitionClient<?> client) {
if (udfpsOverlayController == null) {
return;
}
final IUdfpsOverlayControllerCallback callback =
new IUdfpsOverlayControllerCallback.Stub() {
@Override
public void onUserCanceled() {
client.onUserCanceled();
}
};
try {
udfpsOverlayController.showUdfpsOverlay(sensorId, reason);
udfpsOverlayController.showUdfpsOverlay(sensorId, reason, callback);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when showing the UDFPS overlay", e);
}

View File

@@ -89,7 +89,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp
@Override
protected void startHalOperation() {
UdfpsHelper.showUdfpsOverlay(getSensorId(), Utils.getUdfpsAuthReason(this),
mUdfpsOverlayController);
mUdfpsOverlayController, this);
try {
mCancellationSignal = getFreshDaemon().authenticate(mSequentialId, mOperationId);
} catch (RemoteException e) {

View File

@@ -72,7 +72,7 @@ class FingerprintDetectClient extends AcquisitionClient<ISession> {
protected void startHalOperation() {
UdfpsHelper.showUdfpsOverlay(getSensorId(),
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD,
mUdfpsOverlayController);
mUdfpsOverlayController, this);
try {
mCancellationSignal = getFreshDaemon().detectInteraction(mSequentialId);
} catch (RemoteException e) {

View File

@@ -121,7 +121,7 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps {
protected void startHalOperation() {
UdfpsHelper.showUdfpsOverlay(getSensorId(),
UdfpsHelper.getReasonFromEnrollReason(mEnrollReason),
mUdfpsOverlayController);
mUdfpsOverlayController, this);
try {
mCancellationSignal = getFreshDaemon().enroll(mSequentialId,
HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken));

View File

@@ -120,7 +120,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
@Override
protected void startHalOperation() {
UdfpsHelper.showUdfpsOverlay(getSensorId(), Utils.getUdfpsAuthReason(this),
mUdfpsOverlayController);
mUdfpsOverlayController, this);
try {
// GroupId was never used. In fact, groupId is always the same as userId.
getFreshDaemon().authenticate(mOperationId, getTargetUserId());

View File

@@ -85,7 +85,7 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint>
protected void startHalOperation() {
UdfpsHelper.showUdfpsOverlay(getSensorId(),
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD,
mUdfpsOverlayController);
mUdfpsOverlayController, this);
try {
getFreshDaemon().authenticate(0 /* operationId */, getTargetUserId());
} catch (RemoteException e) {

View File

@@ -84,7 +84,7 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint
protected void startHalOperation() {
UdfpsHelper.showUdfpsOverlay(getSensorId(),
UdfpsHelper.getReasonFromEnrollReason(mEnrollReason),
mUdfpsOverlayController);
mUdfpsOverlayController, this);
try {
// GroupId was never used. In fact, groupId is always the same as userId.
getFreshDaemon().enroll(mHardwareAuthToken, getTargetUserId(), mTimeoutSec);

View File

@@ -0,0 +1,118 @@
/*
* 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.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.content.Context;
import android.hardware.biometrics.BiometricConstants;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import androidx.annotation.NonNull;
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 AcquisitionClientTest {
private static final int TEST_SENSOR_ID = 100;
@Mock
private Context mContext;
@Mock
private IBinder mToken;
@Mock
private ClientMonitorCallbackConverter mClientCallback;
@Mock
private BaseClientMonitor.Callback mSchedulerCallback;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testUserCanceled() throws Exception {
// Start an AcquisitionClient
final TestAcquisitionClient client = new TestAcquisitionClient(mContext, Object::new,
mToken, mClientCallback);
client.start(mSchedulerCallback);
assertTrue(client.mHalOperationRunning);
verify(mSchedulerCallback).onClientStarted(eq(client));
// Pretend that it got canceled by the user.
client.onUserCanceled();
verify(mSchedulerCallback, never()).onClientFinished(any(), anyBoolean());
verify(mClientCallback).onError(eq(TEST_SENSOR_ID),
anyInt(),
eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
eq(0) /* vendorCode */);
assertFalse(client.mHalOperationRunning);
// Pretend that the HAL responded with ERROR_CANCELED
client.onError(BiometricConstants.BIOMETRIC_ERROR_CANCELED, 0 /* vendorCode */);
verifyNoMoreInteractions(mClientCallback);
verify(mSchedulerCallback).onClientFinished(eq(client), anyBoolean());
}
private static class TestAcquisitionClient extends AcquisitionClient<Object> {
boolean mHalOperationRunning;
public TestAcquisitionClient(@NonNull Context context,
@NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
@NonNull ClientMonitorCallbackConverter callback) {
super(context, lazyDaemon, token, callback, 0 /* userId */, "Test", 0 /* cookie */,
TEST_SENSOR_ID /* sensorId */, 0 /* statsModality */, 0 /* statsAction */,
0 /* statsClient */);
}
@Override
public void start(@NonNull Callback callback) {
super.start(callback);
startHalOperation();
}
@Override
protected void stopHalOperation() {
mHalOperationRunning = false;
}
@Override
protected void startHalOperation() {
mHalOperationRunning = true;
}
@Override
public int getProtoEnum() {
return 0;
}
}
}