Merge changes If03d8ee3,I701d9d6f,I715e0e53 into sc-dev

* changes:
  Add a device orientation listener for updaing UDFPS layouts.
  Add modality switch callback to update UDFPS layout during configuration changes.
  Execute hide overlay immediatly.
This commit is contained in:
Joe Bolinger
2021-07-07 18:31:43 +00:00
committed by Android (Google) Code Review
10 changed files with 199 additions and 67 deletions

View File

@@ -90,6 +90,7 @@ public class AuthBiometricFaceToFingerprintView extends AuthBiometricFaceView {
}
@Modality private int mActiveSensorType = TYPE_FACE;
@Nullable private ModalityListener mModalityListener;
@Nullable private FingerprintSensorPropertiesInternal mFingerprintSensorProps;
@Nullable private UdfpsDialogMeasureAdapter mUdfpsMeasureAdapter;
@@ -115,6 +116,10 @@ public class AuthBiometricFaceToFingerprintView extends AuthBiometricFaceView {
return mFingerprintSensorProps.isAnyUdfpsType();
}
void setModalityListener(@NonNull ModalityListener listener) {
mModalityListener = listener;
}
void setFingerprintSensorProps(@NonNull FingerprintSensorPropertiesInternal sensorProps) {
mFingerprintSensorProps = sensorProps;
}
@@ -182,11 +187,16 @@ public class AuthBiometricFaceToFingerprintView extends AuthBiometricFaceView {
@Override
public void updateState(@BiometricState int newState) {
if (mState == STATE_HELP || mState == STATE_ERROR) {
@Modality final int currentType = mActiveSensorType;
mActiveSensorType = TYPE_FINGERPRINT;
setRequireConfirmation(false);
mConfirmButton.setEnabled(false);
mConfirmButton.setVisibility(View.GONE);
if (mModalityListener != null && currentType != mActiveSensorType) {
mModalityListener.onModalitySwitched(currentType, mActiveSensorType);
}
}
super.updateState(newState);

View File

@@ -759,6 +759,9 @@ public abstract class AuthBiometricView extends LinearLayout {
// Restore positive button(s) state
mConfirmButton.setVisibility(
mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_CONFIRM_VISIBILITY));
if (mConfirmButton.getVisibility() == View.GONE) {
setRequireConfirmation(false);
}
mTryAgainButton.setVisibility(
mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY));

View File

@@ -24,7 +24,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.PixelFormat;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.PromptInfo;
@@ -37,6 +36,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.UserManager;
import android.util.Log;
import android.view.Display;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -356,6 +356,12 @@ public class AuthContainerView extends LinearLayout
(AuthBiometricFaceToFingerprintView) factory.inflate(
R.layout.auth_biometric_face_to_fingerprint_view, null, false);
faceToFingerprintView.setFingerprintSensorProps(fingerprintSensorProps);
faceToFingerprintView.setModalityListener(new ModalityListener() {
@Override
public void onModalitySwitched(int oldModality, int newModality) {
maybeUpdatePositionForUdfps(true /* invalidate */);
}
});
mBiometricView = faceToFingerprintView;
} else {
Log.e(TAG, "Fingerprint props not found for sensor ID: " + fingerprintSensorId);
@@ -470,6 +476,11 @@ public class AuthContainerView extends LinearLayout
mPanelController.setContainerDimensions(getMeasuredWidth(), getMeasuredHeight());
}
@Override
public void onOrientationChanged() {
maybeUpdatePositionForUdfps(true /* invalidate */);
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -489,9 +500,7 @@ public class AuthContainerView extends LinearLayout
+ mConfig.mPromptInfo.getAuthenticators());
}
if (shouldUpdatePositionForUdfps()) {
updatePositionForUdfps();
}
maybeUpdatePositionForUdfps(false /* invalidate */);
if (mConfig.mSkipIntro) {
mContainerState = STATE_SHOWING;
@@ -536,14 +545,14 @@ public class AuthContainerView extends LinearLayout
}
}
private boolean shouldUpdatePositionForUdfps() {
if (mBiometricView instanceof AuthBiometricUdfpsView) {
private static boolean shouldUpdatePositionForUdfps(@NonNull View view) {
if (view instanceof AuthBiometricUdfpsView) {
return true;
}
if (mBiometricView instanceof AuthBiometricFaceToFingerprintView) {
if (view instanceof AuthBiometricFaceToFingerprintView) {
AuthBiometricFaceToFingerprintView faceToFingerprintView =
(AuthBiometricFaceToFingerprintView) mBiometricView;
(AuthBiometricFaceToFingerprintView) view;
return faceToFingerprintView.getActiveSensorType() == TYPE_FINGERPRINT
&& faceToFingerprintView.isFingerprintUdfps();
}
@@ -551,8 +560,16 @@ public class AuthContainerView extends LinearLayout
return false;
}
private void updatePositionForUdfps() {
final int displayRotation = getDisplay().getRotation();
private boolean maybeUpdatePositionForUdfps(boolean invalidate) {
final Display display = getDisplay();
if (display == null) {
return false;
}
if (!shouldUpdatePositionForUdfps(mBiometricView)) {
return false;
}
final int displayRotation = display.getRotation();
switch (displayRotation) {
case Surface.ROTATION_0:
mPanelController.setPosition(AuthPanelController.POSITION_BOTTOM);
@@ -576,6 +593,13 @@ public class AuthContainerView extends LinearLayout
setScrollViewGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
break;
}
if (invalidate) {
mPanelView.invalidateOutline();
mBiometricView.requestLayout();
}
return true;
}
private void setScrollViewGravity(int gravity) {
@@ -626,13 +650,6 @@ public class AuthContainerView extends LinearLayout
@Override
public void onAuthenticationFailed(@Modality int modality, String failureReason) {
mBiometricView.onAuthenticationFailed(modality, failureReason);
if (mBiometricView instanceof AuthBiometricFaceToFingerprintView
&& ((AuthBiometricFaceToFingerprintView) mBiometricView).isFingerprintUdfps()
&& modality == BiometricAuthenticator.TYPE_FACE) {
updatePositionForUdfps();
mPanelView.invalidateOutline();
mBiometricView.requestLayout();
}
}
@Override

View File

@@ -30,7 +30,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.graphics.PointF;
import android.graphics.RectF;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager.Authenticators;
@@ -49,7 +48,10 @@ import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import android.view.Display;
import android.view.MotionEvent;
import android.view.OrientationEventListener;
import android.view.Surface;
import android.view.WindowManager;
import com.android.internal.R;
@@ -97,17 +99,15 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
@VisibleForTesting
AuthDialog mCurrentDialog;
private WindowManager mWindowManager;
@Nullable
private UdfpsController mUdfpsController;
@Nullable
private IUdfpsHbmListener mUdfpsHbmListener;
@Nullable
private SidefpsController mSidefpsController;
@NonNull private final WindowManager mWindowManager;
@Nullable private UdfpsController mUdfpsController;
@Nullable private IUdfpsHbmListener mUdfpsHbmListener;
@Nullable private SidefpsController mSidefpsController;
@VisibleForTesting
TaskStackListener mTaskStackListener;
@VisibleForTesting
IBiometricSysuiReceiver mReceiver;
@NonNull private final BiometricOrientationEventListener mOrientationListener;
@Nullable private final List<FaceSensorPropertiesInternal> mFaceProps;
@Nullable private List<FingerprintSensorPropertiesInternal> mFpProps;
@Nullable private List<FingerprintSensorPropertiesInternal> mUdfpsProps;
@@ -120,6 +120,42 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
}
}
private class BiometricOrientationEventListener extends OrientationEventListener {
@Surface.Rotation private int mLastRotation;
BiometricOrientationEventListener(Context context) {
super(context);
mLastRotation = context.getDisplay().getRotation();
}
@Override
public void onOrientationChanged(int orientation) {
if (orientation == ORIENTATION_UNKNOWN) {
return;
}
final Display display = mContext.getDisplay();
if (display == null) {
return;
}
final int rotation = display.getRotation();
if (mLastRotation != rotation) {
mLastRotation = rotation;
if (mCurrentDialog != null) {
mCurrentDialog.onOrientationChanged();
}
if (mUdfpsController != null) {
mUdfpsController.onOrientationChanged();
}
if (mSidefpsController != null) {
mSidefpsController.onOrientationChanged();
}
}
}
}
@NonNull
private final IFingerprintAuthenticatorsRegisteredCallback
mFingerprintAuthenticatorsRegisteredCallback =
@@ -164,6 +200,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
Log.w(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received");
mCurrentDialog.dismissWithoutCallback(true /* animate */);
mCurrentDialog = null;
mOrientationListener.disable();
try {
if (mReceiver != null) {
@@ -192,6 +229,8 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
Log.w(TAG, "Evicting client due to: " + topPackage);
mCurrentDialog.dismissWithoutCallback(true /* animate */);
mCurrentDialog = null;
mOrientationListener.disable();
if (mReceiver != null) {
mReceiver.onDialogDismissed(
BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
@@ -339,15 +378,6 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
}
}
/**
* @return where the UDFPS exists on the screen in pixels in portrait mode.
*/
@Nullable public RectF getUdfpsRegion() {
return mUdfpsController == null
? null
: mUdfpsController.getSensorLocation();
}
/**
* @return where the UDFPS exists on the screen in pixels in portrait mode.
*/
@@ -422,8 +452,10 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
}
@Inject
public AuthController(Context context, CommandQueue commandQueue,
public AuthController(Context context,
CommandQueue commandQueue,
ActivityTaskManager activityTaskManager,
@NonNull WindowManager windowManager,
@Nullable FingerprintManager fingerprintManager,
@Nullable FaceManager faceManager,
Provider<UdfpsController> udfpsControllerFactory,
@@ -435,6 +467,8 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
mFaceManager = faceManager;
mUdfpsControllerFactory = udfpsControllerFactory;
mSidefpsControllerFactory = sidefpsControllerFactory;
mWindowManager = windowManager;
mOrientationListener = new BiometricOrientationEventListener(context);
mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null;
@@ -462,7 +496,6 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
@Override
public void start() {
mCommandQueue.addCallback(this);
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
if (mFingerprintManager != null) {
mFingerprintManager.addAuthenticatorsRegisteredCallback(
@@ -630,6 +663,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
// BiometricService will have already sent the callback to the client in this case.
// This avoids a round trip to SystemUI. So, just dismiss the dialog and we're done.
mCurrentDialog = null;
mOrientationListener.disable();
}
/**
@@ -701,6 +735,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
mReceiver = (IBiometricSysuiReceiver) args.arg2;
mCurrentDialog = newDialog;
mCurrentDialog.show(mWindowManager, savedState);
mOrientationListener.enable();
}
private void onDialogDismissed(@DismissedReason int reason) {
@@ -710,20 +745,12 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
}
mReceiver = null;
mCurrentDialog = null;
mOrientationListener.disable();
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// UdfpsController is not BiometricPrompt-specific. It can be active for keyguard or
// enrollment.
if (mUdfpsController != null) {
mUdfpsController.onConfigurationChanged();
}
if (mSidefpsController != null) {
mSidefpsController.onConfigurationChanged();
}
// Save the state of the current dialog (buttons showing, etc)
if (mCurrentDialog != null) {
@@ -731,6 +758,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
mCurrentDialog.onSaveState(savedState);
mCurrentDialog.dismissWithoutCallback(false /* animate */);
mCurrentDialog = null;
mOrientationListener.disable();
// Only show the dialog if necessary. If it was animating out, the dialog is supposed
// to send its pending callback immediately.

View File

@@ -156,4 +156,12 @@ public interface AuthDialog {
* @return true if device credential is allowed.
*/
boolean isAllowDeviceCredentials();
/**
* Called when the device's orientation changed and the dialog may need to do another
* layout. This is most relevant to UDFPS since configuration changes are not sent by
* the framework in equivalent cases (landscape to reverse landscape) but the dialog
* must remain fixed on the physical sensor location.
*/
void onOrientationChanged();
}

View File

@@ -0,0 +1,36 @@
/*
* 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.systemui.biometrics;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
/**
* Listener for events related to modality changes during operations.
*
* Used by views such as {@link AuthBiometricFaceToFingerprintView} that support fallback style
* authentication.
*/
public interface ModalityListener {
/**
* The modality has changed. Called after the transition has been fully completed.
*
* @param oldModality original modality
* @param newModality current modality
*/
default void onModalitySwitched(@Modality int oldModality, @Modality int newModality) {}
}

View File

@@ -137,8 +137,7 @@ public class SidefpsController {
}
}
void onConfigurationChanged() {
void onOrientationChanged() {
// If mView is null or if view is hidden, then return.
if (mView == null || !mIsVisible) {
return;

View File

@@ -650,7 +650,7 @@ public class UdfpsController implements DozeReceiver {
return mCoreLayoutParams;
}
void onConfigurationChanged() {
void onOrientationChanged() {
// When the configuration changes it's almost always necessary to destroy and re-create
// the overlay's window to pass it the new LayoutParams.
// Hiding the overlay will destroy its window. It's safe to hide the overlay regardless
@@ -663,6 +663,7 @@ public class UdfpsController implements DozeReceiver {
private void showUdfpsOverlay(@NonNull ServerRequest request) {
mExecution.assertIsMainThread();
final int reason = request.mRequestReason;
if (mView == null) {
try {
@@ -751,22 +752,22 @@ public class UdfpsController implements DozeReceiver {
}
private void hideUdfpsOverlay() {
mFgExecutor.execute(() -> {
if (mView != null) {
Log.v(TAG, "hideUdfpsOverlay | removing window");
// Reset the controller back to its starting state.
onFingerUp();
mWindowManager.removeView(mView);
mView.setOnTouchListener(null);
mView.setOnHoverListener(null);
mView.setAnimationViewController(null);
mAccessibilityManager.removeTouchExplorationStateChangeListener(
mTouchExplorationStateChangeListener);
mView = null;
} else {
Log.v(TAG, "hideUdfpsOverlay | the overlay is already hidden");
}
});
mExecution.assertIsMainThread();
if (mView != null) {
Log.v(TAG, "hideUdfpsOverlay | removing window");
// Reset the controller back to its starting state.
onFingerUp();
mWindowManager.removeView(mView);
mView.setOnTouchListener(null);
mView.setOnHoverListener(null);
mView.setAnimationViewController(null);
mAccessibilityManager.removeTouchExplorationStateChangeListener(
mTouchExplorationStateChangeListener);
mView = null;
} else {
Log.v(TAG, "hideUdfpsOverlay | the overlay is already hidden");
}
}
/**

View File

@@ -137,6 +137,31 @@ public class AuthBiometricFaceToFingerprintViewTest extends SysuiTestCase {
verify(mConfirmButton).setVisibility(eq(View.GONE));
}
@Test
public void testStateUpdated_whenSwitchToFingerprint_invokesCallbacks() {
class TestModalityListener implements ModalityListener {
public int switchCount = 0;
@Override
public void onModalitySwitched(int oldModality, int newModality) {
assertEquals(TYPE_FINGERPRINT, newModality);
assertEquals(TYPE_FACE, oldModality);
switchCount++;
}
}
final TestModalityListener modalityListener = new TestModalityListener();
mFaceToFpView.onDialogAnimatedIn();
mFaceToFpView.setModalityListener(modalityListener);
assertEquals(0, modalityListener.switchCount);
mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_ERROR);
mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING);
assertEquals(1, modalityListener.switchCount);
}
@Test
public void testModeUpdated_onSoftError_whenSwitchToFingerprint() {
mFaceToFpView.onDialogAnimatedIn();

View File

@@ -59,6 +59,7 @@ import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper.RunWithLooper;
import android.view.WindowManager;
import com.android.internal.R;
import com.android.systemui.SysuiTestCase;
@@ -97,6 +98,8 @@ public class AuthControllerTest extends SysuiTestCase {
@Mock
private ActivityTaskManager mActivityTaskManager;
@Mock
private WindowManager mWindowManager;
@Mock
private FingerprintManager mFingerprintManager;
@Mock
private FaceManager mFaceManager;
@@ -149,7 +152,7 @@ public class AuthControllerTest extends SysuiTestCase {
when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
mAuthController = new TestableAuthController(context, mCommandQueue,
mActivityTaskManager, mFingerprintManager, mFaceManager,
mActivityTaskManager, mWindowManager, mFingerprintManager, mFaceManager,
() -> mUdfpsController, () -> mSidefpsController);
mAuthController.start();
@@ -576,13 +579,15 @@ public class AuthControllerTest extends SysuiTestCase {
private int mBuildCount = 0;
private PromptInfo mLastBiometricPromptInfo;
TestableAuthController(Context context, CommandQueue commandQueue,
TestableAuthController(Context context,
CommandQueue commandQueue,
ActivityTaskManager activityTaskManager,
WindowManager windowManager,
FingerprintManager fingerprintManager,
FaceManager faceManager,
Provider<UdfpsController> udfpsControllerFactory,
Provider<SidefpsController> sidefpsControllerFactory) {
super(context, commandQueue, activityTaskManager,
super(context, commandQueue, activityTaskManager, windowManager,
fingerprintManager, faceManager, udfpsControllerFactory,
sidefpsControllerFactory);
}