1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.biometrics 18 19 import android.graphics.PointF 20 import android.graphics.RectF 21 import android.hardware.biometrics.SensorLocationInternal 22 import androidx.test.ext.junit.runners.AndroidJUnit4 23 import android.testing.TestableLooper 24 import android.testing.ViewUtils 25 import android.view.LayoutInflater 26 import android.view.Surface 27 import androidx.test.filters.SmallTest 28 import com.android.settingslib.udfps.UdfpsOverlayParams 29 import com.android.systemui.R 30 import com.android.systemui.RoboPilotTest 31 import com.android.systemui.SysuiTestCase 32 import com.android.systemui.util.mockito.any 33 import com.android.systemui.util.mockito.mock 34 import com.android.systemui.util.mockito.withArgCaptor 35 import com.google.common.truth.Truth.assertThat 36 import org.junit.After 37 import org.junit.Before 38 import org.junit.Rule 39 import org.junit.Test 40 import org.junit.runner.RunWith 41 import org.mockito.Mock 42 import org.mockito.Mockito.never 43 import org.mockito.Mockito.nullable 44 import org.mockito.Mockito.verify 45 import org.mockito.Mockito.`when` as whenever 46 import org.mockito.junit.MockitoJUnit 47 48 private const val SENSOR_X = 50 49 private const val SENSOR_Y = 250 50 private const val SENSOR_RADIUS = 10 51 52 @SmallTest 53 @RoboPilotTest 54 @RunWith(AndroidJUnit4::class) 55 @TestableLooper.RunWithLooper 56 class UdfpsViewTest : SysuiTestCase() { 57 58 @JvmField @Rule 59 var rule = MockitoJUnit.rule() 60 61 @Mock 62 lateinit var hbmProvider: UdfpsDisplayModeProvider 63 @Mock 64 lateinit var animationViewController: UdfpsAnimationViewController<UdfpsAnimationView> 65 66 private lateinit var view: UdfpsView 67 68 @Before 69 fun setup() { 70 context.setTheme(R.style.Theme_AppCompat) 71 view = LayoutInflater.from(context).inflate(R.layout.udfps_view, null) as UdfpsView 72 view.animationViewController = animationViewController 73 val sensorBounds = SensorLocationInternal("", SENSOR_X, SENSOR_Y, SENSOR_RADIUS).rect 74 view.overlayParams = UdfpsOverlayParams(sensorBounds, sensorBounds, 1920, 75 1080, 1f, Surface.ROTATION_0) 76 view.setUdfpsDisplayModeProvider(hbmProvider) 77 ViewUtils.attachView(view) 78 } 79 80 @After 81 fun cleanup() { 82 ViewUtils.detachView(view) 83 } 84 85 @Test 86 fun layoutSizeFitsSensor() { 87 val params = withArgCaptor<RectF> { 88 verify(animationViewController).onSensorRectUpdated(capture()) 89 } 90 assertThat(params.width()).isAtLeast(2f * SENSOR_RADIUS) 91 assertThat(params.height()).isAtLeast(2f * SENSOR_RADIUS) 92 } 93 94 @Test 95 fun isWithinSensorAreaAndPaused() = isWithinSensorArea(paused = true) 96 97 @Test 98 fun isWithinSensorAreaAndNotPaused() = isWithinSensorArea(paused = false) 99 100 private fun isWithinSensorArea(paused: Boolean) { 101 whenever(animationViewController.shouldPauseAuth()).thenReturn(paused) 102 whenever(animationViewController.touchTranslation).thenReturn(PointF(0f, 0f)) 103 val end = (SENSOR_RADIUS * 2) - 1 104 for (x in 1 until end) { 105 for (y in 1 until end) { 106 assertThat(view.isWithinSensorArea(x.toFloat(), y.toFloat())).isEqualTo(!paused) 107 } 108 } 109 } 110 111 @Test 112 fun isWithinSensorAreaWhenTranslated() { 113 val offset = PointF(100f, 200f) 114 whenever(animationViewController.touchTranslation).thenReturn(offset) 115 val end = (SENSOR_RADIUS * 2) - 1 116 for (x in 0 until offset.x.toInt() step 2) { 117 for (y in 0 until offset.y.toInt() step 2) { 118 assertThat(view.isWithinSensorArea(x.toFloat(), y.toFloat())).isFalse() 119 } 120 } 121 for (x in offset.x.toInt() + 1 until offset.x.toInt() + end) { 122 for (y in offset.y.toInt() + 1 until offset.y.toInt() + end) { 123 assertThat(view.isWithinSensorArea(x.toFloat(), y.toFloat())).isTrue() 124 } 125 } 126 } 127 128 @Test 129 fun isNotWithinSensorArea() { 130 whenever(animationViewController.touchTranslation).thenReturn(PointF(0f, 0f)) 131 assertThat(view.isWithinSensorArea(SENSOR_RADIUS * 2.5f, SENSOR_RADIUS.toFloat())) 132 .isFalse() 133 assertThat(view.isWithinSensorArea(SENSOR_RADIUS.toFloat(), SENSOR_RADIUS * 2.5f)).isFalse() 134 } 135 136 @Test 137 fun startAndStopIllumination() { 138 val onDone: Runnable = mock() 139 view.configureDisplay(onDone) 140 141 val illuminator = withArgCaptor<Runnable> { 142 verify(hbmProvider).enable(capture()) 143 } 144 145 assertThat(view.isDisplayConfigured).isTrue() 146 verify(animationViewController).onDisplayConfiguring() 147 verify(animationViewController, never()).onDisplayUnconfigured() 148 verify(onDone, never()).run() 149 150 // fake illumination event 151 illuminator.run() 152 waitForLooper() 153 verify(onDone).run() 154 verify(hbmProvider, never()).disable(any()) 155 156 view.unconfigureDisplay() 157 assertThat(view.isDisplayConfigured).isFalse() 158 verify(animationViewController).onDisplayUnconfigured() 159 verify(hbmProvider).disable(nullable(Runnable::class.java)) 160 } 161 162 private fun waitForLooper() = TestableLooper.get(this).processAllMessages() 163 } 164