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.wifi.ui.binder 18 19 import android.content.res.ColorStateList 20 import android.view.View 21 import android.view.ViewGroup 22 import android.widget.ImageView 23 import androidx.core.view.isVisible 24 import androidx.lifecycle.Lifecycle 25 import androidx.lifecycle.repeatOnLifecycle 26 import com.android.systemui.R 27 import com.android.systemui.common.ui.binder.IconViewBinder 28 import com.android.systemui.lifecycle.repeatWhenAttached 29 import com.android.systemui.statusbar.StatusBarIconView 30 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN 31 import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding 32 import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewVisibilityHelper 33 import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon 34 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel 35 import kotlinx.coroutines.InternalCoroutinesApi 36 import kotlinx.coroutines.awaitCancellation 37 import kotlinx.coroutines.flow.MutableStateFlow 38 import kotlinx.coroutines.flow.distinctUntilChanged 39 import kotlinx.coroutines.launch 40 41 /** 42 * Binds a wifi icon in the status bar to its view-model. 43 * 44 * To use this properly, users should maintain a one-to-one relationship between the [View] and the 45 * view-binding, binding each view only once. It is okay and expected for the same instance of the 46 * view-model to be reused for multiple view/view-binder bindings. 47 */ 48 @OptIn(InternalCoroutinesApi::class) 49 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") 50 object WifiViewBinder { 51 52 /** Binds the view to the view-model, continuing to update the former based on the latter. */ 53 @JvmStatic 54 fun bind( 55 view: ViewGroup, 56 viewModel: LocationBasedWifiViewModel, 57 ): ModernStatusBarViewBinding { 58 val groupView = view.requireViewById<ViewGroup>(R.id.wifi_group) 59 val iconView = view.requireViewById<ImageView>(R.id.wifi_signal) 60 val dotView = view.requireViewById<StatusBarIconView>(R.id.status_bar_dot) 61 val activityInView = view.requireViewById<ImageView>(R.id.wifi_in) 62 val activityOutView = view.requireViewById<ImageView>(R.id.wifi_out) 63 val activityContainerView = view.requireViewById<View>(R.id.inout_container) 64 val airplaneSpacer = view.requireViewById<View>(R.id.wifi_airplane_spacer) 65 val signalSpacer = view.requireViewById<View>(R.id.wifi_signal_spacer) 66 67 view.isVisible = true 68 iconView.isVisible = true 69 70 // TODO(b/238425913): We should log this visibility state. 71 @StatusBarIconView.VisibleState 72 val visibilityState: MutableStateFlow<Int> = MutableStateFlow(STATE_HIDDEN) 73 74 val iconTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor) 75 val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor) 76 77 var isCollecting: Boolean = false 78 79 view.repeatWhenAttached { 80 repeatOnLifecycle(Lifecycle.State.STARTED) { 81 isCollecting = true 82 83 launch { 84 visibilityState.collect { visibilityState -> 85 // for b/296864006, we can not hide all the child views if visibilityState 86 // is STATE_HIDDEN. Because hiding all child views would cause the 87 // getWidth() of this view return 0, and that would cause the translation 88 // calculation fails in StatusIconContainer. Therefore, like class 89 // MobileIconBinder, instead of set the child views visibility to View.GONE, 90 // we set their visibility to View.INVISIBLE to make them invisible but 91 // keep the width. 92 ModernStatusBarViewVisibilityHelper.setVisibilityState( 93 visibilityState, 94 groupView, 95 dotView, 96 ) 97 } 98 } 99 100 launch { 101 viewModel.wifiIcon.collect { wifiIcon -> 102 view.isVisible = wifiIcon is WifiIcon.Visible 103 if (wifiIcon is WifiIcon.Visible) { 104 IconViewBinder.bind(wifiIcon.icon, iconView) 105 } 106 } 107 } 108 109 launch { 110 iconTint.collect { tint -> 111 val tintList = ColorStateList.valueOf(tint) 112 iconView.imageTintList = tintList 113 activityInView.imageTintList = tintList 114 activityOutView.imageTintList = tintList 115 dotView.setDecorColor(tint) 116 } 117 } 118 119 launch { decorTint.collect { tint -> dotView.setDecorColor(tint) } } 120 121 launch { 122 viewModel.isActivityInViewVisible.distinctUntilChanged().collect { visible -> 123 activityInView.isVisible = visible 124 } 125 } 126 127 launch { 128 viewModel.isActivityOutViewVisible.distinctUntilChanged().collect { visible -> 129 activityOutView.isVisible = visible 130 } 131 } 132 133 launch { 134 viewModel.isActivityContainerVisible.distinctUntilChanged().collect { visible -> 135 activityContainerView.isVisible = visible 136 } 137 } 138 139 launch { 140 viewModel.isAirplaneSpacerVisible.distinctUntilChanged().collect { visible -> 141 airplaneSpacer.isVisible = visible 142 } 143 } 144 145 launch { 146 viewModel.isSignalSpacerVisible.distinctUntilChanged().collect { visible -> 147 signalSpacer.isVisible = visible 148 } 149 } 150 151 try { 152 awaitCancellation() 153 } finally { 154 isCollecting = false 155 } 156 } 157 } 158 159 return object : ModernStatusBarViewBinding { 160 override fun getShouldIconBeVisible(): Boolean { 161 return viewModel.wifiIcon.value is WifiIcon.Visible 162 } 163 164 override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) { 165 visibilityState.value = state 166 } 167 168 override fun onIconTintChanged(newTint: Int) { 169 iconTint.value = newTint 170 } 171 172 override fun onDecorTintChanged(newTint: Int) { 173 decorTint.value = newTint 174 } 175 176 override fun isCollecting(): Boolean { 177 return isCollecting 178 } 179 } 180 } 181 } 182