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

Commit 60f9c514 authored by Chandru S's avatar Chandru S
Browse files

Add all the derived state needed for rest to unlock feature to SideFpsSensorInteractor

This is code will be reused by SideFpsController as well to show the SFPS indicator

SideFpsSensorInteractor provides
  1. sensorLocation  that provides location of the sensor relative to the left, top of the screen
  2. isProlongedTouchRequiredForAuthentication - is rest to unlock enabled
  3. authenticationDuration - rest duration for authentication
  4. isAvailable - whether SFPS is available

This also handles display changes (felix folds/unfolds) or display rotation changes

Note: SensorInternal provides (???, 0) for sensors along vertical edge (in Rotation_0) or (0, ???) for sensors along horizontal edge (in Rotation_0).

Bug: 277165756
Test: atest SideFpsSensorInteractorTest

Change-Id: I74abe73f57b0c03046f76bfbf98b4da70e1d9a02
parent 67863693
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -954,4 +954,15 @@
    bouncer, lockscreen, shade, and quick settings.
    -->
    <bool name="config_sceneContainerFrameworkEnabled">true</bool>

    <!--
    Time in milliseconds the user has to touch the side FPS sensor to successfully authenticate
    TODO(b/302332976) Get this value from the HAL if they can provide an API for it.
    -->
    <integer name="config_restToUnlockDuration">300</integer>

    <!--
    Width in pixels of the Side FPS sensor.
    -->
    <integer name="config_sfpsSensorWidth">200</integer>
