diff options
13 files changed, 140 insertions, 53 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/SensorLocation.kt index 2f2f3a35dbaa..552679224bca 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/SensorLocation.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.biometrics.shared.model +package com.android.systemui.shared.customization.data /** * Provides current sensor location information in the current screen resolution [scale]. @@ -26,18 +26,40 @@ data class SensorLocation( private val naturalCenterX: Int, private val naturalCenterY: Int, private val naturalRadius: Int, - private val scale: Float = 1f + private val scale: Float = 1f, ) { val centerX: Float get() { return naturalCenterX * scale } + val centerY: Float get() { return naturalCenterY * scale } + val radius: Float get() { return naturalRadius * scale } + + fun encode(): String { + return floatArrayOf( + naturalCenterX.toFloat(), + naturalCenterY.toFloat(), + naturalRadius.toFloat(), + scale, + ) + .joinToString(DELIMITER) + } + + companion object { + + private const val DELIMITER: String = "," + + fun decode(encoded: String): SensorLocation { + val array = encoded.split(DELIMITER).map { it.toFloat() }.toFloatArray() + return SensorLocation(array[0].toInt(), array[1].toInt(), array[2].toInt(), array[3]) + } + } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt index 48af2d9f5542..caa6636bde03 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt @@ -80,13 +80,6 @@ interface CustomizationProviderClient { fun observeFlags(): Flow<List<Flag>> /** - * Returns [Flow] for observing the variables from the System UI. - * - * @see [queryRuntimeValues] - */ - fun observeRuntimeValues(): Flow<Bundle> - - /** * Returns all available affordances supported by the device, regardless of current slot * placement. */ @@ -291,6 +284,9 @@ class CustomizationProviderClientImpl( Contract.RuntimeValuesTable.KEY_IS_SHADE_LAYOUT_WIDE -> { putBoolean(name, cursor.getInt(valueColumnIndex) == 1) } + Contract.RuntimeValuesTable.KEY_UDFPS_LOCATION -> { + putString(name, cursor.getString(valueColumnIndex)) + } } } } @@ -307,10 +303,6 @@ class CustomizationProviderClientImpl( return observeUri(Contract.FlagsTable.URI).map { queryFlags() } } - override fun observeRuntimeValues(): Flow<Bundle> { - return observeUri(Contract.RuntimeValuesTable.URI).map { queryRuntimeValues() } - } - override suspend fun queryAffordances(): List<CustomizationProviderClient.Affordance> { return withContext(backgroundDispatcher) { context.contentResolver diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt index cb167eddcea9..2934f070b05f 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt @@ -19,6 +19,7 @@ package com.android.systemui.shared.customization.data.content import android.content.ContentResolver import android.net.Uri +import com.android.systemui.shared.customization.data.SensorLocation /** Contract definitions for querying content about keyguard quick affordances. */ object CustomizationProviderContract { @@ -213,6 +214,11 @@ object CustomizationProviderContract { * be as wide as the entire screen. */ const val KEY_IS_SHADE_LAYOUT_WIDE = "is_shade_layout_wide" + /** + * This key corresponds to a String value, representing the string form of [SensorLocation], + * which contains the information of the UDFPS location. + */ + const val KEY_UDFPS_LOCATION = "udfps_location" object Columns { /** String. Unique ID for the value. */ diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt index 47c5bda93c0e..70d17820a12c 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt @@ -108,10 +108,6 @@ class FakeCustomizationProviderClient( return flags.asStateFlow() } - override fun observeRuntimeValues(): Flow<Bundle> { - return runtimeValues.asStateFlow() - } - override suspend fun queryAffordances(): List<CustomizationProviderClient.Affordance> { return affordances.value } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/customization/data/SensorLocationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/customization/data/SensorLocationTest.kt new file mode 100644 index 000000000000..526fd45533c7 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/customization/data/SensorLocationTest.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2025 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.shared.customization.data + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class SensorLocationTest : SysuiTestCase() { + + @Test + fun encodeAndDecode() { + val sensorLocation = SensorLocation(640, 2068, 117, 0.75f) + + assertThat(SensorLocation.decode(sensorLocation.encode())).isEqualTo(sensorLocation) + } +} 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 index 68ec0f2d57ef..39f55803bb73 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt @@ -68,7 +68,7 @@ interface FingerprintPropertyRepository { val sensorType: StateFlow<FingerprintSensorType> /** The sensor location relative to each physical display. */ - val sensorLocations: Flow<Map<String, SensorLocationInternal>> + val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> } @SysUISingleton @@ -128,12 +128,14 @@ constructor( initialValue = props.value.sensorType.toSensorType(), ) - override val sensorLocations: Flow<Map<String, SensorLocationInternal>> = - props.map { - it.allLocations.associateBy { sensorLocationInternal -> - sensorLocationInternal.displayId - } - } + override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> = + props + .map { props -> props.allLocations.associateBy { it.displayId } } + .stateIn( + applicationScope, + started = SharingStarted.Eagerly, + initialValue = props.value.allLocations.associateBy { it.displayId }, + ) override val propertiesInitialized: Flow<Boolean> = combine( diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt index d9ed9ca1f07b..ae855d19715e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt @@ -20,11 +20,11 @@ import android.content.Context import android.graphics.Rect 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 com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.shared.customization.data.SensorLocation import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow @@ -42,8 +42,8 @@ class FingerprintPropertyInteractor constructor( @Application private val applicationScope: CoroutineScope, @Application private val context: Context, - repository: FingerprintPropertyRepository, - @Main configurationInteractor: ConfigurationInteractor, + private val repository: FingerprintPropertyRepository, + @Main private val configurationInteractor: ConfigurationInteractor, displayStateInteractor: DisplayStateInteractor, udfpsOverlayInteractor: UdfpsOverlayInteractor, ) { @@ -61,11 +61,16 @@ constructor( * 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> = + private val uniqueDisplayId: StateFlow<String> = displayStateInteractor.displayChanges - .map { context.display?.uniqueId } + .map { context.display.uniqueId } .filterNotNull() .distinctUntilChanged() + .stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = EMPTY_DISPLAY_ID, + ) /** * Sensor location for the: @@ -73,13 +78,15 @@ constructor( * - device's natural screen resolution * - device's natural orientation */ - private val unscaledSensorLocation: Flow<SensorLocationInternal> = - combine(repository.sensorLocations, uniqueDisplayId) { locations, displayId -> + private val unscaledSensorLocation: StateFlow<SensorLocationInternal> = + combineStates(repository.sensorLocations, uniqueDisplayId, applicationScope) { + 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), + locations.getOrDefault(EMPTY_DISPLAY_ID, SensorLocationInternal.DEFAULT), ) } @@ -89,18 +96,18 @@ constructor( * - current screen resolution * - device's natural orientation */ - val sensorLocation: Flow<SensorLocation> = - combine(unscaledSensorLocation, configurationInteractor.scaleForResolution) { + val sensorLocation: StateFlow<SensorLocation> = + combineStates( unscaledSensorLocation, - scale -> - val sensorLocation = - SensorLocation( - naturalCenterX = unscaledSensorLocation.sensorLocationX, - naturalCenterY = unscaledSensorLocation.sensorLocationY, - naturalRadius = unscaledSensorLocation.sensorRadius, - scale = scale, - ) - sensorLocation + configurationInteractor.scaleForResolution, + applicationScope, + ) { unscaledSensorLocation, scale -> + SensorLocation( + naturalCenterX = unscaledSensorLocation.sensorLocationX, + naturalCenterY = unscaledSensorLocation.sensorLocationY, + naturalRadius = unscaledSensorLocation.sensorRadius, + scale = scale, + ) } /** @@ -111,4 +118,19 @@ constructor( */ val udfpsSensorBounds: Flow<Rect> = udfpsOverlayInteractor.udfpsOverlayParams.map { it.sensorBounds }.distinctUntilChanged() + + companion object { + + private const val EMPTY_DISPLAY_ID = "" + + /** Combine two state flows to another state flow. */ + private fun <T1, T2, R> combineStates( + flow1: StateFlow<T1>, + flow2: StateFlow<T2>, + scope: CoroutineScope, + transform: (T1, T2) -> R, + ): StateFlow<R> = + combine(flow1, flow2) { v1, v2 -> transform(v1, v2) } + .stateIn(scope, SharingStarted.Eagerly, transform(flow1.value, flow2.value)) + } } diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt index 747a2a9bd887..7fcdd9596049 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt @@ -52,7 +52,7 @@ interface ConfigurationRepository { /** Called whenever the configuration has changed. */ val onConfigurationChange: Flow<Unit> - val scaleForResolution: Flow<Float> + val scaleForResolution: StateFlow<Float> val configurationValues: Flow<Configuration> diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt index 97a23e1a5010..4d39b033cd7b 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt @@ -23,6 +23,7 @@ import android.view.Surface import com.android.systemui.common.ui.data.repository.ConfigurationRepository import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map @@ -60,7 +61,7 @@ interface ConfigurationInteractor { val configurationValues: Flow<Configuration> /** Emits the current resolution scaling factor */ - val scaleForResolution: Flow<Float> + val scaleForResolution: StateFlow<Float> /** Given [resourceId], emit the dimension pixel size on config change */ fun dimensionPixelSize(resourceId: Int): Flow<Int> @@ -121,5 +122,5 @@ class ConfigurationInteractorImpl(private val repository: ConfigurationRepositor override val configurationValues: Flow<Configuration> = repository.configurationValues - override val scaleForResolution: Flow<Float> = repository.scaleForResolution + override val scaleForResolution: StateFlow<Float> = repository.scaleForResolution } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt index 6c6d730819f3..911327a0bd18 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt @@ -17,10 +17,10 @@ package com.android.systemui.deviceentry.domain.interactor import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor -import com.android.systemui.biometrics.shared.model.SensorLocation import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository +import com.android.systemui.shared.customization.data.SensorLocation import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt index 7a72732ea6bf..18cabad6b2d5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt @@ -33,6 +33,7 @@ import android.util.Log import com.android.app.tracing.coroutines.runBlockingTraced as runBlocking import com.android.systemui.SystemUIAppComponentFactoryBase import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback +import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager @@ -46,6 +47,7 @@ class CustomizationProvider : @Inject lateinit var interactor: KeyguardQuickAffordanceInteractor @Inject lateinit var shadeModeInteractor: ShadeModeInteractor + @Inject lateinit var fingerprintPropertyInteractor: FingerprintPropertyInteractor @Inject lateinit var previewManager: KeyguardRemotePreviewManager @Inject @Main lateinit var mainDispatcher: CoroutineDispatcher @@ -345,6 +347,14 @@ class CustomizationProvider : } private fun queryRuntimeValues(): Cursor { + // If not UDFPS, the udfpsLocation will be null + val udfpsLocation = + if (fingerprintPropertyInteractor.isUdfps.value) { + fingerprintPropertyInteractor.sensorLocation.value + } else { + null + } + return MatrixCursor( arrayOf( Contract.RuntimeValuesTable.Columns.NAME, @@ -358,6 +368,9 @@ class CustomizationProvider : if (shadeModeInteractor.isShadeLayoutWide.value) 1 else 0, ) ) + addRow( + arrayOf(Contract.RuntimeValuesTable.KEY_UDFPS_LOCATION, udfpsLocation?.encode()) + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt index f5e0c81ca9a2..b1a2ec92401a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt @@ -20,7 +20,6 @@ import android.animation.FloatEvaluator import android.animation.IntEvaluator import com.android.keyguard.KeyguardViewController import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor -import com.android.systemui.biometrics.shared.model.SensorLocation import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor @@ -34,6 +33,7 @@ import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.keyguard.ui.view.DeviceEntryIconView import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shared.customization.data.SensorLocation import com.android.systemui.util.kotlin.sample import dagger.Lazy import javax.inject.Inject diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt index 487049740079..e30e92020706 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt @@ -33,10 +33,7 @@ import kotlinx.coroutines.flow.asStateFlow @SysUISingleton class FakeConfigurationRepository @Inject constructor() : ConfigurationRepository { private val _onAnyConfigurationChange = - MutableSharedFlow<Unit>( - replay = 1, - onBufferOverflow = BufferOverflow.DROP_OLDEST, - ) + MutableSharedFlow<Unit>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) override val onAnyConfigurationChange: Flow<Unit> = _onAnyConfigurationChange.asSharedFlow() private val _onConfigurationChange = @@ -53,7 +50,7 @@ class FakeConfigurationRepository @Inject constructor() : ConfigurationRepositor get() = _onMovedToDisplay private val _scaleForResolution = MutableStateFlow(1f) - override val scaleForResolution: Flow<Float> = _scaleForResolution.asStateFlow() + override val scaleForResolution: StateFlow<Float> = _scaleForResolution.asStateFlow() private val pixelSizes = mutableMapOf<Int, MutableStateFlow<Int>>() |