Merge "Add orientation listener to all biometric overlays." into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
b172012858
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user