Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4ed5d552 authored by Beverly Tai's avatar Beverly Tai Committed by Android (Google) Code Review
Browse files

Merge "Introduce and use FingerprintPropertyInteractor" into main

parents 697e97bc 87fa0068
Loading
Loading
Loading
Loading
+100 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.domain.interactor

import android.hardware.biometrics.SensorLocationInternal
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class FingerprintPropertyInteractorTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val underTest = kosmos.fingerprintPropertyInteractor
    private val repository = kosmos.fingerprintPropertyRepository
    private val configurationRepository = kosmos.fakeConfigurationRepository
    private val displayRepository = kosmos.displayRepository

    @Test
    fun sensorLocation_resolution1f() =
        testScope.runTest {
            val currSensorLocation by collectLastValue(underTest.sensorLocation)

            displayRepository.emitDisplayChangeEvent(0)
            runCurrent()
            repository.setProperties(
                sensorId = 0,
                strength = SensorStrength.STRONG,
                sensorType = FingerprintSensorType.UDFPS_OPTICAL,
                sensorLocations =
                    mapOf(
                        Pair("", SensorLocationInternal("", 4, 4, 2)),
                        Pair("otherDisplay", SensorLocationInternal("", 1, 1, 1))
                    )
            )
            runCurrent()
            configurationRepository.setScaleForResolution(1f)
            runCurrent()

            assertThat(currSensorLocation?.centerX).isEqualTo(4)
            assertThat(currSensorLocation?.centerY).isEqualTo(4)
            assertThat(currSensorLocation?.radius).isEqualTo(2)
        }

    @Test
    fun sensorLocation_resolution2f() =
        testScope.runTest {
            val currSensorLocation by collectLastValue(underTest.sensorLocation)

            displayRepository.emitDisplayChangeEvent(0)
            runCurrent()
            repository.setProperties(
                sensorId = 0,
                strength = SensorStrength.STRONG,
                sensorType = FingerprintSensorType.UDFPS_OPTICAL,
                sensorLocations =
                    mapOf(
                        Pair("", SensorLocationInternal("", 4, 4, 2)),
                        Pair("otherDisplay", SensorLocationInternal("", 1, 1, 1))
                    )
            )
            runCurrent()
            configurationRepository.setScaleForResolution(2f)
            runCurrent()

            assertThat(currSensorLocation?.centerX).isEqualTo(4 * 2)
            assertThat(currSensorLocation?.centerY).isEqualTo(4 * 2)
            assertThat(currSensorLocation?.radius).isEqualTo(2 * 2)
        }
}
+91 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.domain.interactor

import android.content.Context
import android.hardware.biometrics.SensorLocationInternal
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
import com.android.systemui.biometrics.shared.model.SensorLocation
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map

@SysUISingleton
class FingerprintPropertyInteractor
@Inject
constructor(
    @Application private val context: Context,
    repository: FingerprintPropertyRepository,
    configurationInteractor: ConfigurationInteractor,
    displayStateInteractor: DisplayStateInteractor,
) {
    /**
     * Devices with multiple physical displays use unique display ids to determine which sensor is
     * on the active physical display. This value represents a unique physical display id.
     */
    private val uniqueDisplayId: Flow<String> =
        displayStateInteractor.displayChanges
            .map { context.display?.uniqueId }
            .filterNotNull()
            .distinctUntilChanged()

    /**
     * Sensor location for the:
     * - current physical display
     * - device's natural screen resolution
     * - device's natural orientation
     */
    private val unscaledSensorLocation: Flow<SensorLocationInternal> =
        combine(
            repository.sensorLocations,
            uniqueDisplayId,
        ) { locations, displayId ->
            // Devices without multiple physical displays do not use the display id as the key;
            // instead, the key is an empty string.
            locations.getOrDefault(
                displayId,
                locations.getOrDefault("", SensorLocationInternal.DEFAULT)
            )
        }

    /**
     * Sensor location for the:
     * - current physical display
     * - current screen resolution
     * - device's natural orientation
     */
    val sensorLocation: Flow<SensorLocation> =
        combine(
            unscaledSensorLocation,
            configurationInteractor.scaleForResolution,
        ) { unscaledSensorLocation, scale ->
            val sensorLocation =
                SensorLocation(
                    unscaledSensorLocation.sensorLocationX,
                    unscaledSensorLocation.sensorLocationY,
                    unscaledSensorLocation.sensorRadius,
                )
            sensorLocation.scale = scale
            sensorLocation
        }
}
+43 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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

/** Provides current sensor location information in the current screen resolution [scale]. */
data class SensorLocation(
    private val naturalCenterX: Int,
    private val naturalCenterY: Int,
    private val naturalRadius: Int
) {
    /**
     * Scale to apply to the sensor location's natural parameters to support different screen
     * resolutions.
     */
    var scale: Float = 1f

    val centerX: Float
        get() {
            return naturalCenterX * scale
        }
    val centerY: Float
        get() {
            return naturalCenterY * scale
        }
    val radius: Float
        get() {
            return naturalRadius * scale
        }
}
+12 −11
Original line number Diff line number Diff line
@@ -17,14 +17,12 @@
package com.android.systemui.keyguard.ui.viewmodel

import android.content.Context
import android.hardware.biometrics.SensorLocationInternal
import com.android.settingslib.Utils
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.res.R
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -44,21 +42,24 @@ constructor(
    configurationInteractor: ConfigurationInteractor,
    deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
    deviceEntryBackgroundViewModel: DeviceEntryBackgroundViewModel,
    fingerprintPropertyRepository: FingerprintPropertyRepository,
    fingerprintPropertyInteractor: FingerprintPropertyInteractor,
    udfpsOverlayInteractor: UdfpsOverlayInteractor,
) {
    private val isSupported: Flow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported

    /**
     * UDFPS icon location in pixels for the current display and screen resolution, in natural
     * orientation.
     */
    val iconLocation: Flow<IconLocation> =
        isSupported.flatMapLatest { supportsUI ->
            if (supportsUI) {
                fingerprintPropertyRepository.sensorLocations.map { sensorLocations ->
                    val sensorLocation =
                        sensorLocations.getOrDefault("", SensorLocationInternal.DEFAULT)
                fingerprintPropertyInteractor.sensorLocation.map { sensorLocation ->
                    IconLocation(
                        left = sensorLocation.sensorLocationX - sensorLocation.sensorRadius,
                        top = sensorLocation.sensorLocationY - sensorLocation.sensorRadius,
                        right = sensorLocation.sensorLocationX + sensorLocation.sensorRadius,
                        bottom = sensorLocation.sensorLocationY + sensorLocation.sensorRadius,
                        left = (sensorLocation.centerX - sensorLocation.radius).toInt(),
                        top = (sensorLocation.centerY - sensorLocation.radius).toInt(),
                        right = (sensorLocation.centerX + sensorLocation.radius).toInt(),
                        bottom = (sensorLocation.centerY + sensorLocation.radius).toInt(),
                    )
                }
            } else {
+36 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.domain.interactor

import android.content.applicationContext
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.display.data.repository.displayStateRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.util.mockito.mock
import java.util.concurrent.Executor

val Kosmos.displayStateInteractor by Fixture {
    DisplayStateInteractorImpl(
        applicationScope = applicationCoroutineScope,
        context = applicationContext,
        mainExecutor = mock<Executor>(),
        displayStateRepository = displayStateRepository,
        displayRepository = displayRepository,
    )
}
Loading