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