blob: 0327cfcf3450a692fe8acf6972de47be9cd1a1fb [file] [log] [blame]
/*
* 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.PointF
import android.graphics.RectF
import android.hardware.biometrics.SensorLocationInternal
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.ViewUtils
import android.view.LayoutInflater
import android.view.Surface
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.nullable
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
private const val SENSOR_X = 50
private const val SENSOR_Y = 250
private const val SENSOR_RADIUS = 10
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class UdfpsViewTest : SysuiTestCase() {
@JvmField @Rule
var rule = MockitoJUnit.rule()
@Mock
lateinit var hbmProvider: UdfpsHbmProvider
@Mock
lateinit var animationViewController: UdfpsAnimationViewController<UdfpsAnimationView>
private lateinit var view: UdfpsView
@Before
fun setup() {
context.setTheme(R.style.Theme_AppCompat)
context.orCreateTestableResources.addOverride(
com.android.internal.R.integer.config_udfps_illumination_transition_ms, 0)
view = LayoutInflater.from(context).inflate(R.layout.udfps_view, null) as UdfpsView
view.animationViewController = animationViewController
val sensorBounds = SensorLocationInternal("", SENSOR_X, SENSOR_Y, SENSOR_RADIUS).rect
view.overlayParams = UdfpsOverlayParams(sensorBounds, 1920, 1080, 1f, Surface.ROTATION_0)
view.setHbmProvider(hbmProvider)
ViewUtils.attachView(view)
}
@After
fun cleanup() {
ViewUtils.detachView(view)
}
@Test
fun forwardsEvents() {
view.dozeTimeTick()
verify(animationViewController).dozeTimeTick()
view.onTouchOutsideView()
verify(animationViewController).onTouchOutsideView()
}
@Test
fun layoutSizeFitsSensor() {
val params = withArgCaptor<RectF> {
verify(animationViewController).onSensorRectUpdated(capture())
}
assertThat(params.width()).isAtLeast(2f * SENSOR_RADIUS)
assertThat(params.height()).isAtLeast(2f * SENSOR_RADIUS)
}
@Test
fun isWithinSensorAreaAndPaused() = isWithinSensorArea(paused = true)
@Test
fun isWithinSensorAreaAndNotPaused() = isWithinSensorArea(paused = false)
private fun isWithinSensorArea(paused: Boolean) {
whenever(animationViewController.shouldPauseAuth()).thenReturn(paused)
whenever(animationViewController.touchTranslation).thenReturn(PointF(0f, 0f))
val end = (SENSOR_RADIUS * 2) - 1
for (x in 1 until end) {
for (y in 1 until end) {
assertThat(view.isWithinSensorArea(x.toFloat(), y.toFloat())).isEqualTo(!paused)
}
}
}
@Test
fun isWithinSensorAreaWhenTranslated() {
val offset = PointF(100f, 200f)
whenever(animationViewController.touchTranslation).thenReturn(offset)
val end = (SENSOR_RADIUS * 2) - 1
for (x in 0 until offset.x.toInt() step 2) {
for (y in 0 until offset.y.toInt() step 2) {
assertThat(view.isWithinSensorArea(x.toFloat(), y.toFloat())).isFalse()
}
}
for (x in offset.x.toInt() + 1 until offset.x.toInt() + end) {
for (y in offset.y.toInt() + 1 until offset.y.toInt() + end) {
assertThat(view.isWithinSensorArea(x.toFloat(), y.toFloat())).isTrue()
}
}
}
@Test
fun isNotWithinSensorArea() {
whenever(animationViewController.touchTranslation).thenReturn(PointF(0f, 0f))
assertThat(view.isWithinSensorArea(SENSOR_RADIUS * 2.5f, SENSOR_RADIUS.toFloat())).isFalse()
assertThat(view.isWithinSensorArea(SENSOR_RADIUS.toFloat(), SENSOR_RADIUS * 2.5f)).isFalse()
}
@Test
fun startAndStopIllumination() {
val onDone: Runnable = mock()
view.startIllumination(onDone)
val illuminator = withArgCaptor<Runnable> {
verify(hbmProvider).enableHbm(anyBoolean(), capture())
}
assertThat(view.isIlluminationRequested).isTrue()
verify(animationViewController).onIlluminationStarting()
verify(animationViewController, never()).onIlluminationStopped()
verify(onDone, never()).run()
// fake illumination event
illuminator.run()
waitForLooper()
verify(onDone).run()
verify(hbmProvider, never()).disableHbm(any())
view.stopIllumination()
assertThat(view.isIlluminationRequested).isFalse()
verify(animationViewController).onIlluminationStopped()
verify(hbmProvider).disableHbm(nullable(Runnable::class.java))
}
private fun waitForLooper() = TestableLooper.get(this).processAllMessages()
}