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