1 /*
<lambda>null2  * Copyright (C) 2022 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.statusbar.pipeline.mobile.ui.binder
18 
19 import android.annotation.ColorInt
20 import android.content.res.ColorStateList
21 import android.view.View
22 import android.view.View.GONE
23 import android.view.View.VISIBLE
24 import android.view.ViewGroup
25 import android.widget.FrameLayout
26 import android.widget.ImageView
27 import android.widget.Space
28 import androidx.core.view.isVisible
29 import androidx.lifecycle.Lifecycle
30 import androidx.lifecycle.lifecycleScope
31 import androidx.lifecycle.repeatOnLifecycle
32 import com.android.settingslib.graph.SignalDrawable
33 import com.android.systemui.Flags.statusBarStaticInoutIndicators
34 import com.android.systemui.common.ui.binder.ContentDescriptionViewBinder
35 import com.android.systemui.common.ui.binder.IconViewBinder
36 import com.android.systemui.lifecycle.repeatWhenAttached
37 import com.android.systemui.plugins.DarkIconDispatcher
38 import com.android.systemui.res.R
39 import com.android.systemui.statusbar.StatusBarIconView
40 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
41 import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
42 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
43 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
44 import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
45 import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewVisibilityHelper
46 import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarViewBinderConstants.ALPHA_ACTIVE
47 import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarViewBinderConstants.ALPHA_INACTIVE
48 import kotlinx.coroutines.awaitCancellation
49 import kotlinx.coroutines.flow.MutableStateFlow
50 import kotlinx.coroutines.flow.distinctUntilChanged
51 import kotlinx.coroutines.launch
52 
53 private data class Colors(
54     @ColorInt val tint: Int,
55     @ColorInt val contrast: Int,
56 )
57 
58 object MobileIconBinder {
59     /** Binds the view to the view-model, continuing to update the former based on the latter */
60     @JvmStatic
61     fun bind(
62         view: ViewGroup,
63         viewModel: LocationBasedMobileViewModel,
64         @StatusBarIconView.VisibleState initialVisibilityState: Int = STATE_HIDDEN,
65         logger: MobileViewLogger,
66     ): ModernStatusBarViewBinding {
67         val mobileGroupView = view.requireViewById<ViewGroup>(R.id.mobile_group)
68         val activityContainer = view.requireViewById<View>(R.id.inout_container)
69         val activityIn = view.requireViewById<ImageView>(R.id.mobile_in)
70         val activityOut = view.requireViewById<ImageView>(R.id.mobile_out)
71         val networkTypeView = view.requireViewById<ImageView>(R.id.mobile_type)
72         val networkTypeContainer = view.requireViewById<FrameLayout>(R.id.mobile_type_container)
73         val iconView = view.requireViewById<ImageView>(R.id.mobile_signal)
74         val mobileDrawable = SignalDrawable(view.context)
75         val roamingView = view.requireViewById<ImageView>(R.id.mobile_roaming)
76         val roamingSpace = view.requireViewById<Space>(R.id.mobile_roaming_space)
77         val dotView = view.requireViewById<StatusBarIconView>(R.id.status_bar_dot)
78 
79         view.isVisible = viewModel.isVisible.value
80         iconView.isVisible = true
81 
82         // TODO(b/238425913): We should log this visibility state.
83         @StatusBarIconView.VisibleState
84         val visibilityState: MutableStateFlow<Int> = MutableStateFlow(initialVisibilityState)
85 
86         val iconTint: MutableStateFlow<Colors> =
87             MutableStateFlow(
88                 Colors(
89                     tint = DarkIconDispatcher.DEFAULT_ICON_TINT,
90                     contrast = DarkIconDispatcher.DEFAULT_INVERSE_ICON_TINT
91                 )
92             )
93         val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
94 
95         var isCollecting = false
96 
97         view.repeatWhenAttached {
98             lifecycleScope.launch {
99                 repeatOnLifecycle(Lifecycle.State.CREATED) {
100                     // isVisible controls the visibility state of the outer group, and thus it needs
101                     // to run in the CREATED lifecycle so it can continue to watch while invisible
102                     // See (b/291031862) for details
103                     launch {
104                         viewModel.isVisible.collect { isVisible ->
105                             viewModel.verboseLogger?.logBinderReceivedVisibility(
106                                 view,
107                                 viewModel.subscriptionId,
108                                 isVisible
109                             )
110                             view.isVisible = isVisible
111                             // [StatusIconContainer] can get out of sync sometimes. Make sure to
112                             // request another layout when this changes.
113                             view.requestLayout()
114                         }
115                     }
116                 }
117             }
118 
119             lifecycleScope.launch {
120                 repeatOnLifecycle(Lifecycle.State.STARTED) {
121                     logger.logCollectionStarted(view, viewModel)
122                     isCollecting = true
123 
124                     launch {
125                         visibilityState.collect { state ->
126                             ModernStatusBarViewVisibilityHelper.setVisibilityState(
127                                 state,
128                                 mobileGroupView,
129                                 dotView,
130                             )
131 
132                             view.requestLayout()
133                         }
134                     }
135 
136                     // Set the icon for the triangle
137                     launch {
138                         viewModel.icon.distinctUntilChanged().collect { icon ->
139                             viewModel.verboseLogger?.logBinderReceivedSignalIcon(
140                                 view,
141                                 viewModel.subscriptionId,
142                                 icon,
143                             )
144                             if (icon is SignalIconModel.Cellular) {
145                                 iconView.setImageDrawable(mobileDrawable)
146                                 mobileDrawable.level = icon.toSignalDrawableState()
147                             } else if (icon is SignalIconModel.Satellite) {
148                                 IconViewBinder.bind(icon.icon, iconView)
149                             }
150                         }
151                     }
152 
153                     launch {
154                         viewModel.contentDescription.distinctUntilChanged().collect {
155                             ContentDescriptionViewBinder.bind(it, view)
156                         }
157                     }
158 
159                     // Set the network type icon
160                     launch {
161                         viewModel.networkTypeIcon.distinctUntilChanged().collect { dataTypeId ->
162                             viewModel.verboseLogger?.logBinderReceivedNetworkTypeIcon(
163                                 view,
164                                 viewModel.subscriptionId,
165                                 dataTypeId,
166                             )
167                             dataTypeId?.let { IconViewBinder.bind(dataTypeId, networkTypeView) }
168                             val prevVis = networkTypeContainer.visibility
169                             networkTypeContainer.visibility =
170                                 if (dataTypeId != null) VISIBLE else GONE
171 
172                             if (prevVis != networkTypeContainer.visibility) {
173                                 view.requestLayout()
174                             }
175                         }
176                     }
177 
178                     // Set the network type background
179                     launch {
180                         viewModel.networkTypeBackground.collect { background ->
181                             networkTypeContainer.setBackgroundResource(background?.res ?: 0)
182 
183                             // Tint will invert when this bit changes
184                             if (background?.res != null) {
185                                 networkTypeContainer.backgroundTintList =
186                                     ColorStateList.valueOf(iconTint.value.tint)
187                                 networkTypeView.imageTintList =
188                                     ColorStateList.valueOf(iconTint.value.contrast)
189                             } else {
190                                 networkTypeView.imageTintList =
191                                     ColorStateList.valueOf(iconTint.value.tint)
192                             }
193                         }
194                     }
195 
196                     // Set the roaming indicator
197                     launch {
198                         viewModel.roaming.distinctUntilChanged().collect { isRoaming ->
199                             roamingView.isVisible = isRoaming
200                             roamingSpace.isVisible = isRoaming
201                         }
202                     }
203 
204                     if (statusBarStaticInoutIndicators()) {
205                         // Set the opacity of the activity indicators
206                         launch {
207                             viewModel.activityInVisible.collect { visible ->
208                                 activityIn.imageAlpha =
209                                     (if (visible) ALPHA_ACTIVE else ALPHA_INACTIVE)
210                             }
211                         }
212 
213                         launch {
214                             viewModel.activityOutVisible.collect { visible ->
215                                 activityOut.imageAlpha =
216                                     (if (visible) ALPHA_ACTIVE else ALPHA_INACTIVE)
217                             }
218                         }
219                     } else {
220                         // Set the activity indicators
221                         launch { viewModel.activityInVisible.collect { activityIn.isVisible = it } }
222 
223                         launch {
224                             viewModel.activityOutVisible.collect { activityOut.isVisible = it }
225                         }
226                     }
227 
228                     launch {
229                         viewModel.activityContainerVisible.collect {
230                             activityContainer.isVisible = it
231                         }
232                     }
233 
234                     // Set the tint
235                     launch {
236                         iconTint.collect { colors ->
237                             val tint = ColorStateList.valueOf(colors.tint)
238                             val contrast = ColorStateList.valueOf(colors.contrast)
239 
240                             iconView.imageTintList = tint
241 
242                             // If the bg is visible, tint it and use the contrast for the fg
243                             if (viewModel.networkTypeBackground.value != null) {
244                                 networkTypeContainer.backgroundTintList = tint
245                                 networkTypeView.imageTintList = contrast
246                             } else {
247                                 networkTypeView.imageTintList = tint
248                             }
249 
250                             roamingView.imageTintList = tint
251                             activityIn.imageTintList = tint
252                             activityOut.imageTintList = tint
253                             dotView.setDecorColor(colors.tint)
254                         }
255                     }
256 
257                     launch { decorTint.collect { tint -> dotView.setDecorColor(tint) } }
258 
259                     try {
260                         awaitCancellation()
261                     } finally {
262                         isCollecting = false
263                         logger.logCollectionStopped(view, viewModel)
264                     }
265                 }
266             }
267         }
268 
269         return object : ModernStatusBarViewBinding {
270             override fun getShouldIconBeVisible(): Boolean {
271                 return viewModel.isVisible.value
272             }
273 
274             override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) {
275                 visibilityState.value = state
276             }
277 
278             override fun onIconTintChanged(newTint: Int, contrastTint: Int) {
279                 iconTint.value = Colors(tint = newTint, contrast = contrastTint)
280             }
281 
282             override fun onDecorTintChanged(newTint: Int) {
283                 decorTint.value = newTint
284             }
285 
286             override fun isCollecting(): Boolean {
287                 return isCollecting
288             }
289         }
290     }
291 }
292