Merge "FingerprintSensorProperties refactor - data layer" into udc-dev am: af51da3111

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/22530804

Change-Id: I4856b9fb5ebabf8c009c8f204ff4044099d21417
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index 67d2f30..f0b9f67 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.biometrics.dagger
 
 import com.android.settingslib.udfps.UdfpsUtils
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepositoryImpl
 import com.android.systemui.biometrics.data.repository.PromptRepository
 import com.android.systemui.biometrics.data.repository.PromptRepositoryImpl
 import com.android.systemui.biometrics.domain.interactor.CredentialInteractor
@@ -41,6 +43,11 @@
 
     @Binds
     @SysUISingleton
+    fun fingerprintRepository(impl: FingerprintPropertyRepositoryImpl):
+            FingerprintPropertyRepository
+
+    @Binds
+    @SysUISingleton
     fun providesCredentialInteractor(impl: CredentialInteractorImpl): CredentialInteractor
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
new file mode 100644
index 0000000..33fb36c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2023 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.data.repository
+
+import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.shareIn
+
+/**
+ * A repository for the global state of FingerprintProperty.
+ *
+ * There is never more than one instance of the FingerprintProperty at any given time.
+ */
+interface FingerprintPropertyRepository {
+
+    /**
+     * If the repository is initialized or not. Other properties are defaults until this is true.
+     */
+    val isInitialized: Flow<Boolean>
+
+    /** The id of fingerprint sensor. */
+    val sensorId: StateFlow<Int>
+
+    /** The security strength of sensor (convenience, weak, strong). */
+    val strength: StateFlow<SensorStrength>
+
+    /** The types of fingerprint sensor (rear, ultrasonic, optical, etc.). */
+    val sensorType: StateFlow<FingerprintSensorType>
+
+    /** The primary sensor location relative to the default display. */
+    val sensorLocation: StateFlow<SensorLocationInternal>
+
+    // TODO(b/251476085): don't implement until we need it, but expose alternative locations as
+    // a map of display id -> location or similar.
+    /** The sensor location relative to each physical display. */
+    // val sensorLocations<Map<String, SensorLocationInternal>>
+}
+
+@SysUISingleton
+class FingerprintPropertyRepositoryImpl
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    private val fingerprintManager: FingerprintManager
+) : FingerprintPropertyRepository {
+
+    override val isInitialized: Flow<Boolean> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+                        override fun onAllAuthenticatorsRegistered(
+                            sensors: List<FingerprintSensorPropertiesInternal>
+                        ) {
+                            if (sensors.isNotEmpty()) {
+                                setProperties(sensors[0])
+                                trySendWithFailureLogging(true, TAG, "initialize properties")
+                            }
+                        }
+                    }
+                fingerprintManager.addAuthenticatorsRegisteredCallback(callback)
+                trySendWithFailureLogging(false, TAG, "initial value defaulting to false")
+                awaitClose {}
+            }
+            .shareIn(scope = applicationScope, started = SharingStarted.Eagerly, replay = 1)
+
+    private val _sensorId: MutableStateFlow<Int> = MutableStateFlow(-1)
+    override val sensorId: StateFlow<Int> = _sensorId.asStateFlow()
+
+    private val _strength: MutableStateFlow<SensorStrength> =
+        MutableStateFlow(SensorStrength.CONVENIENCE)
+    override val strength = _strength.asStateFlow()
+
+    private val _sensorType: MutableStateFlow<FingerprintSensorType> =
+        MutableStateFlow(FingerprintSensorType.UNKNOWN)
+    override val sensorType = _sensorType.asStateFlow()
+
+    private val _sensorLocation: MutableStateFlow<SensorLocationInternal> =
+        MutableStateFlow(SensorLocationInternal.DEFAULT)
+    override val sensorLocation = _sensorLocation.asStateFlow()
+
+    private fun setProperties(prop: FingerprintSensorPropertiesInternal) {
+        _sensorId.value = prop.sensorId
+        _strength.value = sensorStrengthIntToObject(prop.sensorStrength)
+        _sensorType.value = sensorTypeIntToObject(prop.sensorType)
+        _sensorLocation.value = prop.location
+    }
+
+    companion object {
+        private const val TAG = "FingerprintPropertyRepositoryImpl"
+    }
+}
+
+private fun sensorStrengthIntToObject(value: Int): SensorStrength {
+    return when (value) {
+        0 -> SensorStrength.CONVENIENCE
+        1 -> SensorStrength.WEAK
+        2 -> SensorStrength.STRONG
+        else -> throw IllegalArgumentException("Invalid SensorStrength value: $value")
+    }
+}
+
+private fun sensorTypeIntToObject(value: Int): FingerprintSensorType {
+    return when (value) {
+        0 -> FingerprintSensorType.UNKNOWN
+        1 -> FingerprintSensorType.REAR
+        2 -> FingerprintSensorType.UDFPS_ULTRASONIC
+        3 -> FingerprintSensorType.UDFPS_OPTICAL
+        4 -> FingerprintSensorType.POWER_BUTTON
+        5 -> FingerprintSensorType.HOME_BUTTON
+        else -> throw IllegalArgumentException("Invalid SensorType value: $value")
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
new file mode 100644
index 0000000..df5cefd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 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.shared.model
+
+import android.hardware.fingerprint.FingerprintSensorProperties
+
+/** Fingerprint sensor types. Represents [FingerprintSensorProperties.SensorType]. */
+enum class FingerprintSensorType {
+    UNKNOWN,
+    REAR,
+    UDFPS_ULTRASONIC,
+    UDFPS_OPTICAL,
+    POWER_BUTTON,
+    HOME_BUTTON,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt
new file mode 100644
index 0000000..2982d0b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 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.shared.model
+
+import android.hardware.biometrics.SensorProperties
+
+/** Fingerprint sensor security strength. Represents [SensorProperties.Strength]. */
+enum class SensorStrength {
+    CONVENIENCE,
+    WEAK,
+    STRONG,
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
new file mode 100644
index 0000000..f3a100b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2023 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.data.repository
+
+import android.hardware.biometrics.ComponentInfoInternal
+import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.biometrics.SensorProperties
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorProperties
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(JUnit4::class)
+class FingerprintRepositoryImplTest : SysuiTestCase() {
+
+    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+    private lateinit var testScope: TestScope
+
+    @Mock private lateinit var fingerprintManager: FingerprintManager
+    private lateinit var repository: FingerprintPropertyRepositoryImpl
+
+    @Captor
+    private lateinit var fingerprintAuthenticatorsCaptor:
+        ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback.Stub>
+
+    @Before
+    fun setup() {
+        val dispatcher = StandardTestDispatcher()
+        testScope = TestScope(dispatcher)
+        repository =
+            FingerprintPropertyRepositoryImpl(testScope.backgroundScope, fingerprintManager)
+        testScope.runCurrent()
+
+        verify(fingerprintManager)
+            .addAuthenticatorsRegisteredCallback(fingerprintAuthenticatorsCaptor.capture())
+    }
+
+    @Test
+    fun initializeProperties() =
+        testScope.runTest {
+            val isInitialized = collectLastValue(repository.isInitialized)
+
+            assertDefaultProperties()
+            assertThat(isInitialized()).isFalse()
+
+            val fingerprintProps =
+                listOf(
+                    FingerprintSensorPropertiesInternal(
+                        1 /* sensorId */,
+                        SensorProperties.STRENGTH_STRONG,
+                        5 /* maxEnrollmentsPerUser */,
+                        listOf<ComponentInfoInternal>(
+                            ComponentInfoInternal(
+                                "sensor" /* componentId */,
+                                "vendor/model/revision" /* hardwareVersion */,
+                                "1.01" /* firmwareVersion */,
+                                "00000001" /* serialNumber */,
+                                "" /* softwareVersion */
+                            )
+                        ),
+                        FingerprintSensorProperties.TYPE_REAR,
+                        false /* halControlsIllumination */,
+                        true /* resetLockoutRequiresHardwareAuthToken */,
+                        listOf<SensorLocationInternal>(
+                            SensorLocationInternal(
+                                "" /* displayId */,
+                                540 /* sensorLocationX */,
+                                1636 /* sensorLocationY */,
+                                130 /* sensorRadius */
+                            )
+                        )
+                    )
+                )
+
+            fingerprintAuthenticatorsCaptor.value.onAllAuthenticatorsRegistered(fingerprintProps)
+
+            assertThat(repository.sensorId.value).isEqualTo(1)
+            assertThat(repository.strength.value).isEqualTo(SensorStrength.STRONG)
+            assertThat(repository.sensorType.value).isEqualTo(FingerprintSensorType.REAR)
+            with(repository.sensorLocation.value) {
+                assertThat(displayId).isEqualTo("")
+                assertThat(sensorLocationX).isEqualTo(540)
+                assertThat(sensorLocationY).isEqualTo(1636)
+                assertThat(sensorRadius).isEqualTo(130)
+            }
+            assertThat(isInitialized()).isTrue()
+        }
+
+    private fun assertDefaultProperties() {
+        assertThat(repository.sensorId.value).isEqualTo(-1)
+        assertThat(repository.strength.value).isEqualTo(SensorStrength.CONVENIENCE)
+        assertThat(repository.sensorType.value).isEqualTo(FingerprintSensorType.UNKNOWN)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
new file mode 100644
index 0000000..d9012a5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 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.data.repository
+
+import android.hardware.biometrics.SensorLocationInternal
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeFingerprintPropertyRepository : FingerprintPropertyRepository {
+
+    private val _isInitialized: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    override val isInitialized = _isInitialized.asStateFlow()
+
+    private val _sensorId: MutableStateFlow<Int> = MutableStateFlow(-1)
+    override val sensorId: StateFlow<Int> = _sensorId.asStateFlow()
+
+    private val _strength: MutableStateFlow<SensorStrength> =
+        MutableStateFlow(SensorStrength.CONVENIENCE)
+    override val strength = _strength.asStateFlow()
+
+    private val _sensorType: MutableStateFlow<FingerprintSensorType> =
+        MutableStateFlow(FingerprintSensorType.UNKNOWN)
+    override val sensorType: StateFlow<FingerprintSensorType> = _sensorType.asStateFlow()
+
+    private val _sensorLocation: MutableStateFlow<SensorLocationInternal> =
+        MutableStateFlow(SensorLocationInternal.DEFAULT)
+    override val sensorLocation = _sensorLocation.asStateFlow()
+
+    fun setProperties(
+        sensorId: Int,
+        strength: SensorStrength,
+        sensorType: FingerprintSensorType,
+        sensorLocation: SensorLocationInternal
+    ) {
+        _sensorId.value = sensorId
+        _strength.value = strength
+        _sensorType.value = sensorType
+        _sensorLocation.value = sensorLocation
+        _isInitialized.value = true
+    }
+}