diff options
7 files changed, 251 insertions, 46 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index badad9d86a0f..71e2bb657de4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -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, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricOrientationEventListener.kt b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricOrientationEventListener.kt new file mode 100644 index 000000000000..08ea857eb208 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricOrientationEventListener.kt @@ -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() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java index 436e1e4e3116..a51c2b802b91 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java @@ -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(); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 710aca038569..f9103b56200b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -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(); } /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java index bfcd1310acc2..e94f836337a9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -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) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt new file mode 100644 index 000000000000..7019a4bbb08c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt @@ -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() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 25722e1c956b..d8d3676d4fa2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -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; @@ -236,6 +238,22 @@ public class UdfpsControllerTest extends SysuiTestCase { } @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 when(mUdfpsView.isIlluminationRequested()).thenReturn(false); |