Merge "Add orientation listener to all biometric overlays." into sc-dev

This commit is contained in:
TreeHugger Robot
2021-07-10 02:23:23 +00:00
committed by Android (Google) Code Review
7 changed files with 251 additions and 46 deletions

View File

@@ -48,10 +48,7 @@ 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;
@@ -72,6 +69,8 @@ import java.util.Set;
import javax.inject.Inject;
import javax.inject.Provider;
import kotlin.Unit;
/**
* Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the
* appropriate biometric UI (e.g. BiometricDialogView).
@@ -107,7 +106,8 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
TaskStackListener mTaskStackListener;
@VisibleForTesting
IBiometricSysuiReceiver mReceiver;
@NonNull private final BiometricOrientationEventListener mOrientationListener;
@VisibleForTesting
@NonNull final BiometricOrientationEventListener mOrientationListener;
@Nullable private final List<FaceSensorPropertiesInternal> mFaceProps;
@Nullable private List<FingerprintSensorPropertiesInternal> mFpProps;
@Nullable private List<FingerprintSensorPropertiesInternal> mUdfpsProps;
@@ -120,42 +120,6 @@ 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 =
@@ -468,7 +432,10 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
mUdfpsControllerFactory = udfpsControllerFactory;
mSidefpsControllerFactory = sidefpsControllerFactory;
mWindowManager = windowManager;
mOrientationListener = new BiometricOrientationEventListener(context);
mOrientationListener = new BiometricOrientationEventListener(context, () -> {
onOrientationChanged();
return Unit.INSTANCE;
});
mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null;
@@ -790,6 +757,12 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
}
}
private void onOrientationChanged() {
if (mCurrentDialog != null) {
mCurrentDialog.onOrientationChanged();
}
}
protected AuthDialog buildDialog(PromptInfo promptInfo, boolean requireConfirmation,
int userId, int[] sensorIds, boolean credentialAllowed, String opPackageName,
boolean skipIntro, long operationId,

View File

@@ -0,0 +1,60 @@
/*
* 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.content.Context
import android.view.OrientationEventListener
/**
* An [OrientationEventListener] that invokes the [onOrientationChanged] callback whenever
* the orientation of the device has changed in order to keep overlays for biometric sensors
* aligned with the device's screen.
*/
class BiometricOrientationEventListener(
private val context: Context,
private val onOrientationChanged: () -> Unit
) : OrientationEventListener(context) {
/** If actively listening (not available in base class). */
var enabled: Boolean = false
private set
private var lastRotation = context.display?.rotation ?: ORIENTATION_UNKNOWN
override fun onOrientationChanged(orientation: Int) {
if (orientation == ORIENTATION_UNKNOWN) {
return
}
val rotation = context.display?.rotation ?: return
if (lastRotation != rotation) {
lastRotation = rotation
onOrientationChanged()
}
}
override fun enable() {
enabled = true
super.enable()
}
override fun disable() {
enabled = false
super.disable()
}
}

View File

