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.keyguard.ui.composable.section
18 
19 import android.content.Context
20 import android.util.DisplayMetrics
21 import android.view.View
22 import android.view.WindowManager
23 import androidx.compose.runtime.Composable
24 import androidx.compose.ui.Modifier
25 import androidx.compose.ui.graphics.Color
26 import androidx.compose.ui.layout.layout
27 import androidx.compose.ui.platform.LocalContext
28 import androidx.compose.ui.unit.Constraints
29 import androidx.compose.ui.unit.IntOffset
30 import androidx.compose.ui.unit.IntRect
31 import androidx.compose.ui.viewinterop.AndroidView
32 import com.android.compose.animation.scene.ElementKey
33 import com.android.compose.animation.scene.SceneScope
34 import com.android.keyguard.LockIconView
35 import com.android.keyguard.LockIconViewController
36 import com.android.systemui.biometrics.AuthController
37 import com.android.systemui.dagger.qualifiers.Application
38 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
39 import com.android.systemui.flags.FeatureFlagsClassic
40 import com.android.systemui.flags.Flags
41 import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
42 import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder
43 import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
44 import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
45 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel
46 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
47 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
48 import com.android.systemui.plugins.FalsingManager
49 import com.android.systemui.res.R
50 import com.android.systemui.shade.NotificationPanelView
51 import com.android.systemui.statusbar.VibratorHelper
52 import dagger.Lazy
53 import javax.inject.Inject
54 import kotlinx.coroutines.CoroutineScope
55 
56 class LockSection
57 @Inject
58 constructor(
59     @Application private val applicationScope: CoroutineScope,
60     private val windowManager: WindowManager,
61     private val authController: AuthController,
62     private val featureFlags: FeatureFlagsClassic,
63     private val lockIconViewController: Lazy<LockIconViewController>,
64     private val deviceEntryIconViewModel: Lazy<DeviceEntryIconViewModel>,
65     private val deviceEntryForegroundViewModel: Lazy<DeviceEntryForegroundViewModel>,
66     private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>,
67     private val falsingManager: Lazy<FalsingManager>,
68     private val vibratorHelper: Lazy<VibratorHelper>,
69     private val notificationPanelView: NotificationPanelView,
70 ) {
71     @Composable
72     fun SceneScope.LockIcon(overrideColor: Color? = null, modifier: Modifier = Modifier) {
73         if (!KeyguardBottomAreaRefactor.isEnabled && !DeviceEntryUdfpsRefactor.isEnabled) {
74             return
75         }
76 
77         notificationPanelView.findViewById<View?>(R.id.lock_icon_view)?.let {
78             notificationPanelView.removeView(it)
79         }
80 
81         val context = LocalContext.current
82 
83         AndroidView(
84             factory = { context ->
85                 val view =
86                     if (DeviceEntryUdfpsRefactor.isEnabled) {
87                         DeviceEntryIconView(context, null).apply {
88                             id = R.id.device_entry_icon_view
89                             DeviceEntryIconViewBinder.bind(
90                                 applicationScope,
91                                 this,
92                                 deviceEntryIconViewModel.get(),
93                                 deviceEntryForegroundViewModel.get(),
94                                 deviceEntryBackgroundViewModel.get(),
95                                 falsingManager.get(),
96                                 vibratorHelper.get(),
97                                 overrideColor,
98                             )
99                         }
100                     } else {
101                         // KeyguardBottomAreaRefactor.isEnabled
102                         LockIconView(context, null).apply {
103                             id = R.id.lock_icon_view
104                             lockIconViewController.get().setLockIconView(this)
105                         }
106                     }
107                 view
108             },
109             modifier =
110                 modifier.element(LockIconElementKey).layout { measurable, _ ->
111                     val lockIconBounds = lockIconBounds(context)
112                     val placeable =
113                         measurable.measure(
114                             Constraints.fixed(
115                                 width = lockIconBounds.width,
116                                 height = lockIconBounds.height,
117                             )
118                         )
119                     layout(
120                         width = placeable.width,
121                         height = placeable.height,
122                         alignmentLines =
123                             mapOf(
124                                 BlueprintAlignmentLines.LockIcon.Left to lockIconBounds.left,
125                                 BlueprintAlignmentLines.LockIcon.Top to lockIconBounds.top,
126                                 BlueprintAlignmentLines.LockIcon.Right to lockIconBounds.right,
127                                 BlueprintAlignmentLines.LockIcon.Bottom to lockIconBounds.bottom,
128                             ),
129                     ) {
130                         placeable.place(0, 0)
131                     }
132                 },
133         )
134     }
135 
136     /**
137      * Returns the bounds of the lock icon, in window view coordinates.
138      *
139      * On devices that support UDFPS (under-display fingerprint sensor), the bounds of the icon are
140      * the same as the bounds of the sensor.
141      */
142     private fun lockIconBounds(
143         context: Context,
144     ): IntRect {
145         val windowViewBounds = windowManager.currentWindowMetrics.bounds
146         var widthPx = windowViewBounds.right.toFloat()
147         if (featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) {
148             val insets = windowManager.currentWindowMetrics.windowInsets
149             // Assumed to be initially neglected as there are no left or right insets in portrait.
150             // However, on landscape, these insets need to included when calculating the midpoint.
151             @Suppress("DEPRECATION")
152             widthPx -= (insets.systemWindowInsetLeft + insets.systemWindowInsetRight).toFloat()
153         }
154         val defaultDensity =
155             DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
156                 DisplayMetrics.DENSITY_DEFAULT.toFloat()
157         val lockIconRadiusPx = (defaultDensity * 36).toInt()
158 
159         val udfpsLocation = authController.udfpsLocation
160         val (center, radius) =
161             if (authController.isUdfpsSupported && udfpsLocation != null) {
162                 Pair(
163                     IntOffset(
164                         x = udfpsLocation.x,
165                         y = udfpsLocation.y,
166                     ),
167                     authController.udfpsRadius.toInt(),
168                 )
169             } else {
170                 val scaleFactor = authController.scaleFactor
171                 val bottomPaddingPx =
172                     context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
173                 val heightPx = windowViewBounds.bottom.toFloat()
174 
175                 Pair(
176                     IntOffset(
177                         x = (widthPx / 2).toInt(),
178                         y =
179                             (heightPx - ((bottomPaddingPx + lockIconRadiusPx) * scaleFactor))
180                                 .toInt(),
181                     ),
182                     (lockIconRadiusPx * scaleFactor).toInt(),
183                 )
184             }
185 
186         return IntRect(center, radius)
187     }
188 }
189 
190 private val LockIconElementKey = ElementKey("LockIcon")
191