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