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.view 18 19 import android.content.res.ColorStateList 20 import android.testing.AndroidTestingRunner 21 import android.testing.TestableLooper 22 import android.testing.TestableLooper.RunWithLooper 23 import android.testing.ViewUtils 24 import android.view.View 25 import android.view.ViewGroup 26 import android.widget.ImageView 27 import androidx.test.filters.SmallTest 28 import com.android.systemui.SysuiTestCase 29 import com.android.systemui.log.table.logcatTableLogBuffer 30 import com.android.systemui.res.R 31 import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT 32 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN 33 import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON 34 import com.android.systemui.statusbar.phone.StatusBarLocation 35 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository 36 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor 37 import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel 38 import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModelImpl 39 import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository 40 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants 41 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository 42 import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository 43 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor 44 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl 45 import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants 46 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel 47 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel 48 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel.Companion.viewModelForLocation 49 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel 50 import com.android.systemui.testKosmos 51 import com.google.common.truth.Truth.assertThat 52 import kotlinx.coroutines.CoroutineScope 53 import kotlinx.coroutines.Dispatchers 54 import kotlinx.coroutines.flow.MutableStateFlow 55 import org.junit.Before 56 import org.junit.Test 57 import org.junit.runner.RunWith 58 import org.mockito.Mock 59 import org.mockito.MockitoAnnotations 60 61 @SmallTest 62 @RunWith(AndroidTestingRunner::class) 63 @RunWithLooper(setAsMainLooper = true) 64 class ModernStatusBarWifiViewTest : SysuiTestCase() { 65 private val kosmos = testKosmos() 66 67 private lateinit var testableLooper: TestableLooper 68 69 private val tableLogBuffer = logcatTableLogBuffer(kosmos, "ModernStatusBarWifiViewTest") 70 @Mock private lateinit var connectivityConstants: ConnectivityConstants 71 @Mock private lateinit var wifiConstants: WifiConstants 72 private lateinit var airplaneModeRepository: FakeAirplaneModeRepository 73 private lateinit var connectivityRepository: FakeConnectivityRepository 74 private lateinit var wifiRepository: FakeWifiRepository 75 private lateinit var interactor: WifiInteractor 76 private lateinit var viewModel: LocationBasedWifiViewModel 77 private lateinit var scope: CoroutineScope 78 private lateinit var airplaneModeViewModel: AirplaneModeViewModel 79 80 @Before setUpnull81 fun setUp() { 82 MockitoAnnotations.initMocks(this) 83 testableLooper = TestableLooper.get(this) 84 85 airplaneModeRepository = FakeAirplaneModeRepository() 86 connectivityRepository = FakeConnectivityRepository() 87 wifiRepository = FakeWifiRepository() 88 wifiRepository.setIsWifiEnabled(true) 89 scope = CoroutineScope(Dispatchers.Unconfined) 90 interactor = WifiInteractorImpl(connectivityRepository, wifiRepository, scope) 91 airplaneModeViewModel = 92 AirplaneModeViewModelImpl( 93 AirplaneModeInteractor( 94 airplaneModeRepository, 95 connectivityRepository, 96 kosmos.fakeMobileConnectionsRepository, 97 ), 98 tableLogBuffer, 99 scope, 100 ) 101 val viewModelCommon = 102 WifiViewModel( 103 airplaneModeViewModel, 104 shouldShowSignalSpacerProvider = { MutableStateFlow(false) }, 105 connectivityConstants, 106 context, 107 tableLogBuffer, 108 interactor, 109 scope, 110 wifiConstants, 111 ) 112 viewModel = 113 viewModelForLocation( 114 viewModelCommon, 115 StatusBarLocation.HOME, 116 ) 117 } 118 119 // Note: The following tests are more like integration tests, since they stand up a full 120 // [WifiViewModel] and test the interactions between the view, view-binder, and view-model. 121 122 @Test setVisibleState_icon_iconShownDotHiddennull123 fun setVisibleState_icon_iconShownDotHidden() { 124 val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) 125 126 view.setVisibleState(STATE_ICON, /* animate= */ false) 127 128 ViewUtils.attachView(view) 129 testableLooper.processAllMessages() 130 131 assertThat(view.getIconGroupView().visibility).isEqualTo(View.VISIBLE) 132 assertThat(view.getDotView().visibility).isEqualTo(View.GONE) 133 134 ViewUtils.detachView(view) 135 } 136 137 @Test setVisibleState_dot_iconHiddenDotShownnull138 fun setVisibleState_dot_iconHiddenDotShown() { 139 val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) 140 141 view.setVisibleState(STATE_DOT, /* animate= */ false) 142 143 ViewUtils.attachView(view) 144 testableLooper.processAllMessages() 145 146 assertThat(view.getIconGroupView().visibility).isEqualTo(View.INVISIBLE) 147 assertThat(view.getDotView().visibility).isEqualTo(View.VISIBLE) 148 149 ViewUtils.detachView(view) 150 } 151 152 @Test setVisibleState_hidden_iconAndDotHiddennull153 fun setVisibleState_hidden_iconAndDotHidden() { 154 val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) 155 156 view.setVisibleState(STATE_HIDDEN, /* animate= */ false) 157 158 ViewUtils.attachView(view) 159 testableLooper.processAllMessages() 160 161 assertThat(view.getIconGroupView().visibility).isEqualTo(View.INVISIBLE) 162 assertThat(view.getDotView().visibility).isEqualTo(View.INVISIBLE) 163 164 ViewUtils.detachView(view) 165 } 166 167 /* Regression test for b/296864006. When STATE_HIDDEN we need to ensure the wifi view width 168 * would not break the StatusIconContainer translation calculation. */ 169 @Test setVisibleState_hidden_keepWidthnull170 fun setVisibleState_hidden_keepWidth() { 171 val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) 172 173 view.setVisibleState(STATE_ICON, /* animate= */ false) 174 175 // get the view width when it's in visible state 176 ViewUtils.attachView(view) 177 val lp = view.layoutParams 178 lp.width = ViewGroup.LayoutParams.WRAP_CONTENT 179 lp.height = ViewGroup.LayoutParams.WRAP_CONTENT 180 view.layoutParams = lp 181 testableLooper.processAllMessages() 182 val currentWidth = view.width 183 184 view.setVisibleState(STATE_HIDDEN, /* animate= */ false) 185 testableLooper.processAllMessages() 186 187 // the view width when STATE_HIDDEN should be at least the width when STATE_ICON. Because 188 // when STATE_HIDDEN the invisible dot view width might be larger than group view width, 189 // then the wifi view width would be enlarged. 190 assertThat(view.width).isAtLeast(currentWidth) 191 192 ViewUtils.detachView(view) 193 } 194 195 @Test isIconVisible_notEnabled_outputsFalsenull196 fun isIconVisible_notEnabled_outputsFalse() { 197 wifiRepository.setIsWifiEnabled(false) 198 wifiRepository.setWifiNetwork(WifiNetworkModel.Active.of(isValidated = true, level = 2)) 199 200 val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) 201 202 ViewUtils.attachView(view) 203 testableLooper.processAllMessages() 204 205 assertThat(view.isIconVisible).isFalse() 206 207 ViewUtils.detachView(view) 208 } 209 210 @Test isIconVisible_enabled_outputsTruenull211 fun isIconVisible_enabled_outputsTrue() { 212 wifiRepository.setIsWifiEnabled(true) 213 wifiRepository.setWifiNetwork(WifiNetworkModel.Active.of(isValidated = true, level = 2)) 214 215 val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) 216 217 ViewUtils.attachView(view) 218 testableLooper.processAllMessages() 219 220 assertThat(view.isIconVisible).isTrue() 221 222 ViewUtils.detachView(view) 223 } 224 225 @Test onDarkChanged_iconHasNewColornull226 fun onDarkChanged_iconHasNewColor() { 227 val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) 228 ViewUtils.attachView(view) 229 testableLooper.processAllMessages() 230 231 val color = 0x12345678 232 val contrast = 0x12344321 233 view.onDarkChangedWithContrast(arrayListOf(), color, contrast) 234 testableLooper.processAllMessages() 235 236 assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color)) 237 238 ViewUtils.detachView(view) 239 } 240 241 @Test setStaticDrawableColor_iconHasNewColornull242 fun setStaticDrawableColor_iconHasNewColor() { 243 val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) 244 ViewUtils.attachView(view) 245 testableLooper.processAllMessages() 246 247 val color = 0x23456789 248 val contrast = 0x12344321 249 view.setStaticDrawableColor(color, contrast) 250 testableLooper.processAllMessages() 251 252 assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color)) 253 254 ViewUtils.detachView(view) 255 } 256 getIconGroupViewnull257 private fun View.getIconGroupView(): View { 258 return this.requireViewById(R.id.wifi_group) 259 } 260 getIconViewnull261 private fun View.getIconView(): ImageView { 262 return this.requireViewById(R.id.wifi_signal) 263 } 264 getDotViewnull265 private fun View.getDotView(): View { 266 return this.requireViewById(R.id.status_bar_dot) 267 } 268 } 269 270 private const val SLOT_NAME = "TestSlotName" 271