1 /*
<lambda>null2  * Copyright (C) 2023 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.domain.interactor
18 
19 import android.content.Context
20 import android.hardware.fingerprint.FingerprintManager
21 import android.util.Log
22 import android.view.MotionEvent
23 import com.android.systemui.biometrics.AuthController
24 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
25 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
26 import com.android.systemui.common.coroutine.ConflatedCallbackFlow
27 import com.android.systemui.dagger.SysUISingleton
28 import com.android.systemui.dagger.qualifiers.Application
29 import com.android.systemui.res.R
30 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
31 import javax.inject.Inject
32 import kotlinx.coroutines.CoroutineScope
33 import kotlinx.coroutines.channels.awaitClose
34 import kotlinx.coroutines.flow.Flow
35 import kotlinx.coroutines.flow.MutableStateFlow
36 import kotlinx.coroutines.flow.SharingStarted
37 import kotlinx.coroutines.flow.StateFlow
38 import kotlinx.coroutines.flow.asStateFlow
39 import kotlinx.coroutines.flow.map
40 import kotlinx.coroutines.flow.stateIn
41 
42 /** Encapsulates business logic for interacting with the UDFPS overlay. */
43 @SysUISingleton
44 class UdfpsOverlayInteractor
45 @Inject
46 constructor(
47     @Application private val context: Context,
48     private val authController: AuthController,
49     private val selectedUserInteractor: SelectedUserInteractor,
50     private val fingerprintManager: FingerprintManager?,
51     @Application scope: CoroutineScope
52 ) {
53     private fun calculateIconSize(): Int {
54         val pixelPitch = context.resources.getFloat(R.dimen.pixel_pitch)
55         if (pixelPitch <= 0) {
56             Log.e(
57                 "UdfpsOverlayInteractor",
58                 "invalid pixelPitch: $pixelPitch. Pixel pitch must be updated per device."
59             )
60         }
61         return (context.resources.getFloat(R.dimen.udfps_icon_size) / pixelPitch).toInt()
62     }
63 
64     private var iconSize: Int = calculateIconSize()
65 
66     /** Whether a touch is within the under-display fingerprint sensor area */
67     fun isTouchWithinUdfpsArea(ev: MotionEvent): Boolean {
68         val isUdfpsEnrolled =
69             authController.isUdfpsEnrolled(selectedUserInteractor.getSelectedUserId())
70         val isWithinOverlayBounds =
71             udfpsOverlayParams.value.overlayBounds.contains(ev.rawX.toInt(), ev.rawY.toInt())
72         return isUdfpsEnrolled && isWithinOverlayBounds
73     }
74 
75     private var _requestId = MutableStateFlow(0L)
76 
77     /** RequestId of current AcquisitionClient */
78     val requestId: StateFlow<Long> = _requestId.asStateFlow()
79 
80     fun setRequestId(requestId: Long) {
81         _requestId.value = requestId
82     }
83 
84     /** Sets whether Udfps overlay should handle touches */
85     fun setHandleTouches(shouldHandle: Boolean = true) {
86         if (authController.isUltrasonicUdfpsSupported
87                 && shouldHandle != _shouldHandleTouches.value) {
88             fingerprintManager?.setIgnoreDisplayTouches(
89                 requestId.value,
90                 authController.udfpsProps!!.get(0).sensorId,
91                 !shouldHandle
92             )
93         }
94         _shouldHandleTouches.value = shouldHandle
95     }
96 
97     private var _shouldHandleTouches = MutableStateFlow(true)
98 
99     /** Whether Udfps overlay should handle touches */
100     val shouldHandleTouches: StateFlow<Boolean> = _shouldHandleTouches.asStateFlow()
101 
102     /** Returns the current udfpsOverlayParams */
103     val udfpsOverlayParams: StateFlow<UdfpsOverlayParams> =
104         ConflatedCallbackFlow.conflatedCallbackFlow {
105                 val callback =
106                     object : AuthController.Callback {
107                         override fun onUdfpsLocationChanged(
108                             udfpsOverlayParams: UdfpsOverlayParams
109                         ) {
110                             trySendWithFailureLogging(
111                                 udfpsOverlayParams,
112                                 TAG,
113                                 "update udfpsOverlayParams"
114                             )
115                         }
116                     }
117                 authController.addCallback(callback)
118                 awaitClose { authController.removeCallback(callback) }
119             }
120             .stateIn(scope, started = SharingStarted.Eagerly, initialValue = UdfpsOverlayParams())
121 
122     // Padding between the fingerprint icon and its bounding box in pixels.
123     val iconPadding: Flow<Int> =
124         udfpsOverlayParams.map { params ->
125             val sensorWidth = params.nativeSensorBounds.right - params.nativeSensorBounds.left
126             val nativePadding = (sensorWidth - iconSize) / 2
127             (nativePadding * params.scaleFactor).toInt()
128         }
129 
130     companion object {
131         private const val TAG = "UdfpsOverlayInteractor"
132     }
133 }
134