• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2023 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 package com.android.launcher3.util
17 
18 import android.content.res.Configuration
19 import android.content.res.Resources
20 import android.graphics.Point
21 import android.graphics.Rect
22 import android.hardware.display.DisplayManager
23 import android.util.ArrayMap
24 import android.util.DisplayMetrics
25 import android.view.Display
26 import android.view.Surface
27 import androidx.test.annotation.UiThreadTest
28 import androidx.test.filters.SmallTest
29 import com.android.launcher3.LauncherPrefs
30 import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
31 import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING_IN_DESKTOP_MODE
32 import com.android.launcher3.dagger.LauncherAppComponent
33 import com.android.launcher3.dagger.LauncherAppSingleton
34 import com.android.launcher3.util.DisplayController.CHANGE_DENSITY
35 import com.android.launcher3.util.DisplayController.CHANGE_DESKTOP_MODE
36 import com.android.launcher3.util.DisplayController.CHANGE_ROTATION
37 import com.android.launcher3.util.DisplayController.CHANGE_SHOW_LOCKED_TASKBAR
38 import com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING
39 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener
40 import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
41 import com.android.launcher3.util.window.CachedDisplayInfo
42 import com.android.launcher3.util.window.WindowManagerProxy
43 import dagger.BindsInstance
44 import dagger.Component
45 import junit.framework.Assert.assertFalse
46 import junit.framework.Assert.assertTrue
47 import kotlin.math.min
48 import org.junit.After
49 import org.junit.Before
50 import org.junit.Test
51 import org.junit.runner.RunWith
52 import org.mockito.kotlin.any
53 import org.mockito.kotlin.anyOrNull
54 import org.mockito.kotlin.doNothing
55 import org.mockito.kotlin.doReturn
56 import org.mockito.kotlin.eq
57 import org.mockito.kotlin.mock
58 import org.mockito.kotlin.spy
59 import org.mockito.kotlin.verify
60 import org.mockito.kotlin.whenever
61 import org.mockito.stubbing.Answer
62 
63 /** Unit tests for {@link DisplayController} */
64 @SmallTest
65 @RunWith(LauncherMultivalentJUnit::class)
66 class DisplayControllerTest {
67 
68     private val context = spy(SandboxModelContext())
69     private val windowManagerProxy: MyWmProxy = mock()
70     private val launcherPrefs: LauncherPrefs = mock()
71     private lateinit var displayManager: DisplayManager
72     private val display: Display = mock()
73     private val resources: Resources = mock()
74     private val displayInfoChangeListener: DisplayInfoChangeListener = mock()
75 
76     private lateinit var displayController: DisplayController
77 
78     private val width = 2208
79     private val height = 1840
80     private val inset = 110
81     private val densityDpi = 420
82     private val density = densityDpi / DisplayMetrics.DENSITY_DEFAULT.toFloat()
83     private val bounds =
84         arrayOf(
85             WindowBounds(Rect(0, 0, width, height), Rect(0, inset, 0, 0), Surface.ROTATION_0),
86             WindowBounds(Rect(0, 0, height, width), Rect(0, inset, 0, 0), Surface.ROTATION_90),
87             WindowBounds(Rect(0, 0, width, height), Rect(0, inset, 0, 0), Surface.ROTATION_180),
88             WindowBounds(Rect(0, 0, height, width), Rect(0, inset, 0, 0), Surface.ROTATION_270),
89         )
90     private val configuration =
91         Configuration(context.resources.configuration).apply {
92             densityDpi = this@DisplayControllerTest.densityDpi
93             screenWidthDp = (bounds[0].bounds.width() / density).toInt()
94             screenHeightDp = (bounds[0].bounds.height() / density).toInt()
95             smallestScreenWidthDp = min(screenWidthDp, screenHeightDp)
96         }
97 
98     @Before
99     fun setUp() {
100         context.initDaggerComponent(
101             DaggerDisplayControllerTestComponent.builder()
102                 .bindWMProxy(windowManagerProxy)
103                 .bindLauncherPrefs(launcherPrefs)
104         )
105         displayManager = context.spyService(DisplayManager::class.java)
106 
107         whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false)
108         whenever(launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(true)
109 
110         // Mock WindowManagerProxy
111         val displayInfo = CachedDisplayInfo(Point(width, height), Surface.ROTATION_0)
112         whenever(windowManagerProxy.getDisplayInfo(any())).thenReturn(displayInfo)
113         whenever(windowManagerProxy.estimateInternalDisplayBounds(any()))
114             .thenAnswer(
115                 Answer {
116                     // Always create a new copy of bounds
117                     val perDisplayBounds = ArrayMap<CachedDisplayInfo, List<WindowBounds>>()
118                     perDisplayBounds[displayInfo] = bounds.toList()
119                     return@Answer perDisplayBounds
120                 }
121             )
122         whenever(windowManagerProxy.getRealBounds(any(), any())).thenAnswer { i ->
123             bounds[i.getArgument<CachedDisplayInfo>(1).rotation]
124         }
125         whenever(windowManagerProxy.showLockedTaskbarOnHome(any())).thenReturn(false)
126 
127         whenever(windowManagerProxy.getNavigationMode(any())).thenReturn(NavigationMode.NO_BUTTON)
128         // Mock context
129         doReturn(context).whenever(context).createWindowContext(any(), any(), anyOrNull())
130         doNothing().whenever(context).registerComponentCallbacks(any())
131 
132         // Mock display
133         whenever(display.rotation).thenReturn(displayInfo.rotation)
134         doReturn(display).whenever(context).display
135         doReturn(display).whenever(displayManager).getDisplay(any())
136 
137         // Mock resources
138         doReturn(context).whenever(context).applicationContext
139         whenever(resources.configuration).thenReturn(configuration)
140         whenever(context.resources).thenReturn(resources)
141 
142         // Initialize DisplayController
143         displayController = DisplayController.INSTANCE.get(context)
144         displayController.addChangeListener(displayInfoChangeListener)
145     }
146 
147     @After
148     fun tearDown() {
149         // We need to reset the taskbar mode preference override even if a test throws an exception.
150         // Otherwise, it may break the following tests' assumptions.
151         DisplayController.enableTaskbarModePreferenceForTests(false)
152         context.onDestroy()
153     }
154 
155     @Test
156     @UiThreadTest
157     fun testRotation() {
158         val displayInfo = CachedDisplayInfo(Point(height, width), Surface.ROTATION_90)
159         whenever(windowManagerProxy.getDisplayInfo(any())).thenReturn(displayInfo)
160         whenever(display.rotation).thenReturn(displayInfo.rotation)
161         val configuration =
162             Configuration(configuration).apply {
163                 screenWidthDp = configuration.screenHeightDp
164                 screenHeightDp = configuration.screenWidthDp
165             }
166         whenever(resources.configuration).thenReturn(configuration)
167 
168         displayController.onConfigurationChanged(configuration)
169 
170         verify(displayInfoChangeListener).onDisplayInfoChanged(any(), any(), eq(CHANGE_ROTATION))
171     }
172 
173     @Test
174     @UiThreadTest
175     fun testFontScale() {
176         val configuration = Configuration(configuration).apply { fontScale = 1.2f }
177         whenever(resources.configuration).thenReturn(configuration)
178 
179         displayController.onConfigurationChanged(configuration)
180 
181         verify(displayInfoChangeListener).onDisplayInfoChanged(any(), any(), eq(CHANGE_DENSITY))
182     }
183 
184     @Test
185     @UiThreadTest
186     fun testTaskbarPinning() {
187         whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(true)
188         displayController.notifyConfigChange()
189         verify(displayInfoChangeListener)
190             .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING))
191     }
192 
193     @Test
194     @UiThreadTest
195     fun testTaskbarPinningChangeInDesktopMode() {
196         whenever(launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(false)
197         displayController.notifyConfigChange()
198         verify(displayInfoChangeListener)
199             .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING))
200     }
201 
202     @Test
203     @UiThreadTest
204     fun testTaskbarPinningChangeInLockedTaskbarChange() {
205         whenever(windowManagerProxy.showLockedTaskbarOnHome(any())).thenReturn(true)
206         whenever(windowManagerProxy.isHomeVisible(any())).thenReturn(true)
207         whenever(windowManagerProxy.isInDesktopMode(any())).thenReturn(false)
208         whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false)
209         DisplayController.enableTaskbarModePreferenceForTests(true)
210 
211         assertTrue(displayController.getInfo().isTransientTaskbar())
212         displayController.notifyConfigChange()
213 
214         verify(displayInfoChangeListener)
215             .onDisplayInfoChanged(
216                 any(),
217                 any(),
218                 eq(CHANGE_TASKBAR_PINNING or CHANGE_SHOW_LOCKED_TASKBAR),
219             )
220         assertFalse(displayController.getInfo().isTransientTaskbar())
221     }
222 
223     @Test
224     @UiThreadTest
225     fun testLockedTaskbarChangeOnConfigurationChanged() {
226         whenever(windowManagerProxy.showLockedTaskbarOnHome(any())).thenReturn(true)
227         whenever(windowManagerProxy.isHomeVisible(any())).thenReturn(true)
228         whenever(windowManagerProxy.isInDesktopMode(any())).thenReturn(false)
229         whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false)
230         DisplayController.enableTaskbarModePreferenceForTests(true)
231         assertTrue(displayController.getInfo().isTransientTaskbar())
232 
233         displayController.onConfigurationChanged(configuration)
234 
235         verify(displayInfoChangeListener)
236             .onDisplayInfoChanged(
237                 any(),
238                 any(),
239                 eq(CHANGE_TASKBAR_PINNING or CHANGE_SHOW_LOCKED_TASKBAR),
240             )
241         assertFalse(displayController.getInfo().isTransientTaskbar())
242     }
243 
244     @Test
245     @UiThreadTest
246     fun testTaskbarPinnedForDesktopTaskbar_inDesktopMode() {
247         whenever(windowManagerProxy.showDesktopTaskbarForFreeformDisplay(any())).thenReturn(true)
248         whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false)
249         whenever(launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(false)
250         whenever(windowManagerProxy.isInDesktopMode(any())).thenReturn(true)
251         whenever(windowManagerProxy.isHomeVisible(any())).thenReturn(false)
252         DisplayController.enableTaskbarModePreferenceForTests(true)
253 
254         assertTrue(displayController.getInfo().isTransientTaskbar())
255 
256         displayController.onConfigurationChanged(configuration)
257 
258         verify(displayInfoChangeListener)
259             .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING or CHANGE_DESKTOP_MODE))
260         assertFalse(displayController.getInfo().isTransientTaskbar())
261     }
262 
263     @Test
264     @UiThreadTest
265     fun testTaskbarPinnedForDesktopTaskbar_notInDesktopMode() {
266         whenever(windowManagerProxy.showDesktopTaskbarForFreeformDisplay(any())).thenReturn(true)
267         whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false)
268         whenever(launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(false)
269         whenever(windowManagerProxy.isInDesktopMode(any())).thenReturn(false)
270         whenever(windowManagerProxy.isHomeVisible(any())).thenReturn(false)
271         DisplayController.enableTaskbarModePreferenceForTests(true)
272 
273         assertTrue(displayController.getInfo().isTransientTaskbar())
274 
275         displayController.onConfigurationChanged(configuration)
276 
277         verify(displayInfoChangeListener)
278             .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING))
279         assertFalse(displayController.getInfo().isTransientTaskbar())
280     }
281 
282     @Test
283     @UiThreadTest
284     fun testTaskbarPinnedForDesktopTaskbar_onHome() {
285         whenever(windowManagerProxy.showDesktopTaskbarForFreeformDisplay(any())).thenReturn(true)
286         whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false)
287         whenever(launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(false)
288         whenever(windowManagerProxy.isInDesktopMode(any())).thenReturn(false)
289         whenever(windowManagerProxy.isHomeVisible(any())).thenReturn(true)
290         DisplayController.enableTaskbarModePreferenceForTests(true)
291 
292         assertTrue(displayController.getInfo().isTransientTaskbar())
293 
294         displayController.onConfigurationChanged(configuration)
295 
296         verify(displayInfoChangeListener)
297             .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING))
298         assertFalse(displayController.getInfo().isTransientTaskbar())
299     }
300 }
301 
302 class MyWmProxy : WindowManagerProxy()
303 
304 @LauncherAppSingleton
305 @Component(modules = [AllModulesMinusWMProxy::class])
306 interface DisplayControllerTestComponent : LauncherAppComponent {
307 
308     @Component.Builder
309     interface Builder : LauncherAppComponent.Builder {
bindWMProxynull310         @BindsInstance fun bindWMProxy(proxy: WindowManagerProxy): Builder
311 
312         @BindsInstance fun bindLauncherPrefs(prefs: LauncherPrefs): Builder
313 
314         override fun build(): DisplayControllerTestComponent
315     }
316 }
317