1 /* 2 * 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.content.res.ColorStateList 20 import android.view.View 21 import android.view.View.GONE 22 import android.view.View.VISIBLE 23 import android.view.ViewGroup 24 import android.widget.ImageView 25 import android.widget.Space 26 import androidx.core.view.isVisible 27 import androidx.lifecycle.Lifecycle 28 import androidx.lifecycle.lifecycleScope 29 import androidx.lifecycle.repeatOnLifecycle 30 import com.android.settingslib.graph.SignalDrawable 31 import com.android.systemui.R 32 import com.android.systemui.common.ui.binder.ContentDescriptionViewBinder 33 import com.android.systemui.common.ui.binder.IconViewBinder 34 import com.android.systemui.lifecycle.repeatWhenAttached 35 import com.android.systemui.statusbar.StatusBarIconView 36 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN 37 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger 38 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel 39 import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding 40 import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewVisibilityHelper 41 import kotlinx.coroutines.awaitCancellation 42 import kotlinx.coroutines.flow.MutableStateFlow 43 import kotlinx.coroutines.flow.distinctUntilChanged 44 import kotlinx.coroutines.launch 45 46 object MobileIconBinder { 47 /** Binds the view to the view-model, continuing to update the former based on the latter */ 48 @JvmStatic 49 fun bind( 50 view: ViewGroup, 51 viewModel: LocationBasedMobileViewModel, 52 @StatusBarIconView.VisibleState initialVisibilityState: Int = STATE_HIDDEN, 53 logger: MobileViewLogger, 54 ): ModernStatusBarViewBinding { 55 val mobileGroupView = view.requireViewById<ViewGroup>(R.id.mobile_group) 56 val activityContainer = view.requireViewById<View>(R.id.inout_container) 57 val activityIn = view.requireViewById<ImageView>(R.id.mobile_in) 58 val activityOut = view.requireViewById<ImageView>(R.id.mobile_out) 59 val networkTypeView = view.requireViewById<ImageView>(R.id.mobile_type) 60 val iconView = view.requireViewById<ImageView>(R.id.mobile_signal) 61 val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) } 62 val roamingView = view.requireViewById<ImageView>(R.id.mobile_roaming) 63 val roamingSpace = view.requireViewById<Space>(R.id.mobile_roaming_space) 64 val dotView = view.requireViewById<StatusBarIconView>(R.id.status_bar_dot) 65 66 view.isVisible = viewModel.isVisible.value 67 iconView.isVisible = true 68 69 // TODO(b/238425913): We should log this visibility state. 70 @StatusBarIconView.VisibleState 71 val visibilityState: MutableStateFlow<Int> = MutableStateFlow(initialVisibilityState) 72 73 val iconTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor) 74 val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor) 75 76 var isCollecting = false 77 78 view.repeatWhenAttached { 79 lifecycleScope.launch { 80 repeatOnLifecycle(Lifecycle.State.CREATED) { 81 // isVisible controls the visibility state of the outer group, and thus it needs 82 // to run in the CREATED lifecycle so it can continue to watch while invisible 83 // See (b/291031862) for details 84 launch { 85 viewModel.isVisible.collect { isVisible -> 86 viewModel.verboseLogger?.logBinderReceivedVisibility( 87 view, 88 viewModel.subscriptionId, 89 isVisible 90 ) 91 view.isVisible = isVisible 92 // [StatusIconContainer] can get out of sync sometimes. Make sure to 93 // request another layout when this changes. 94 view.requestLayout() 95 } 96 } 97 } 98 } 99 100 lifecycleScope.launch { 101 repeatOnLifecycle(Lifecycle.State.STARTED) { 102 logger.logCollectionStarted(view, viewModel) 103 isCollecting = true 104 105 launch { 106 visibilityState.collect { state -> 107 ModernStatusBarViewVisibilityHelper.setVisibilityState( 108 state, 109 mobileGroupView, 110 dotView, 111 ) 112 } 113 } 114 115 // Set the icon for the triangle 116 launch { 117 viewModel.icon.distinctUntilChanged().collect { icon -> 118 viewModel.verboseLogger?.logBinderReceivedSignalIcon( 119 view, 120 viewModel.subscriptionId, 121 icon, 122 ) 123 mobileDrawable.level = icon.toSignalDrawableState() 124 } 125 } 126 127 launch { 128 viewModel.contentDescription.distinctUntilChanged().collect { 129 ContentDescriptionViewBinder.bind(it, view) 130 } 131 } 132 133 // Set the network type icon 134 launch { 135 viewModel.networkTypeIcon.distinctUntilChanged().collect { dataTypeId -> 136 viewModel.verboseLogger?.logBinderReceivedNetworkTypeIcon( 137 view, 138 viewModel.subscriptionId, 139 dataTypeId, 140 ) 141 dataTypeId?.let { IconViewBinder.bind(dataTypeId, networkTypeView) } 142 networkTypeView.visibility = if (dataTypeId != null) VISIBLE else GONE 143 } 144 } 145 146 // Set the roaming indicator 147 launch { 148 viewModel.roaming.distinctUntilChanged().collect { isRoaming -> 149 roamingView.isVisible = isRoaming 150 roamingSpace.isVisible = isRoaming 151 } 152 } 153 154 // Set the activity indicators 155 launch { viewModel.activityInVisible.collect { activityIn.isVisible = it } } 156 157 launch { viewModel.activityOutVisible.collect { activityOut.isVisible = it } } 158 159 launch { 160 viewModel.activityContainerVisible.collect { 161 activityContainer.isVisible = it 162 } 163 } 164 165 // Set the tint 166 launch { 167 iconTint.collect { tint -> 168 val tintList = ColorStateList.valueOf(tint) 169 iconView.imageTintList = tintList 170 networkTypeView.imageTintList = tintList 171 roamingView.imageTintList = tintList 172 activityIn.imageTintList = tintList 173 activityOut.imageTintList = tintList 174 dotView.setDecorColor(tint) 175 } 176 } 177 178 launch { decorTint.collect { tint -> dotView.setDecorColor(tint) } } 179 180 try { 181 awaitCancellation() 182 } finally { 183 isCollecting = false 184 logger.logCollectionStopped(view, viewModel) 185 } 186 } 187 } 188 } 189 190 return object : ModernStatusBarViewBinding { 191 override fun getShouldIconBeVisible(): Boolean { 192 return viewModel.isVisible.value 193 } 194 195 override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) { 196 visibilityState.value = state 197 } 198 199 override fun onIconTintChanged(newTint: Int) { 200 iconTint.value = newTint 201 } 202 203 override fun onDecorTintChanged(newTint: Int) { 204 decorTint.value = newTint 205 } 206 207 override fun isCollecting(): Boolean { 208 return isCollecting 209 } 210 } 211 } 212 } 213