1 /* 2 * Copyright (C) 2021 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.qs.tileimpl 18 19 import android.content.Context 20 import android.graphics.drawable.Drawable 21 import android.service.quicksettings.Tile 22 import android.testing.AndroidTestingRunner 23 import android.testing.TestableLooper 24 import android.text.TextUtils 25 import android.view.ContextThemeWrapper 26 import android.view.View 27 import android.view.accessibility.AccessibilityNodeInfo 28 import android.widget.TextView 29 import androidx.test.filters.SmallTest 30 import com.android.systemui.R 31 import com.android.systemui.SysuiTestCase 32 import com.android.systemui.plugins.qs.QSIconView 33 import com.android.systemui.plugins.qs.QSTile 34 import com.google.common.truth.Truth.assertThat 35 import org.junit.Before 36 import org.junit.Test 37 import org.junit.runner.RunWith 38 import org.mockito.Mock 39 import org.mockito.MockitoAnnotations 40 41 @RunWith(AndroidTestingRunner::class) 42 @SmallTest 43 @TestableLooper.RunWithLooper(setAsMainLooper = true) 44 class QSTileViewImplTest : SysuiTestCase() { 45 46 @Mock 47 private lateinit var iconView: QSIconView 48 @Mock 49 private lateinit var customDrawable: Drawable 50 51 private lateinit var tileView: FakeTileView 52 private lateinit var customDrawableView: View 53 private lateinit var chevronView: View 54 55 @Before 56 fun setUp() { 57 MockitoAnnotations.initMocks(this) 58 context.ensureTestableResources() 59 60 tileView = FakeTileView(context, iconView, false) 61 customDrawableView = tileView.requireViewById(R.id.customDrawable) 62 chevronView = tileView.requireViewById(R.id.chevron) 63 } 64 65 @Test 66 fun testSecondaryLabelNotModified_unavailable() { 67 val state = QSTile.State() 68 val testString = "TEST STRING" 69 state.state = Tile.STATE_UNAVAILABLE 70 state.secondaryLabel = testString 71 72 tileView.changeState(state) 73 74 assertThat(state.secondaryLabel as CharSequence).isEqualTo(testString) 75 } 76 77 @Test 78 fun testSecondaryLabelNotModified_booleanInactive() { 79 val state = QSTile.BooleanState() 80 val testString = "TEST STRING" 81 state.state = Tile.STATE_INACTIVE 82 state.secondaryLabel = testString 83 84 tileView.changeState(state) 85 86 assertThat(state.secondaryLabel as CharSequence).isEqualTo(testString) 87 } 88 89 @Test 90 fun testSecondaryLabelNotModified_booleanActive() { 91 val state = QSTile.BooleanState() 92 val testString = "TEST STRING" 93 state.state = Tile.STATE_ACTIVE 94 state.secondaryLabel = testString 95 96 tileView.changeState(state) 97 98 assertThat(state.secondaryLabel as CharSequence).isEqualTo(testString) 99 } 100 101 @Test 102 fun testSecondaryLabelNotModified_availableNotBoolean_inactive() { 103 val state = QSTile.State() 104 state.state = Tile.STATE_INACTIVE 105 state.secondaryLabel = "" 106 107 tileView.changeState(state) 108 109 assertThat(TextUtils.isEmpty(state.secondaryLabel)).isTrue() 110 } 111 112 @Test 113 fun testSecondaryLabelNotModified_availableNotBoolean_active() { 114 val state = QSTile.State() 115 state.state = Tile.STATE_ACTIVE 116 state.secondaryLabel = "" 117 118 tileView.changeState(state) 119 120 assertThat(TextUtils.isEmpty(state.secondaryLabel)).isTrue() 121 } 122 123 @Test 124 fun testSecondaryLabelDescription_unavailable_default() { 125 val state = QSTile.State() 126 state.state = Tile.STATE_UNAVAILABLE 127 state.secondaryLabel = "" 128 129 tileView.changeState(state) 130 131 assertThat(state.secondaryLabel as CharSequence).isEqualTo( 132 context.getString(R.string.tile_unavailable) 133 ) 134 } 135 136 @Test 137 fun testSecondaryLabelDescription_booleanInactive_default() { 138 val state = QSTile.BooleanState() 139 state.state = Tile.STATE_INACTIVE 140 state.secondaryLabel = "" 141 142 tileView.changeState(state) 143 144 assertThat(state.secondaryLabel as CharSequence).isEqualTo( 145 context.getString(R.string.switch_bar_off) 146 ) 147 } 148 149 @Test 150 fun testSecondaryLabelDescription_booleanActive_default() { 151 val state = QSTile.BooleanState() 152 state.state = Tile.STATE_ACTIVE 153 state.secondaryLabel = "" 154 155 tileView.changeState(state) 156 157 assertThat(state.secondaryLabel as CharSequence).isEqualTo( 158 context.getString(R.string.switch_bar_on) 159 ) 160 } 161 162 @Test 163 fun testShowCustomDrawableViewBooleanState() { 164 val state = QSTile.BooleanState() 165 state.sideViewCustomDrawable = customDrawable 166 167 tileView.changeState(state) 168 169 assertThat(customDrawableView.visibility).isEqualTo(View.VISIBLE) 170 assertThat(chevronView.visibility).isEqualTo(View.GONE) 171 } 172 173 @Test 174 fun testShowCustomDrawableViewNonBooleanState() { 175 val state = QSTile.State() 176 state.sideViewCustomDrawable = customDrawable 177 178 tileView.changeState(state) 179 180 assertThat(customDrawableView.visibility).isEqualTo(View.VISIBLE) 181 assertThat(chevronView.visibility).isEqualTo(View.GONE) 182 } 183 184 @Test 185 fun testShowCustomDrawableViewBooleanStateForceChevron() { 186 val state = QSTile.BooleanState() 187 state.sideViewCustomDrawable = customDrawable 188 state.forceExpandIcon = true 189 190 tileView.changeState(state) 191 192 assertThat(customDrawableView.visibility).isEqualTo(View.VISIBLE) 193 assertThat(chevronView.visibility).isEqualTo(View.GONE) 194 } 195 196 @Test 197 fun testShowChevronNonBooleanState() { 198 val state = QSTile.State() 199 200 tileView.changeState(state) 201 202 assertThat(customDrawableView.visibility).isEqualTo(View.GONE) 203 assertThat(chevronView.visibility).isEqualTo(View.VISIBLE) 204 } 205 206 @Test 207 fun testShowChevronBooleanStateForcheShow() { 208 val state = QSTile.BooleanState() 209 state.forceExpandIcon = true 210 211 tileView.changeState(state) 212 213 assertThat(customDrawableView.visibility).isEqualTo(View.GONE) 214 assertThat(chevronView.visibility).isEqualTo(View.VISIBLE) 215 } 216 217 @Test 218 fun testNoImageShown() { 219 val state = QSTile.BooleanState() 220 221 tileView.changeState(state) 222 223 assertThat(customDrawableView.visibility).isEqualTo(View.GONE) 224 assertThat(chevronView.visibility).isEqualTo(View.GONE) 225 } 226 227 @Test 228 fun testUseStateStringsForKnownSpec_Boolean() { 229 val state = QSTile.BooleanState() 230 val spec = "internet" 231 state.spec = spec 232 233 val unavailableString = "${spec}_unavailable" 234 val offString = "${spec}_off" 235 val onString = "${spec}_on" 236 237 context.orCreateTestableResources.addOverride(R.array.tile_states_internet, arrayOf( 238 unavailableString, 239 offString, 240 onString 241 )) 242 243 // State UNAVAILABLE 244 state.secondaryLabel = "" 245 state.state = Tile.STATE_UNAVAILABLE 246 tileView.changeState(state) 247 assertThat((tileView.secondaryLabel as TextView).text).isEqualTo(unavailableString) 248 249 // State INACTIVE 250 state.secondaryLabel = "" 251 state.state = Tile.STATE_INACTIVE 252 tileView.changeState(state) 253 assertThat((tileView.secondaryLabel as TextView).text).isEqualTo(offString) 254 255 // State ACTIVE 256 state.secondaryLabel = "" 257 state.state = Tile.STATE_ACTIVE 258 tileView.changeState(state) 259 assertThat((tileView.secondaryLabel as TextView).text).isEqualTo(onString) 260 } 261 262 @Test 263 fun testCollectionItemInfoHasPosition() { 264 val position = 5 265 tileView.setPosition(position) 266 267 val info = AccessibilityNodeInfo(tileView) 268 tileView.onInitializeAccessibilityNodeInfo(info) 269 270 assertThat(info.collectionItemInfo.rowIndex).isEqualTo(position) 271 assertThat(info.collectionItemInfo.rowSpan).isEqualTo(1) 272 assertThat(info.collectionItemInfo.columnIndex).isEqualTo(0) 273 assertThat(info.collectionItemInfo.columnSpan).isEqualTo(1) 274 } 275 276 @Test 277 fun testCollectionItemInfoNoPosition() { 278 val info = AccessibilityNodeInfo(tileView) 279 tileView.onInitializeAccessibilityNodeInfo(info) 280 281 assertThat(info.collectionItemInfo).isNull() 282 } 283 284 @Test 285 fun testDisabledByPolicyInactive_usesUnavailableColors() { 286 val stateDisabledByPolicy = QSTile.State() 287 stateDisabledByPolicy.state = Tile.STATE_INACTIVE 288 stateDisabledByPolicy.disabledByPolicy = true 289 290 val stateUnavailable = QSTile.State() 291 stateUnavailable.state = Tile.STATE_UNAVAILABLE 292 293 tileView.changeState(stateDisabledByPolicy) 294 val colorsDisabledByPolicy = tileView.getCurrentColors() 295 296 tileView.changeState(stateUnavailable) 297 val colorsUnavailable = tileView.getCurrentColors() 298 299 assertThat(colorsDisabledByPolicy).containsExactlyElementsIn(colorsUnavailable) 300 } 301 302 @Test 303 fun testDisabledByPolicyActive_usesUnavailableColors() { 304 val stateDisabledByPolicy = QSTile.State() 305 stateDisabledByPolicy.state = Tile.STATE_ACTIVE 306 stateDisabledByPolicy.disabledByPolicy = true 307 308 val stateUnavailable = QSTile.State() 309 stateUnavailable.state = Tile.STATE_UNAVAILABLE 310 311 tileView.changeState(stateDisabledByPolicy) 312 val colorsDisabledByPolicy = tileView.getCurrentColors() 313 314 tileView.changeState(stateUnavailable) 315 val colorsUnavailable = tileView.getCurrentColors() 316 317 assertThat(colorsDisabledByPolicy).containsExactlyElementsIn(colorsUnavailable) 318 } 319 320 @Test 321 fun testDisableByPolicyThenRemoved_changesColor() { 322 val stateActive = QSTile.State() 323 stateActive.state = Tile.STATE_ACTIVE 324 325 val stateDisabledByPolicy = stateActive.copy() 326 stateDisabledByPolicy.disabledByPolicy = true 327 328 tileView.changeState(stateActive) 329 val activeColors = tileView.getCurrentColors() 330 331 tileView.changeState(stateDisabledByPolicy) 332 // It has unavailable colors 333 assertThat(tileView.getCurrentColors()).isNotEqualTo(activeColors) 334 335 // When we get back to not disabled by policy tile, it should go back to active colors 336 tileView.changeState(stateActive) 337 assertThat(tileView.getCurrentColors()).containsExactlyElementsIn(activeColors) 338 } 339 340 @Test 341 fun testDisabledByPolicy_secondaryLabelText() { 342 val testA11yLabel = "TEST_LABEL" 343 context.orCreateTestableResources 344 .addOverride( 345 R.string.accessibility_tile_disabled_by_policy_action_description, 346 testA11yLabel 347 ) 348 349 val stateDisabledByPolicy = QSTile.State() 350 stateDisabledByPolicy.state = Tile.STATE_INACTIVE 351 stateDisabledByPolicy.disabledByPolicy = true 352 353 tileView.changeState(stateDisabledByPolicy) 354 355 val info = AccessibilityNodeInfo(tileView) 356 tileView.onInitializeAccessibilityNodeInfo(info) 357 assertThat( 358 info.actionList.find { 359 it.id == AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id 360 }?.label 361 ).isEqualTo(testA11yLabel) 362 } 363 364 @Test 365 fun testDisabledByPolicy_unavailableInStateDescription() { 366 val state = QSTile.BooleanState() 367 val spec = "internet" 368 state.spec = spec 369 state.disabledByPolicy = true 370 state.state = Tile.STATE_INACTIVE 371 372 val unavailableString = "${spec}_unavailable" 373 val offString = "${spec}_off" 374 val onString = "${spec}_on" 375 376 context.orCreateTestableResources.addOverride(R.array.tile_states_internet, arrayOf( 377 unavailableString, 378 offString, 379 onString 380 )) 381 382 tileView.changeState(state) 383 assertThat(tileView.stateDescription?.contains(unavailableString)).isTrue() 384 } 385 386 class FakeTileView( 387 context: Context, 388 icon: QSIconView, 389 collapsed: Boolean 390 ) : QSTileViewImpl( 391 ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings), 392 icon, 393 collapsed 394 ) { 395 fun changeState(state: QSTile.State) { 396 handleStateChanged(state) 397 } 398 } 399 } 400