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.Context 19 import android.content.res.Configuration 20 import android.content.res.Resources 21 import android.graphics.Point 22 import android.graphics.Rect 23 import android.hardware.display.DisplayManager 24 import android.util.ArrayMap 25 import android.util.DisplayMetrics 26 import android.view.Display 27 import android.view.Surface 28 import androidx.test.annotation.UiThreadTest 29 import androidx.test.core.app.ApplicationProvider 30 import androidx.test.filters.SmallTest 31 import com.android.launcher3.LauncherPrefs 32 import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING 33 import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING_IN_DESKTOP_MODE 34 import com.android.launcher3.util.DisplayController.CHANGE_DENSITY 35 import com.android.launcher3.util.DisplayController.CHANGE_ROTATION 36 import com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING 37 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener 38 import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext 39 import com.android.launcher3.util.window.CachedDisplayInfo 40 import com.android.launcher3.util.window.WindowManagerProxy 41 import kotlin.math.min 42 import org.junit.Before 43 import org.junit.Test 44 import org.junit.runner.RunWith 45 import org.mockito.kotlin.any 46 import org.mockito.kotlin.anyOrNull 47 import org.mockito.kotlin.doNothing 48 import org.mockito.kotlin.doReturn 49 import org.mockito.kotlin.eq 50 import org.mockito.kotlin.mock 51 import org.mockito.kotlin.verify 52 import org.mockito.kotlin.whenever 53 import org.mockito.stubbing.Answer 54 55 /** Unit tests for {@link DisplayController} */ 56 @SmallTest 57 @RunWith(LauncherMultivalentJUnit::class) 58 class DisplayControllerTest { 59 60 private val appContext: Context = ApplicationProvider.getApplicationContext() 61 62 private val context: SandboxContext = mock() 63 private val windowManagerProxy: WindowManagerProxy = mock() 64 private val launcherPrefs: LauncherPrefs = mock() 65 private val displayManager: DisplayManager = mock() 66 private val display: Display = mock() 67 private val resources: Resources = mock() 68 private val displayInfoChangeListener: DisplayInfoChangeListener = mock() 69 70 private lateinit var displayController: DisplayController 71 72 private val width = 2208 73 private val height = 1840 74 private val inset = 110 75 private val densityDpi = 420 76 private val density = densityDpi / DisplayMetrics.DENSITY_DEFAULT.toFloat() 77 private val bounds = 78 arrayOf( 79 WindowBounds(Rect(0, 0, width, height), Rect(0, inset, 0, 0), Surface.ROTATION_0), 80 WindowBounds(Rect(0, 0, height, width), Rect(0, inset, 0, 0), Surface.ROTATION_90), 81 WindowBounds(Rect(0, 0, width, height), Rect(0, inset, 0, 0), Surface.ROTATION_180), 82 WindowBounds(Rect(0, 0, height, width), Rect(0, inset, 0, 0), Surface.ROTATION_270) 83 ) 84 private val configuration = 85 Configuration(appContext.resources.configuration).apply { 86 densityDpi = this@DisplayControllerTest.densityDpi 87 screenWidthDp = (bounds[0].bounds.width() / density).toInt() 88 screenHeightDp = (bounds[0].bounds.height() / density).toInt() 89 smallestScreenWidthDp = min(screenWidthDp, screenHeightDp) 90 } 91 92 @Before 93 fun setUp() { 94 whenever(context.getObject(eq(WindowManagerProxy.INSTANCE))).thenReturn(windowManagerProxy) 95 whenever(context.getObject(eq(LauncherPrefs.INSTANCE))).thenReturn(launcherPrefs) 96 whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false) 97 whenever(launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(true) 98 99 // Mock WindowManagerProxy 100 val displayInfo = CachedDisplayInfo(Point(width, height), Surface.ROTATION_0) 101 whenever(windowManagerProxy.getDisplayInfo(any())).thenReturn(displayInfo) 102 whenever(windowManagerProxy.estimateInternalDisplayBounds(any())) 103 .thenAnswer( 104 Answer { 105 // Always create a new copy of bounds 106 val perDisplayBounds = ArrayMap<CachedDisplayInfo, List<WindowBounds>>() 107 perDisplayBounds[displayInfo] = bounds.toList() 108 return@Answer perDisplayBounds 109 } 110 ) 111 whenever(windowManagerProxy.getRealBounds(any(), any())).thenAnswer { i -> 112 bounds[i.getArgument<CachedDisplayInfo>(1).rotation] 113 } 114 115 whenever(windowManagerProxy.getNavigationMode(any())).thenReturn(NavigationMode.NO_BUTTON) 116 // Mock context 117 whenever(context.createWindowContext(any(), any(), anyOrNull())).thenReturn(context) 118 whenever(context.getSystemService(eq(DisplayManager::class.java))) 119 .thenReturn(displayManager) 120 doNothing().whenever(context).registerComponentCallbacks(any()) 121 122 // Mock display 123 whenever(display.rotation).thenReturn(displayInfo.rotation) 124 whenever(context.display).thenReturn(display) 125 whenever(displayManager.getDisplay(any())).thenReturn(display) 126 127 // Mock resources 128 doReturn(context).whenever(context).applicationContext 129 whenever(resources.configuration).thenReturn(configuration) 130 whenever(context.resources).thenReturn(resources) 131 132 // Initialize DisplayController 133 displayController = DisplayController(context) 134 displayController.addChangeListener(displayInfoChangeListener) 135 } 136 137 @Test 138 @UiThreadTest 139 fun testRotation() { 140 val displayInfo = CachedDisplayInfo(Point(height, width), Surface.ROTATION_90) 141 whenever(windowManagerProxy.getDisplayInfo(any())).thenReturn(displayInfo) 142 whenever(display.rotation).thenReturn(displayInfo.rotation) 143 val configuration = 144 Configuration(configuration).apply { 145 screenWidthDp = configuration.screenHeightDp 146 screenHeightDp = configuration.screenWidthDp 147 } 148 whenever(resources.configuration).thenReturn(configuration) 149 150 displayController.onConfigurationChanged(configuration) 151 152 verify(displayInfoChangeListener).onDisplayInfoChanged(any(), any(), eq(CHANGE_ROTATION)) 153 } 154 155 @Test 156 @UiThreadTest 157 fun testFontScale() { 158 val configuration = Configuration(configuration).apply { fontScale = 1.2f } 159 whenever(resources.configuration).thenReturn(configuration) 160 161 displayController.onConfigurationChanged(configuration) 162 163 verify(displayInfoChangeListener).onDisplayInfoChanged(any(), any(), eq(CHANGE_DENSITY)) 164 } 165 166 @Test 167 @UiThreadTest 168 fun testTaskbarPinning() { 169 whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(true) 170 displayController.handleInfoChange(display) 171 verify(displayInfoChangeListener) 172 .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING)) 173 } 174 175 @Test 176 @UiThreadTest 177 fun testTaskbarPinningChangeInDesktopMode() { 178 whenever(launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(false) 179 displayController.handleInfoChange(display) 180 verify(displayInfoChangeListener) 181 .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING)) 182 } 183 } 184