1 /*
2  * 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 
18 package com.android.systemui.keyguard.ui.binder
19 
20 import android.graphics.RectF
21 import android.view.View
22 import android.widget.FrameLayout
23 import androidx.asynclayoutinflater.view.AsyncLayoutInflater
24 import androidx.lifecycle.Lifecycle
25 import androidx.lifecycle.repeatOnLifecycle
26 import com.android.systemui.R
27 import com.android.systemui.biometrics.UdfpsKeyguardView
28 import com.android.systemui.biometrics.ui.binder.UdfpsKeyguardInternalViewBinder
29 import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
30 import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel
31 import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel
32 import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel
33 import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModel
34 import com.android.systemui.lifecycle.repeatWhenAttached
35 import kotlinx.coroutines.ExperimentalCoroutinesApi
36 import kotlinx.coroutines.flow.combine
37 import kotlinx.coroutines.launch
38 
39 @ExperimentalCoroutinesApi
40 object UdfpsKeyguardViewBinder {
41     /**
42      * Drives UI for the keyguard UDFPS view. Inflates child views on a background thread. For view
43      * binders for its child views, see [UdfpsFingerprintViewBinder], [UdfpsBackgroundViewBinder] &
44      * [UdfpsAodFingerprintViewBinder].
45      */
46     @JvmStatic
47     fun bind(
48         view: UdfpsKeyguardView,
49         viewModel: UdfpsKeyguardViewModel,
50         udfpsKeyguardInternalViewModel: UdfpsKeyguardInternalViewModel,
51         aodViewModel: UdfpsAodViewModel,
52         fingerprintViewModel: FingerprintViewModel,
53         backgroundViewModel: BackgroundViewModel,
54     ) {
55         view.useExpandedOverlay(viewModel.useExpandedOverlay())
56 
57         val layoutInflaterFinishListener =
58             AsyncLayoutInflater.OnInflateFinishedListener { inflatedInternalView, _, parent ->
59                 UdfpsKeyguardInternalViewBinder.bind(
60                     inflatedInternalView,
61                     udfpsKeyguardInternalViewModel,
62                     aodViewModel,
63                     fingerprintViewModel,
64                     backgroundViewModel,
65                 )
66                 if (viewModel.useExpandedOverlay()) {
67                     val lp = inflatedInternalView.layoutParams as FrameLayout.LayoutParams
68                     lp.width = viewModel.sensorBounds.width()
69                     lp.height = viewModel.sensorBounds.height()
70                     val relativeToView =
71                         getBoundsRelativeToView(
72                             inflatedInternalView,
73                             RectF(viewModel.sensorBounds),
74                         )
75                     lp.setMarginsRelative(
76                         relativeToView.left.toInt(),
77                         relativeToView.top.toInt(),
78                         relativeToView.right.toInt(),
79                         relativeToView.bottom.toInt(),
80                     )
81                     parent!!.addView(inflatedInternalView, lp)
82                 } else {
83                     parent!!.addView(inflatedInternalView)
84                 }
85             }
86         val inflater = AsyncLayoutInflater(view.context)
87         inflater.inflate(R.layout.udfps_keyguard_view_internal, view, layoutInflaterFinishListener)
88 
89         view.repeatWhenAttached {
90             repeatOnLifecycle(Lifecycle.State.CREATED) {
91                 launch {
92                     combine(aodViewModel.isVisible, fingerprintViewModel.visible) {
93                             isAodVisible,
94                             isFingerprintVisible ->
95                             isAodVisible || isFingerprintVisible
96                         }
97                         .collect { view.setVisible(it) }
98                 }
99             }
100         }
101     }
102 
103     /**
104      * Converts coordinates of RectF relative to the screen to coordinates relative to this view.
105      *
106      * @param bounds RectF based off screen coordinates in current orientation
107      */
108     private fun getBoundsRelativeToView(view: View, bounds: RectF): RectF {
109         val pos: IntArray = view.locationOnScreen
110         return RectF(
111             bounds.left - pos[0],
112             bounds.top - pos[1],
113             bounds.right - pos[0],
114             bounds.bottom - pos[1]
115         )
116     }
117 }
118