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.TableLogBuffer 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.google.common.truth.Truth.assertThat 51 import kotlinx.coroutines.CoroutineScope 52 import kotlinx.coroutines.Dispatchers 53 import kotlinx.coroutines.flow.MutableStateFlow 54 import org.junit.Before 55 import org.junit.Test 56 import org.junit.runner.RunWith 57 import org.mockito.Mock 58 import org.mockito.MockitoAnnotations 59 60 @SmallTest 61 @RunWith(AndroidTestingRunner::class) 62 @RunWithLooper(setAsMainLooper = true) 63 class ModernStatusBarWifiViewTest : SysuiTestCase() { 64 65 private lateinit var testableLooper: TestableLooper 66 67 @Mock private lateinit var tableLogBuffer: TableLogBuffer 68 @Mock private lateinit var connectivityConstants: ConnectivityConstants 69 @Mock private lateinit var wifiConstants: WifiConstants 70 private lateinit var airplaneModeRepository: FakeAirplaneModeRepository 71 private lateinit var connectivityRepository: FakeConnectivityRepository 72 private lateinit var wifiRepository: FakeWifiRepository 73 private lateinit var interactor: WifiInteractor 74 private lateinit var viewModel: LocationBasedWifiViewModel 75 private lateinit var scope: CoroutineScope 76 private lateinit var airplaneModeViewModel: AirplaneModeViewModel 77 78 @Before setUpnull79 fun setUp() { 80 MockitoAnnotations.initMocks(this) 81 testableLooper = TestableLooper.get(this) 82 83 airplaneModeRepository = FakeAirplaneModeRepository() 84 connectivityRepository = FakeConnectivityRepository() 85 wifiRepository = FakeWifiRepository() 86 wifiRepository.setIsWifiEnabled(true) 87 scope = CoroutineScope(Dispatchers.Unconfined) 88 interactor = WifiInteractorImpl(connectivityRepository, wifiRepository, scope) 89 airplaneModeViewModel = 90 AirplaneModeViewModelImpl( 91 AirplaneModeInteractor( 92 airplaneModeRepository, 93 connectivityRepository, 94 FakeMobileConnectionsRepository(), 95 ), 96 tableLogBuffer, 97 scope, 98 ) 99 val viewModelCommon = 100 WifiViewModel( 101 airplaneModeViewModel, 102 shouldShowSignalSpacerProvider = { MutableStateFlow(false) }, 103 connectivityConstants, 104 context, 105 tableLogBuffer, 106 interactor, 107 scope, 108 wifiConstants, 109 ) 110 viewModel = 111 viewModelForLocation( 112 viewModelCommon, 113 StatusBarLocation.HOME, 114 ) 115 } 116 117 // Note: The following tests are more like integration tests, since they stand up a full 118 // [WifiViewModel] and test the interactions between the view, view-binder, and view-model. 119 120 @Test setVisibleState_icon_iconShownDotHiddennull121 fun setVisibleState_icon_iconShownDotHidden() { 122 val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) 123 124 view.setVisibleState(STATE_ICON, /* animate= */ false) 125 126 ViewUtils.attachView(view) 127 testableLooper.processAllMessages() 128 129 assertThat(view.getIconGroupView().visibility).isEqualTo(View.VISIBLE) 130 assertThat(view.getDotView().visibility).isEqualTo(View.GONE) 131 132 ViewUtils.detachView(view) 133 } 134 135 @Test setVisibleState_dot_iconHiddenDotShownnull136 fun setVisibleState_dot_iconHiddenDotShown() { 137 val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) 138 139 view.setVisibleState(STATE_DOT, /* animate= */ false) 140 141 ViewUtils.attachView(view) 142 testableLooper.processAllMessages() 143 144 assertThat(view.getIconGroupView().visibility).isEqualTo(View.INVISIBLE) 145 assertThat(view.getDotView().visibility).isEqualTo(View.VISIBLE) 146 147 ViewUtils.detachView(view) 148 } 149 150 @Test setVisibleState_hidden_iconAndDotHiddennull151 fun setVisibleState_hidden_iconAndDotHidden() { 152 val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) 153 154 view.setVisibleState(STATE_HIDDEN, /* animate= */ false) 155 156 ViewUtils.attachView(view) 157 testableLooper.processAllMessages() 158 159 assertThat(view.getIconGroupView().visibility).isEqualTo(View.INVISIBLE) 160 assertThat(view.getDotView().visibility).isEqualTo(View.INVISIBLE) 161 162 ViewUtils.detachView(view) 163 } 164 165 /* Regression test for b/296864006. When STATE_HIDDEN we need to ensure the wifi view width 166 * would not break the StatusIconContainer translation calculation. */ 167 @Test setVisibleState_hidden_keepWidthnull168 fun setVisibleState_hidden_keepWidth() { 169 val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) 170 171 view.setVisibleState(STATE_ICON, /* animate= */ false) 172 173 // get the view width when it's in visible state 174 ViewUtils.attachView(view) 175 val lp = view.layoutParams 176 lp.width = ViewGroup.LayoutParams.WRAP_CONTENT 177 lp.height = ViewGroup.LayoutParams.WRAP_CONTENT 178 view.layoutParams = lp 179 testableLooper.processAllMessages() 180 val currentWidth = view.width 181 182 view.setVisibleState(STATE_HIDDEN, /* animate= */ false) 183 testableLooper.processAllMessages() 184 185 // the view width when STATE_HIDDEN should be at least the width when STATE_ICON. Because 186 // when STATE_HIDDEN the invisible dot view width might be larger than group view width, 187 // then the wifi view width would be enlarged. 188 assertThat(view.width).isAtLeast(currentWidth) 189 190 ViewUtils.detachView(view) 191 } 192 193 @Test isIconVisible_notEnabled_outputsFalsenull194 fun isIconVisible_notEnabled_outputsFalse() { 195 wifiRepository.setIsWifiEnabled(false) 196 wifiRepository.setWifiNetwork( 197 WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2) 198 ) 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( 214 WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2) 215 ) 216 217 val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) 218 219 ViewUtils.attachView(view) 220 testableLooper.processAllMessages() 221 222 assertThat(view.isIconVisible).isTrue() 223 224 ViewUtils.detachView(view) 225 } 226 227 @Test onDarkChanged_iconHasNewColornull228 fun onDarkChanged_iconHasNewColor() { 229 val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) 230 ViewUtils.attachView(view) 231 testableLooper.processAllMessages() 232 233 val color = 0x12345678 234 val contrast = 0x12344321 235 view.onDarkChangedWithContrast(arrayListOf(), color, contrast) 236 testableLooper.processAllMessages() 237 238 assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color)) 239 240 ViewUtils.detachView(view) 241 } 242 243 @Test setStaticDrawableColor_iconHasNewColornull244 fun setStaticDrawableColor_iconHasNewColor() { 245 val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) 246 ViewUtils.attachView(view) 247 testableLooper.processAllMessages() 248 249 val color = 0x23456789 250 val contrast = 0x12344321 251 view.setStaticDrawableColor(color, contrast) 252 testableLooper.processAllMessages() 253 254 assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color)) 255 256 ViewUtils.detachView(view) 257 } 258 getIconGroupViewnull259 private fun View.getIconGroupView(): View { 260 return this.requireViewById(R.id.wifi_group) 261 } 262 getIconViewnull263 private fun View.getIconView(): ImageView { 264 return this.requireViewById(R.id.wifi_signal) 265 } 266 getDotViewnull267 private fun View.getDotView(): View { 268 return this.requireViewById(R.id.status_bar_dot) 269 } 270 } 271 272 private const val SLOT_NAME = "TestSlotName" 273 private const val NETWORK_ID = 200 274