@@ -41,6 +41,8 @@ import com.android.systemui.util.concurrency.DelayableExecutor;
import javax.inject.Inject;
import kotlin.Unit;
/**
* Shows and hides the side fingerprint sensor (side-fps) overlay and handles side fps touch events.
*/
@@ -52,6 +54,8 @@ public class SidefpsController {
private final FingerprintManager mFingerprintManager;
private final WindowManager mWindowManager;
private final DelayableExecutor mFgExecutor;
@VisibleForTesting @NonNull final BiometricOrientationEventListener mOrientationListener;
// TODO: update mDisplayHeight and mDisplayWidth for multi-display devices
private final int mDisplayHeight;
private final int mDisplayWidth;
@@ -95,6 +99,10 @@ public class SidefpsController {
mFingerprintManager = checkNotNull(fingerprintManager);
mWindowManager = windowManager;
mFgExecutor = fgExecutor;
mOrientationListener = new BiometricOrientationEventListener(context, () -> {
onOrientationChanged();
return Unit.INSTANCE;
});
mSensorProps = findFirstSidefps();
checkArgument(mSensorProps != null);
@@ -119,14 +127,15 @@ public class SidefpsController {
mFingerprintManager.setSidefpsController(mSidefpsControllerImpl);
}
void show() {
private void show() {
mView = (SidefpsView) mInflater.inflate(R.layout.sidefps_view, null, false);
mView.setSensorProperties(mSensorProps);
mWindowManager.addView(mView, computeLayoutParams());
mOrientationListener.enable();
}
void hide() {
private void hide() {
if (mView != null) {
mWindowManager.removeView(mView);
mView.setOnTouchListener(null);
@@ -135,13 +144,16 @@ public class SidefpsController {
} else {
Log.v(TAG, "hideUdfpsOverlay | the overlay is already hidden");
}
mOrientationListener.disable();
}
void onOrientationChanged() {
private void onOrientationChanged() {
// If mView is null or if view is hidden, then return.
if (mView == null || !mIsVisible) {
return;
}
// If the overlay needs to be displayed with a new configuration, destroy the current
// overlay, and re-create and show the overlay with the updated LayoutParams.
hide();

View File

@@ -79,6 +79,8 @@ import java.util.Optional;
import javax.inject.Inject;
import kotlin.Unit;
/**
* Shows and hides the under-display fingerprint sensor (UDFPS) overlay, handles UDFPS touch events,
* and coordinates triggering of the high-brightness mode (HBM).
@@ -118,6 +120,7 @@ public class UdfpsController implements DozeReceiver {
@NonNull private final AccessibilityManager mAccessibilityManager;
@NonNull private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
@Nullable private final UdfpsHbmProvider mHbmProvider;
@VisibleForTesting @NonNull final BiometricOrientationEventListener mOrientationListener;
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
@VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps;
@@ -511,6 +514,10 @@ public class UdfpsController implements DozeReceiver {
mHbmProvider = hbmProvider.orElse(null);
screenLifecycle.addObserver(mScreenObserver);
mScreenOn = screenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_ON;
mOrientationListener = new BiometricOrientationEventListener(context, () -> {
onOrientationChanged();
return Unit.INSTANCE;
});
mSensorProps = findFirstUdfps();
// At least one UDFPS sensor exists
@@ -650,7 +657,7 @@ public class UdfpsController implements DozeReceiver {
return mCoreLayoutParams;
}
void onOrientationChanged() {
private 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
@@ -668,6 +675,7 @@ public class UdfpsController implements DozeReceiver {
if (mView == null) {
try {
Log.v(TAG, "showUdfpsOverlay | adding window reason=" + reason);
mView = (UdfpsView) mInflater.inflate(R.layout.udfps_view, null, false);
mOnFingerDown = false;
mView.setSensorProperties(mSensorProps);
@@ -675,6 +683,7 @@ public class UdfpsController implements DozeReceiver {
UdfpsAnimationViewController animation = inflateUdfpsAnimation(reason);
animation.init();
mView.setAnimationViewController(animation);
mOrientationListener.enable();
// This view overlaps the sensor area, so prevent it from being selectable
// during a11y.
@@ -768,6 +777,8 @@ public class UdfpsController implements DozeReceiver {
} else {
Log.v(TAG, "hideUdfpsOverlay | the overlay is already hidden");
}
mOrientationListener.disable();
}
/**

View File

@@ -20,7 +20,9 @@ import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -55,12 +57,13 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.os.Bundle;
import android.os.RemoteException;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper.RunWithLooper;
import android.view.WindowManager;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.CommandQueue;
@@ -539,6 +542,17 @@ public class AuthControllerTest extends SysuiTestCase {
verify(mUdfpsController).onAodInterrupt(eq(pos), eq(pos), eq(majorMinor), eq(majorMinor));
}
@Test
public void testSubscribesToOrientationChangesWhenShowingDialog() {
assertFalse(mAuthController.mOrientationListener.getEnabled());
showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
assertTrue(mAuthController.mOrientationListener.getEnabled());
mAuthController.hideAuthenticationDialog();
assertFalse(mAuthController.mOrientationListener.getEnabled());
}
// Helpers
private void showDialog(int[] sensorIds, boolean credentialAllowed) {

View File

@@ -0,0 +1,117 @@
/*
* 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.SensorProperties
import android.hardware.display.DisplayManagerGlobal
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.hardware.fingerprint.ISidefpsController
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.Display
import android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
import android.view.DisplayInfo
import android.view.LayoutInflater
import android.view.WindowManager
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
private const val DISPLAY_ID = 2
private const val SENSOR_ID = 1
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class SidefpsControllerTest : SysuiTestCase() {
@JvmField @Rule
var rule = MockitoJUnit.rule()
@Mock
lateinit var layoutInflater: LayoutInflater
@Mock
lateinit var fingerprintManager: FingerprintManager
@Mock
lateinit var windowManager: WindowManager
@Mock
lateinit var sidefpsView: SidefpsView
private val executor = FakeExecutor(FakeSystemClock())
private lateinit var overlayController: ISidefpsController
private lateinit var sideFpsController: SidefpsController
@Before
fun setup() {
`when`(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sidefpsView)
`when`(fingerprintManager.sensorPropertiesInternal).thenReturn(
listOf(
FingerprintSensorPropertiesInternal(
SENSOR_ID,
SensorProperties.STRENGTH_STRONG,
5 /* maxEnrollmentsPerUser */,
listOf() /* componentInfo */,
FingerprintSensorProperties.TYPE_POWER_BUTTON,
true /* resetLockoutRequiresHardwareAuthToken */
)
)
)
`when`(windowManager.defaultDisplay).thenReturn(
Display(
DisplayManagerGlobal.getInstance(),
DISPLAY_ID,
DisplayInfo(),
DEFAULT_DISPLAY_ADJUSTMENTS
)
)
sideFpsController = SidefpsController(
mContext, layoutInflater, fingerprintManager, windowManager, executor
)
overlayController = ArgumentCaptor.forClass(ISidefpsController::class.java).apply {
verify(fingerprintManager).setSidefpsController(capture())
}.value
}
@Test
fun testSubscribesToOrientationChangesWhenShowingOverlay() {
assertThat(sideFpsController.mOrientationListener.enabled).isFalse()
overlayController.show()
executor.runAllReady()
assertThat(sideFpsController.mOrientationListener.enabled).isTrue()
overlayController.hide()
executor.runAllReady()
assertThat(sideFpsController.mOrientationListener.enabled).isFalse()
}
}

View File

@@ -17,6 +17,8 @@
package com.android.systemui.biometrics;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -235,6 +237,22 @@ public class UdfpsControllerTest extends SysuiTestCase {
verify(mWindowManager).removeView(eq(mUdfpsView));
}
@Test
public void testSubscribesToOrientationChangesWhenShowingOverlay() throws Exception {
assertFalse(mUdfpsController.mOrientationListener.getEnabled());
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
assertTrue(mUdfpsController.mOrientationListener.getEnabled());
mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
mFgExecutor.runAllReady();
assertFalse(mUdfpsController.mOrientationListener.getEnabled());
}
@Test
public void fingerDown() throws RemoteException {
// Configure UdfpsView to accept the ACTION_DOWN event