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