diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 4ef1f92421e55..a091ed928a4d3 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -30,6 +30,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Point; +import android.graphics.Rect; import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.display.DisplayManager; import android.hardware.fingerprint.FingerprintManager; @@ -299,6 +300,30 @@ public class UdfpsController implements DozeReceiver { mOverlay.getOverlayView().setDebugMessage(message); }); } + + public Rect getSensorBounds() { + return mOverlayParams.getSensorBounds(); + } + + /** + * Passes a mocked MotionEvent to OnTouch. + * + * @param event MotionEvent to simulate in onTouch + */ + public void debugOnTouch(long requestId, MotionEvent event) { + UdfpsController.this.onTouch(requestId, event, false); + } + + /** + * Debug to run onUiReady + */ + public void debugOnUiReady(long requestId, int sensorId) { + if (UdfpsController.this.mAlternateTouchProvider != null) { + UdfpsController.this.mAlternateTouchProvider.onUiReady(); + } else { + UdfpsController.this.mFingerprintManager.onUiReady(requestId, sensorId); + } + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt index da50f1c94f294..f48cfd3dc3a4a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt @@ -17,6 +17,7 @@ package com.android.systemui.biometrics import android.content.Context +import android.graphics.Rect import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER @@ -27,6 +28,11 @@ import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN import android.hardware.fingerprint.IUdfpsOverlayControllerCallback import android.util.Log import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.MotionEvent.ACTION_DOWN +import android.view.MotionEvent.ACTION_MOVE +import android.view.MotionEvent.ACTION_UP +import com.android.internal.annotations.VisibleForTesting import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry @@ -36,6 +42,8 @@ import javax.inject.Inject private const val TAG = "UdfpsShell" private const val REQUEST_ID = 2L private const val SENSOR_ID = 0 +private const val MINOR = 10F +private const val MAJOR = 10F /** * Used to show and hide the UDFPS overlay with statusbar commands. @@ -67,6 +75,12 @@ class UdfpsShell @Inject constructor( hideUdfpsOverlay() } else if (args.size == 2 && args[0] == "show") { showOverlay(getEnrollmentReason(args[1])) + } else if (args.size == 1 && args[0] == "onUiReady") { + onUiReady() + } else if (args.size == 1 && args[0] == "simFingerDown") { + simFingerDown() + } else if (args.size == 1 && args[0] == "simFingerUp") { + simFingerUp() } else { invalidCommand(pw) } @@ -80,6 +94,11 @@ class UdfpsShell @Inject constructor( "auth-keyguard, auth-other, auth-settings]") pw.println(" -> reason otherwise defaults to unknown") pw.println(" - hide") + pw.println(" - onUiReady") + pw.println(" - simFingerDown") + pw.println(" -> Simulates onFingerDown on sensor") + pw.println(" - simFingerUp") + pw.println(" -> Simulates onFingerUp on sensor") } private fun invalidCommand(pw: PrintWriter) { @@ -125,4 +144,54 @@ class UdfpsShell @Inject constructor( private fun hideOverlay() { udfpsOverlayController?.hideUdfpsOverlay(SENSOR_ID) } + + + @VisibleForTesting + fun onUiReady() { + udfpsOverlayController?.debugOnUiReady(REQUEST_ID, SENSOR_ID) + } + + @VisibleForTesting + fun simFingerDown() { + val sensorBounds: Rect = udfpsOverlayController!!.sensorBounds + + val downEvent: MotionEvent? = obtainMotionEvent(ACTION_DOWN, sensorBounds.exactCenterX(), + sensorBounds.exactCenterY(), MINOR, MAJOR) + udfpsOverlayController?.debugOnTouch(REQUEST_ID, downEvent) + + val moveEvent: MotionEvent? = obtainMotionEvent(ACTION_MOVE, sensorBounds.exactCenterX(), + sensorBounds.exactCenterY(), MINOR, MAJOR) + udfpsOverlayController?.debugOnTouch(REQUEST_ID, moveEvent) + + downEvent?.recycle() + moveEvent?.recycle() + } + + @VisibleForTesting + fun simFingerUp() { + val sensorBounds: Rect = udfpsOverlayController!!.sensorBounds + + val upEvent: MotionEvent? = obtainMotionEvent(ACTION_UP, sensorBounds.exactCenterX(), + sensorBounds.exactCenterY(), MINOR, MAJOR) + udfpsOverlayController?.debugOnTouch(REQUEST_ID, upEvent) + upEvent?.recycle() + } + + private fun obtainMotionEvent( + action: Int, + x: Float, + y: Float, + minor: Float, + major: Float + ): MotionEvent? { + val pp = MotionEvent.PointerProperties() + pp.id = 1 + val pc = MotionEvent.PointerCoords() + pc.x = x + pc.y = y + pc.touchMinor = minor + pc.touchMajor = major + return MotionEvent.obtain(0, 0, action, 1, arrayOf(pp), arrayOf(pc), + 0, 0, 1f, 1f, 0, 0, 0, 0) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt new file mode 100644 index 0000000000000..54f20db59afde --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt @@ -0,0 +1,92 @@ +/* + * 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.graphics.Rect +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.MotionEvent +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.UdfpsController.UdfpsOverlayController +import com.android.systemui.statusbar.commandline.CommandRegistry +import com.android.systemui.util.mockito.any +import junit.framework.Assert.assertEquals +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenEver +import org.mockito.junit.MockitoJUnit + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class UdfpsShellTest : SysuiTestCase() { + + @JvmField @Rule var rule = MockitoJUnit.rule() + + // Unit under test + private lateinit var udfpsShell: UdfpsShell + + @Mock lateinit var commandRegistry: CommandRegistry + @Mock lateinit var udfpsOverlay: UdfpsOverlay + @Mock lateinit var udfpsOverlayController: UdfpsOverlayController + + @Captor private lateinit var motionEvent: ArgumentCaptor + + private val sensorBounds = Rect() + + @Before + fun setup() { + whenEver(udfpsOverlayController.sensorBounds).thenReturn(sensorBounds) + + udfpsShell = UdfpsShell(commandRegistry, udfpsOverlay) + udfpsShell.udfpsOverlayController = udfpsOverlayController + } + + @Test + fun testSimFingerDown() { + udfpsShell.simFingerDown() + + verify(udfpsOverlayController, times(2)).debugOnTouch(any(), motionEvent.capture()) + + assertEquals(motionEvent.allValues[0].action, MotionEvent.ACTION_DOWN) // ACTION_MOVE + assertEquals(motionEvent.allValues[1].action, MotionEvent.ACTION_MOVE) // ACTION_MOVE + } + + @Test + fun testSimFingerUp() { + udfpsShell.simFingerUp() + + verify(udfpsOverlayController).debugOnTouch(any(), motionEvent.capture()) + + assertEquals(motionEvent.value.action, MotionEvent.ACTION_UP) + } + + @Test + fun testOnUiReady() { + udfpsShell.onUiReady() + + verify(udfpsOverlayController).debugOnUiReady(any(), any()) + } +}