• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.view.Display
25 import androidx.test.ext.junit.runners.AndroidJUnit4
26 import androidx.test.filters.SmallTest
27 import com.android.systemui.SysuiTestCase
28 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
29 import com.google.common.truth.Truth.assertThat
30 import java.util.Locale
31 import org.junit.Before
32 import org.junit.Ignore
33 import org.junit.Test
34 import org.junit.runner.RunWith
35 import org.mockito.Mockito.doAnswer
36 import org.mockito.Mockito.mock
37 import org.mockito.Mockito.never
38 import org.mockito.Mockito.verify
39 
40 @RunWith(AndroidJUnit4::class)
41 @SmallTest
42 class ConfigurationControllerImplTest : SysuiTestCase() {
43 
44     private lateinit var mConfigurationController: ConfigurationControllerImpl
45 
46     @Before
setUpnull47     fun setUp() {
48         mConfigurationController = ConfigurationControllerImpl(mContext)
49     }
50 
51     @Test
testThemeChangenull52     fun testThemeChange() {
53         val listener = mock(ConfigurationListener::class.java)
54         mConfigurationController.addCallback(listener)
55 
56         mConfigurationController.notifyThemeChanged()
57         verify(listener).onThemeChanged()
58     }
59 
60     @Test
testRemoveListenerDuringCallbacknull61     fun testRemoveListenerDuringCallback() {
62         val listener = mock(ConfigurationListener::class.java)
63         mConfigurationController.addCallback(listener)
64         val listener2 = mock(ConfigurationListener::class.java)
65         mConfigurationController.addCallback(listener2)
66 
67         doAnswer {
68                 mConfigurationController.removeCallback(listener2)
69                 null
70             }
71             .`when`(listener)
72             .onThemeChanged()
73 
74         mConfigurationController.notifyThemeChanged()
75         verify(listener).onThemeChanged()
76         verify(listener2, never()).onThemeChanged()
77     }
78 
79     @Test
configChanged_listenerNotifiednull80     fun configChanged_listenerNotified() {
81         val config = mContext.resources.configuration
82         config.densityDpi = 12
83         config.smallestScreenWidthDp = 240
84         mConfigurationController.onConfigurationChanged(config)
85 
86         val listener = createAndAddListener()
87 
88         // WHEN the config is updated
89         config.densityDpi = 20
90         config.smallestScreenWidthDp = 300
91         mConfigurationController.onConfigurationChanged(config)
92 
93         // THEN the listener is notified
94         assertThat(listener.changedConfig?.densityDpi).isEqualTo(20)
95         assertThat(listener.changedConfig?.smallestScreenWidthDp).isEqualTo(300)
96     }
97 
98     @Test
densityChanged_listenerNotifiednull99     fun densityChanged_listenerNotified() {
100         val config = mContext.resources.configuration
101         config.densityDpi = 12
102         mConfigurationController.onConfigurationChanged(config)
103 
104         val listener = createAndAddListener()
105 
106         // WHEN the density is updated
107         config.densityDpi = 20
108         mConfigurationController.onConfigurationChanged(config)
109 
110         // THEN the listener is notified
111         assertThat(listener.densityOrFontScaleChanged).isTrue()
112     }
113 
114     @Test
fontChanged_listenerNotifiednull115     fun fontChanged_listenerNotified() {
116         val config = mContext.resources.configuration
117         config.fontScale = 1.5f
118         mConfigurationController.onConfigurationChanged(config)
119 
120         val listener = createAndAddListener()
121 
122         // WHEN the font is updated
123         config.fontScale = 1.4f
124         mConfigurationController.onConfigurationChanged(config)
125 
126         // THEN the listener is notified
127         assertThat(listener.densityOrFontScaleChanged).isTrue()
128     }
129 
130     @Test
isCarAndUiModeChanged_densityListenerNotifiednull131     fun isCarAndUiModeChanged_densityListenerNotified() {
132         val config = mContext.resources.configuration
133         config.uiMode = UI_MODE_TYPE_CAR or UI_MODE_NIGHT_YES
134         // Re-create the controller since we calculate car mode on creation
135         mConfigurationController = ConfigurationControllerImpl(mContext)
136 
137         val listener = createAndAddListener()
138 
139         // WHEN the ui mode is updated
140         config.uiMode = UI_MODE_TYPE_CAR or UI_MODE_NIGHT_NO
141         mConfigurationController.onConfigurationChanged(config)
142 
143         // THEN the listener is notified
144         assertThat(listener.densityOrFontScaleChanged).isTrue()
145     }
146 
147     @Test
isNotCarAndUiModeChanged_densityListenerNotNotifiednull148     fun isNotCarAndUiModeChanged_densityListenerNotNotified() {
149         val config = mContext.resources.configuration
150         config.uiMode = UI_MODE_NIGHT_YES
151         // Re-create the controller since we calculate car mode on creation
152         mConfigurationController = ConfigurationControllerImpl(mContext)
153 
154         val listener = createAndAddListener()
155 
156         // WHEN the ui mode is updated
157         config.uiMode = UI_MODE_NIGHT_NO
158         mConfigurationController.onConfigurationChanged(config)
159 
160         // THEN the listener is not notified because it's not car mode
161         assertThat(listener.densityOrFontScaleChanged).isFalse()
162     }
163 
164     @Test
smallestScreenWidthChanged_listenerNotifiednull165     fun smallestScreenWidthChanged_listenerNotified() {
166         val config = mContext.resources.configuration
167         config.smallestScreenWidthDp = 240
168         mConfigurationController.onConfigurationChanged(config)
169 
170         val listener = createAndAddListener()
171 
172         // WHEN the width is updated
173         config.smallestScreenWidthDp = 300
174         mConfigurationController.onConfigurationChanged(config)
175 
176         // THEN the listener is notified
177         assertThat(listener.smallestScreenWidthChanged).isTrue()
178     }
179 
180     @Test
maxBoundsChange_newConfigObject_listenerNotifiednull181     fun maxBoundsChange_newConfigObject_listenerNotified() {
182         val config = mContext.resources.configuration
183         config.windowConfiguration.setMaxBounds(0, 0, 200, 200)
184         mConfigurationController.onConfigurationChanged(config)
185 
186         val listener = createAndAddListener()
187 
188         // WHEN a new configuration object with new bounds is sent
189         val newConfig = Configuration()
190         newConfig.windowConfiguration.setMaxBounds(0, 0, 100, 100)
191         mConfigurationController.onConfigurationChanged(newConfig)
192 
193         // THEN the listener is notified
194         assertThat(listener.maxBoundsChanged).isTrue()
195     }
196 
197     // Regression test for b/245799099
198     @Test
maxBoundsChange_sameObject_listenerNotifiednull199     fun maxBoundsChange_sameObject_listenerNotified() {
200         val config = mContext.resources.configuration
201         config.windowConfiguration.setMaxBounds(0, 0, 200, 200)
202         mConfigurationController.onConfigurationChanged(config)
203 
204         val listener = createAndAddListener()
205 
206         // WHEN the existing config is updated with new bounds
207         config.windowConfiguration.setMaxBounds(0, 0, 100, 100)
208         mConfigurationController.onConfigurationChanged(config)
209 
210         // THEN the listener is notified
211         assertThat(listener.maxBoundsChanged).isTrue()
212     }
213 
214     @Test
localeListChanged_listenerNotifiednull215     fun localeListChanged_listenerNotified() {
216         val config = mContext.resources.configuration
217         config.setLocales(LocaleList(Locale.CANADA, Locale.GERMANY))
218         mConfigurationController.onConfigurationChanged(config)
219 
220         val listener = createAndAddListener()
221 
222         // WHEN the locales are updated
223         config.setLocales(LocaleList(Locale.FRANCE, Locale.JAPAN, Locale.CHINESE))
224         mConfigurationController.onConfigurationChanged(config)
225 
226         // THEN the listener is notified
227         assertThat(listener.localeListChanged).isTrue()
228     }
229 
230     @Test
uiModeChanged_listenerNotifiednull231     fun uiModeChanged_listenerNotified() {
232         val config = mContext.resources.configuration
233         config.uiMode = UI_MODE_NIGHT_YES
234         mConfigurationController.onConfigurationChanged(config)
235 
236         val listener = createAndAddListener()
237 
238         // WHEN the ui mode is updated
239         config.uiMode = UI_MODE_NIGHT_NO
240         mConfigurationController.onConfigurationChanged(config)
241 
242         // THEN the listener is notified
243         assertThat(listener.uiModeChanged).isTrue()
244     }
245 
246     @Test
layoutDirectionUpdated_listenerNotifiednull247     fun layoutDirectionUpdated_listenerNotified() {
248         val config = mContext.resources.configuration
249         config.screenLayout = SCREENLAYOUT_LAYOUTDIR_LTR
250         mConfigurationController.onConfigurationChanged(config)
251 
252         val listener = createAndAddListener()
253 
254         // WHEN the layout is updated
255         config.screenLayout = SCREENLAYOUT_LAYOUTDIR_RTL
256         mConfigurationController.onConfigurationChanged(config)
257 
258         // THEN the listener is notified
259         assertThat(listener.layoutDirectionChanged).isTrue()
260     }
261 
262     @Test
assetPathsUpdated_listenerNotifiednull263     fun assetPathsUpdated_listenerNotified() {
264         val config = mContext.resources.configuration
265         config.assetsSeq = 45
266         mConfigurationController.onConfigurationChanged(config)
267 
268         val listener = createAndAddListener()
269 
270         // WHEN the assets sequence is updated
271         config.assetsSeq = 46
272         mConfigurationController.onConfigurationChanged(config)
273 
274         // THEN the listener is notified
275         assertThat(listener.themeChanged).isTrue()
276     }
277 
278     @Test
orientationUpdated_listenerNotifiednull279     fun orientationUpdated_listenerNotified() {
280         val config = mContext.resources.configuration
281         config.orientation = Configuration.ORIENTATION_LANDSCAPE
282         mConfigurationController.onConfigurationChanged(config)
283 
284         val listener = createAndAddListener()
285 
286         // WHEN the orientation is updated
287         config.orientation = Configuration.ORIENTATION_PORTRAIT
288         mConfigurationController.onConfigurationChanged(config)
289 
290         // THEN the listener is notified
291         assertThat(listener.orientationChanged).isTrue()
292     }
293 
294     @Test
multipleUpdates_listenerNotifiedOfAllnull295     fun multipleUpdates_listenerNotifiedOfAll() {
296         val config = mContext.resources.configuration
297         config.densityDpi = 14
298         config.windowConfiguration.setMaxBounds(0, 0, 2, 2)
299         config.uiMode = UI_MODE_NIGHT_YES
300         mConfigurationController.onConfigurationChanged(config)
301 
302         val listener = createAndAddListener()
303 
304         // WHEN multiple fields are updated
305         config.densityDpi = 20
306         config.windowConfiguration.setMaxBounds(0, 0, 3, 3)
307         config.uiMode = UI_MODE_NIGHT_NO
308         mConfigurationController.onConfigurationChanged(config)
309 
310         // THEN the listener is notified of all of them
311         assertThat(listener.densityOrFontScaleChanged).isTrue()
312         assertThat(listener.maxBoundsChanged).isTrue()
313         assertThat(listener.uiModeChanged).isTrue()
314     }
315 
316     @Test
onMovedToDisplay_dispatchedToChildrennull317     fun onMovedToDisplay_dispatchedToChildren() {
318         val config = mContext.resources.configuration
319         val listener = createAndAddListener()
320 
321         mConfigurationController.dispatchOnMovedToDisplay(newDisplayId = 1, config)
322 
323         assertThat(listener.display).isEqualTo(1)
324         assertThat(listener.changedConfig).isEqualTo(config)
325     }
326 
327     @Test
328     @Ignore("b/261408895")
equivalentConfigObject_listenerNotNotifiednull329     fun equivalentConfigObject_listenerNotNotified() {
330         val config = mContext.resources.configuration
331         val listener = createAndAddListener()
332 
333         // WHEN we update with the new object that has all the same fields
334         mConfigurationController.onConfigurationChanged(Configuration(config))
335 
336         listener.assertNoMethodsCalled()
337     }
338 
createAndAddListenernull339     private fun createAndAddListener(): TestListener {
340         val listener = TestListener()
341         mConfigurationController.addCallback(listener)
342         // Adding a listener can trigger some callbacks, so we want to reset the values right
343         // after the listener is added
344         listener.reset()
345         return listener
346     }
347 
348     private class TestListener : ConfigurationListener {
349         var changedConfig: Configuration? = null
350         var densityOrFontScaleChanged = false
351         var smallestScreenWidthChanged = false
352         var maxBoundsChanged = false
353         var uiModeChanged = false
354         var themeChanged = false
355         var localeListChanged = false
356         var layoutDirectionChanged = false
357         var orientationChanged = false
358         var display = Display.DEFAULT_DISPLAY
359 
onConfigChangednull360         override fun onConfigChanged(newConfig: Configuration?) {
361             changedConfig = newConfig
362         }
363 
onDensityOrFontScaleChangednull364         override fun onDensityOrFontScaleChanged() {
365             densityOrFontScaleChanged = true
366         }
367 
onSmallestScreenWidthChangednull368         override fun onSmallestScreenWidthChanged() {
369             smallestScreenWidthChanged = true
370         }
371 
onMaxBoundsChangednull372         override fun onMaxBoundsChanged() {
373             maxBoundsChanged = true
374         }
375 
onUiModeChangednull376         override fun onUiModeChanged() {
377             uiModeChanged = true
378         }
379 
onThemeChangednull380         override fun onThemeChanged() {
381             themeChanged = true
382         }
383 
onLocaleListChangednull384         override fun onLocaleListChanged() {
385             localeListChanged = true
386         }
387 
onLayoutDirectionChangednull388         override fun onLayoutDirectionChanged(isLayoutRtl: Boolean) {
389             layoutDirectionChanged = true
390         }
391 
onOrientationChangednull392         override fun onOrientationChanged(orientation: Int) {
393             orientationChanged = true
394         }
395 
onMovedToDisplaynull396         override fun onMovedToDisplay(newDisplayId: Int, newConfiguration: Configuration?) {
397             display = newDisplayId
398             changedConfig = newConfiguration
399         }
400 
assertNoMethodsCallednull401         fun assertNoMethodsCalled() {
402             assertThat(densityOrFontScaleChanged).isFalse()
403             assertThat(smallestScreenWidthChanged).isFalse()
404             assertThat(maxBoundsChanged).isFalse()
405             assertThat(uiModeChanged).isFalse()
406             assertThat(themeChanged).isFalse()
407             assertThat(localeListChanged).isFalse()
408             assertThat(layoutDirectionChanged).isFalse()
409         }
410 
resetnull411         fun reset() {
412             changedConfig = null
413             densityOrFontScaleChanged = false
414             smallestScreenWidthChanged = false
415             maxBoundsChanged = false
416             uiModeChanged = false
417             themeChanged = false
418             localeListChanged = false
419             layoutDirectionChanged = false
420             display = Display.DEFAULT_DISPLAY
421         }
422     }
423 }
424