</resources>
+0 −8
Original line number Diff line number Diff line
@@ -23,8 +23,6 @@ import com.android.systemui.biometrics.domain.interactor.LogContextInteractor
import com.android.systemui.biometrics.domain.interactor.LogContextInteractorImpl
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor
import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractorImpl
import com.android.systemui.dagger.SysUISingleton
import dagger.Binds
import dagger.Module
@@ -49,10 +47,4 @@ interface BiometricsDomainLayerModule {
    @Binds
    @SysUISingleton
    fun bindsLogContextInteractor(impl: LogContextInteractorImpl): LogContextInteractor

    @Binds
    @SysUISingleton
    fun providesSideFpsOverlayInteractor(
        impl: SideFpsOverlayInteractorImpl
    ): SideFpsOverlayInteractor
}
+5 −0
Original line number Diff line number Diff line
@@ -54,6 +54,9 @@ interface DisplayStateInteractor {
    /** Current rotation of the display */
    val currentRotation: StateFlow<DisplayRotation>

    /** Display change event indicating a change to the given displayId has occurred. */
    val displayChanges: Flow<Int>

    /** Called on configuration changes, used to keep the display state in sync */
    fun onConfigurationChanged(newConfig: Configuration)
}
@@ -74,6 +77,8 @@ constructor(
        screenSizeFoldProvider = foldProvider
    }

    override val displayChanges = displayRepository.displayChangeEvent

    override val isFolded: Flow<Boolean> =
        conflatedCallbackFlow {
                val sendFoldStateUpdate = { state: Boolean ->
+0 −62
Original line number Diff line number Diff line
/*
 * 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.domain.interactor

import android.hardware.biometrics.SensorLocationInternal
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine

/** Business logic for SideFps overlay offsets. */
interface SideFpsOverlayInteractor {

    /** The displayId of the current display. */
    val displayId: Flow<String>

    /** Overlay offsets corresponding to given displayId. */
    val overlayOffsets: Flow<SensorLocationInternal>

    /** Called on display changes, used to keep the display state in sync */
    fun onDisplayChanged(displayId: String)
}

@SysUISingleton
class SideFpsOverlayInteractorImpl
@Inject
constructor(fingerprintPropertyRepository: FingerprintPropertyRepository) :
    SideFpsOverlayInteractor {

    private val _displayId: MutableStateFlow<String> = MutableStateFlow("")
    override val displayId: Flow<String> = _displayId.asStateFlow()

    override val overlayOffsets: Flow<SensorLocationInternal> =
        combine(displayId, fingerprintPropertyRepository.sensorLocations) { displayId, offsets ->
            offsets[displayId] ?: SensorLocationInternal.DEFAULT
        }

    override fun onDisplayChanged(displayId: String) {
        _displayId.value = displayId
    }

    companion object {
        private const val TAG = "SideFpsOverlayInteractorImpl"
    }
}
+136 −0
Original line number Diff line number Diff line
/*
 * 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.domain.interactor

import android.content.Context
import android.hardware.biometrics.SensorLocationInternal
import android.view.WindowManager
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
import com.android.systemui.biometrics.domain.model.SideFpsSensorLocation
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.isDefaultOrientation
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.res.R
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map

@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class SideFpsSensorInteractor
@Inject
constructor(
    private val context: Context,
    fingerprintPropertyRepository: FingerprintPropertyRepository,
    windowManager: WindowManager,
    displayStateInteractor: DisplayStateInteractor,
    featureFlags: FeatureFlagsClassic,
) {

    private val sensorForCurrentDisplay =
        combine(
                displayStateInteractor.displayChanges,
                fingerprintPropertyRepository.sensorLocations,
                ::Pair
            )
            .map { (_, locations) -> locations[context.display?.uniqueId] }
            .filterNotNull()

    val isAvailable: Flow<Boolean> =
        fingerprintPropertyRepository.sensorType.map { it == FingerprintSensorType.POWER_BUTTON }

    val authenticationDuration: Flow<Long> =
        flowOf(context.resources?.getInteger(R.integer.config_restToUnlockDuration)?.toLong() ?: 0L)

    val isProlongedTouchRequiredForAuthentication: Flow<Boolean> =
        isAvailable.flatMapLatest { sfpsAvailable ->
            if (sfpsAvailable) {
                // todo (b/305236201) also add the settings check here.
                flowOf(featureFlags.isEnabled(Flags.REST_TO_UNLOCK))
            } else {
                flowOf(false)
            }
        }

    val sensorLocation: Flow<SideFpsSensorLocation> =
        combine(displayStateInteractor.currentRotation, sensorForCurrentDisplay, ::Pair).map {
            (rotation, sensorLocation: SensorLocationInternal) ->
            val isSensorVerticalInDefaultOrientation = sensorLocation.sensorLocationY != 0
            // device dimensions in the current rotation
            val size = windowManager.maximumWindowMetrics.bounds
            val isDefaultOrientation = rotation.isDefaultOrientation()
            // Width and height are flipped is device is not in rotation_0 or rotation_180
            // Flipping it to the width and height of the device in default orientation.
            val displayWidth = if (isDefaultOrientation) size.width() else size.height()
            val displayHeight = if (isDefaultOrientation) size.height() else size.width()
            val sensorWidth = context.resources?.getInteger(R.integer.config_sfpsSensorWidth) ?: 0

            val (sensorLeft, sensorTop) =
                if (isSensorVerticalInDefaultOrientation) {
                    when (rotation) {
                        DisplayRotation.ROTATION_0 -> {
                            Pair(displayWidth, sensorLocation.sensorLocationY)
                        }
                        DisplayRotation.ROTATION_90 -> {
                            Pair(sensorLocation.sensorLocationY, 0)
                        }
                        DisplayRotation.ROTATION_180 -> {
                            Pair(0, displayHeight - sensorLocation.sensorLocationY - sensorWidth)
                        }
                        DisplayRotation.ROTATION_270 -> {
                            Pair(
                                displayHeight - sensorLocation.sensorLocationY - sensorWidth,
                                displayWidth
                            )
                        }
                    }
                } else {
                    when (rotation) {
                        DisplayRotation.ROTATION_0 -> {
                            Pair(sensorLocation.sensorLocationX, 0)
                        }
                        DisplayRotation.ROTATION_90 -> {
                            Pair(0, displayWidth - sensorLocation.sensorLocationX - sensorWidth)
                        }
                        DisplayRotation.ROTATION_180 -> {
                            Pair(
                                displayWidth - sensorLocation.sensorLocationX - sensorWidth,
                                displayHeight
                            )
                        }
                        DisplayRotation.ROTATION_270 -> {
                            Pair(displayHeight, sensorLocation.sensorLocationX)
                        }
                    }
                }

            SideFpsSensorLocation(
                left = sensorLeft,
                top = sensorTop,
                width = sensorWidth,
                isSensorVerticalInDefaultOrientation = isSensorVerticalInDefaultOrientation
            )
        }
}
Loading