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