1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package com.android.systemui.statusbar.phone 16 17 import android.content.res.Configuration 18 import android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_LTR 19 import android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_RTL 20 import android.content.res.Configuration.UI_MODE_NIGHT_NO 21 import android.content.res.Configuration.UI_MODE_NIGHT_YES 22 import android.content.res.Configuration.UI_MODE_TYPE_CAR 23 import android.os.LocaleList 24 import androidx.test.ext.junit.runners.AndroidJUnit4 25 import androidx.test.filters.SmallTest 26 import com.android.systemui.SysuiTestCase 27 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener 28 import com.google.common.truth.Truth.assertThat 29 import org.junit.Before 30 import org.junit.Ignore 31 import org.junit.Test 32 import org.junit.runner.RunWith 33 import org.mockito.Mockito.doAnswer 34 import org.mockito.Mockito.mock 35 import org.mockito.Mockito.never 36 import org.mockito.Mockito.verify 37 import java.util.Locale 38 39 @RunWith(AndroidJUnit4::class) 40 @SmallTest 41 class ConfigurationControllerImplTest : SysuiTestCase() { 42 43 private lateinit var mConfigurationController: ConfigurationControllerImpl 44 45 @Before setUpnull46 fun setUp() { 47 mConfigurationController = ConfigurationControllerImpl(mContext) 48 } 49 50 @Test testThemeChangenull51 fun testThemeChange() { 52 val listener = mock(ConfigurationListener::class.java) 53 mConfigurationController.addCallback(listener) 54 55 mConfigurationController.notifyThemeChanged() 56 verify(listener).onThemeChanged() 57 } 58 59 @Test testRemoveListenerDuringCallbacknull60 fun testRemoveListenerDuringCallback() { 61 val listener = mock(ConfigurationListener::class.java) 62 mConfigurationController.addCallback(listener) 63 val listener2 = mock(ConfigurationListener::class.java) 64 mConfigurationController.addCallback(listener2) 65 66 doAnswer { 67 mConfigurationController.removeCallback(listener2) 68 null 69 }.`when`(listener).onThemeChanged() 70 71 mConfigurationController.notifyThemeChanged() 72 verify(listener).onThemeChanged() 73 verify(listener2, never()).onThemeChanged() 74 } 75 76 @Test configChanged_listenerNotifiednull77 fun configChanged_listenerNotified() { 78 val config = mContext.resources.configuration 79 config.densityDpi = 12 80 config.smallestScreenWidthDp = 240 81 mConfigurationController.onConfigurationChanged(config) 82 83 val listener = createAndAddListener() 84 85 // WHEN the config is updated 86 config.densityDpi = 20 87 config.smallestScreenWidthDp = 300 88 mConfigurationController.onConfigurationChanged(config) 89 90 // THEN the listener is notified 91 assertThat(listener.changedConfig?.densityDpi).isEqualTo(20) 92 assertThat(listener.changedConfig?.smallestScreenWidthDp).isEqualTo(300) 93 } 94 95 @Test densityChanged_listenerNotifiednull96 fun densityChanged_listenerNotified() { 97 val config = mContext.resources.configuration 98 config.densityDpi = 12 99 mConfigurationController.onConfigurationChanged(config) 100 101 val listener = createAndAddListener() 102 103 // WHEN the density is updated 104 config.densityDpi = 20 105 mConfigurationController.onConfigurationChanged(config) 106 107 // THEN the listener is notified 108 assertThat(listener.densityOrFontScaleChanged).isTrue() 109 } 110 111 @Test fontChanged_listenerNotifiednull112 fun fontChanged_listenerNotified() { 113 val config = mContext.resources.configuration 114 config.fontScale = 1.5f 115 mConfigurationController.onConfigurationChanged(config) 116 117 val listener = createAndAddListener() 118 119 // WHEN the font is updated 120 config.fontScale = 1.4f 121 mConfigurationController.onConfigurationChanged(config) 122 123 // THEN the listener is notified 124 assertThat(listener.densityOrFontScaleChanged).isTrue() 125 } 126 127 @Test isCarAndUiModeChanged_densityListenerNotifiednull128 fun isCarAndUiModeChanged_densityListenerNotified() { 129 val config = mContext.resources.configuration 130 config.uiMode = UI_MODE_TYPE_CAR or UI_MODE_NIGHT_YES 131 // Re-create the controller since we calculate car mode on creation 132 mConfigurationController = ConfigurationControllerImpl(mContext) 133 134 val listener = createAndAddListener() 135 136 // WHEN the ui mode is updated 137 config.uiMode = UI_MODE_TYPE_CAR or UI_MODE_NIGHT_NO 138 mConfigurationController.onConfigurationChanged(config) 139 140 // THEN the listener is notified 141 assertThat(listener.densityOrFontScaleChanged).isTrue() 142 } 143 144 @Test isNotCarAndUiModeChanged_densityListenerNotNotifiednull145 fun isNotCarAndUiModeChanged_densityListenerNotNotified() { 146 val config = mContext.resources.configuration 147 config.uiMode = UI_MODE_NIGHT_YES 148 // Re-create the controller since we calculate car mode on creation 149 mConfigurationController = ConfigurationControllerImpl(mContext) 150 151 val listener = createAndAddListener() 152 153 // WHEN the ui mode is updated 154 config.uiMode = UI_MODE_NIGHT_NO 155 mConfigurationController.onConfigurationChanged(config) 156 157 // THEN the listener is not notified because it's not car mode 158 assertThat(listener.densityOrFontScaleChanged).isFalse() 159 } 160 161 @Test smallestScreenWidthChanged_listenerNotifiednull162 fun smallestScreenWidthChanged_listenerNotified() { 163 val config = mContext.resources.configuration 164 config.smallestScreenWidthDp = 240 165 mConfigurationController.onConfigurationChanged(config) 166 167 val listener = createAndAddListener() 168 169 // WHEN the width is updated 170 config.smallestScreenWidthDp = 300 171 mConfigurationController.onConfigurationChanged(config) 172 173 // THEN the listener is notified 174 assertThat(listener.smallestScreenWidthChanged).isTrue() 175 } 176 177 @Test maxBoundsChange_newConfigObject_listenerNotifiednull178 fun maxBoundsChange_newConfigObject_listenerNotified() { 179 val config = mContext.resources.configuration 180 config.windowConfiguration.setMaxBounds(0, 0, 200, 200) 181 mConfigurationController.onConfigurationChanged(config) 182 183 val listener = createAndAddListener() 184 185 // WHEN a new configuration object with new bounds is sent 186 val newConfig = Configuration() 187 newConfig.windowConfiguration.setMaxBounds(0, 0, 100, 100) 188 mConfigurationController.onConfigurationChanged(newConfig) 189 190 // THEN the listener is notified 191 assertThat(listener.maxBoundsChanged).isTrue() 192 } 193 194 // Regression test for b/245799099 195 @Test maxBoundsChange_sameObject_listenerNotifiednull196 fun maxBoundsChange_sameObject_listenerNotified() { 197 val config = mContext.resources.configuration 198 config.windowConfiguration.setMaxBounds(0, 0, 200, 200) 199 mConfigurationController.onConfigurationChanged(config) 200 201 val listener = createAndAddListener() 202 203 // WHEN the existing config is updated with new bounds 204 config.windowConfiguration.setMaxBounds(0, 0, 100, 100) 205 mConfigurationController.onConfigurationChanged(config) 206 207 // THEN the listener is notified 208 assertThat(listener.maxBoundsChanged).isTrue() 209 } 210 211 212 @Test localeListChanged_listenerNotifiednull213 fun localeListChanged_listenerNotified() { 214 val config = mContext.resources.configuration 215 config.setLocales(LocaleList(Locale.CANADA, Locale.GERMANY)) 216 mConfigurationController.onConfigurationChanged(config) 217 218 val listener = createAndAddListener() 219 220 // WHEN the locales are updated 221 config.setLocales(LocaleList(Locale.FRANCE, Locale.JAPAN, Locale.CHINESE)) 222 mConfigurationController.onConfigurationChanged(config) 223 224 // THEN the listener is notified 225 assertThat(listener.localeListChanged).isTrue() 226 } 227 228 @Test uiModeChanged_listenerNotifiednull229 fun uiModeChanged_listenerNotified() { 230 val config = mContext.resources.configuration 231 config.uiMode = UI_MODE_NIGHT_YES 232 mConfigurationController.onConfigurationChanged(config) 233 234 val listener = createAndAddListener() 235 236 // WHEN the ui mode is updated 237 config.uiMode = UI_MODE_NIGHT_NO 238 mConfigurationController.onConfigurationChanged(config) 239 240 // THEN the listener is notified 241 assertThat(listener.uiModeChanged).isTrue() 242 } 243 244 @Test layoutDirectionUpdated_listenerNotifiednull245 fun layoutDirectionUpdated_listenerNotified() { 246 val config = mContext.resources.configuration 247 config.screenLayout = SCREENLAYOUT_LAYOUTDIR_LTR 248 mConfigurationController.onConfigurationChanged(config) 249 250 val listener = createAndAddListener() 251 252 // WHEN the layout is updated 253 config.screenLayout = SCREENLAYOUT_LAYOUTDIR_RTL 254 mConfigurationController.onConfigurationChanged(config) 255 256 // THEN the listener is notified 257 assertThat(listener.layoutDirectionChanged).isTrue() 258 } 259 260 @Test assetPathsUpdated_listenerNotifiednull261 fun assetPathsUpdated_listenerNotified() { 262 val config = mContext.resources.configuration 263 config.assetsSeq = 45 264 mConfigurationController.onConfigurationChanged(config) 265 266 val listener = createAndAddListener() 267 268 // WHEN the assets sequence is updated 269 config.assetsSeq = 46 270 mConfigurationController.onConfigurationChanged(config) 271 272 // THEN the listener is notified 273 assertThat(listener.themeChanged).isTrue() 274 } 275 276 @Test orientationUpdated_listenerNotifiednull277 fun orientationUpdated_listenerNotified() { 278 val config = mContext.resources.configuration 279 config.orientation = Configuration.ORIENTATION_LANDSCAPE 280 mConfigurationController.onConfigurationChanged(config) 281 282 val listener = createAndAddListener() 283 284 // WHEN the orientation is updated 285 config.orientation = Configuration.ORIENTATION_PORTRAIT 286 mConfigurationController.onConfigurationChanged(config) 287 288 // THEN the listener is notified 289 assertThat(listener.orientationChanged).isTrue() 290 } 291 292 293 @Test multipleUpdates_listenerNotifiedOfAllnull294 fun multipleUpdates_listenerNotifiedOfAll() { 295 val config = mContext.resources.configuration 296 config.densityDpi = 14 297 config.windowConfiguration.setMaxBounds(0, 0, 2, 2) 298 config.uiMode = UI_MODE_NIGHT_YES 299 mConfigurationController.onConfigurationChanged(config) 300 301 val listener = createAndAddListener() 302 303 // WHEN multiple fields are updated 304 config.densityDpi = 20 305 config.windowConfiguration.setMaxBounds(0, 0, 3, 3) 306 config.uiMode = UI_MODE_NIGHT_NO 307 mConfigurationController.onConfigurationChanged(config) 308 309 // THEN the listener is notified of all of them 310 assertThat(listener.densityOrFontScaleChanged).isTrue() 311 assertThat(listener.maxBoundsChanged).isTrue() 312 assertThat(listener.uiModeChanged).isTrue() 313 } 314 315 @Test 316 @Ignore("b/261408895") equivalentConfigObject_listenerNotNotifiednull317 fun equivalentConfigObject_listenerNotNotified() { 318 val config = mContext.resources.configuration 319 val listener = createAndAddListener() 320 321 // WHEN we update with the new object that has all the same fields 322 mConfigurationController.onConfigurationChanged(Configuration(config)) 323 324 listener.assertNoMethodsCalled() 325 } 326 createAndAddListenernull327 private fun createAndAddListener(): TestListener { 328 val listener = TestListener() 329 mConfigurationController.addCallback(listener) 330 // Adding a listener can trigger some callbacks, so we want to reset the values right 331 // after the listener is added 332 listener.reset() 333 return listener 334 } 335 336 private class TestListener : ConfigurationListener { 337 var changedConfig: Configuration? = null 338 var densityOrFontScaleChanged = false 339 var smallestScreenWidthChanged = false 340 var maxBoundsChanged = false 341 var uiModeChanged = false 342 var themeChanged = false 343 var localeListChanged = false 344 var layoutDirectionChanged = false 345 var orientationChanged = false 346 onConfigChangednull347 override fun onConfigChanged(newConfig: Configuration?) { 348 changedConfig = newConfig 349 } onDensityOrFontScaleChangednull350 override fun onDensityOrFontScaleChanged() { 351 densityOrFontScaleChanged = true 352 } onSmallestScreenWidthChangednull353 override fun onSmallestScreenWidthChanged() { 354 smallestScreenWidthChanged = true 355 } onMaxBoundsChangednull356 override fun onMaxBoundsChanged() { 357 maxBoundsChanged = true 358 } onUiModeChangednull359 override fun onUiModeChanged() { 360 uiModeChanged = true 361 } onThemeChangednull362 override fun onThemeChanged() { 363 themeChanged = true 364 } onLocaleListChangednull365 override fun onLocaleListChanged() { 366 localeListChanged = true 367 } onLayoutDirectionChangednull368 override fun onLayoutDirectionChanged(isLayoutRtl: Boolean) { 369 layoutDirectionChanged = true 370 } onOrientationChangednull371 override fun onOrientationChanged(orientation: Int) { 372 orientationChanged = true 373 } 374 assertNoMethodsCallednull375 fun assertNoMethodsCalled() { 376 assertThat(densityOrFontScaleChanged).isFalse() 377 assertThat(smallestScreenWidthChanged).isFalse() 378 assertThat(maxBoundsChanged).isFalse() 379 assertThat(uiModeChanged).isFalse() 380 assertThat(themeChanged).isFalse() 381 assertThat(localeListChanged).isFalse() 382 assertThat(layoutDirectionChanged).isFalse() 383 } 384 resetnull385 fun reset() { 386 changedConfig = null 387 densityOrFontScaleChanged = false 388 smallestScreenWidthChanged = false 389 maxBoundsChanged = false 390 uiModeChanged = false 391 themeChanged = false 392 localeListChanged = false 393 layoutDirectionChanged = false 394 } 395 } 396 } 397