1 /* 2 * Copyright (C) 2022 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 17 package com.android.server.input 18 19 20 import android.content.Context 21 import android.content.ContextWrapper 22 import android.hardware.display.DisplayViewport 23 import android.os.IInputConstants 24 import android.os.test.TestLooper 25 import android.platform.test.annotations.Presubmit 26 import android.provider.Settings 27 import android.test.mock.MockContentResolver 28 import android.view.Display 29 import android.view.PointerIcon 30 import androidx.test.InstrumentationRegistry 31 import com.android.internal.util.test.FakeSettingsProvider 32 import com.google.common.truth.Truth.assertThat 33 import org.junit.Assert.assertFalse 34 import org.junit.Assert.assertTrue 35 import org.junit.Before 36 import org.junit.Rule 37 import org.junit.Test 38 import org.mockito.ArgumentMatchers.any 39 import org.mockito.ArgumentMatchers.anyBoolean 40 import org.mockito.ArgumentMatchers.anyFloat 41 import org.mockito.ArgumentMatchers.anyInt 42 import org.mockito.ArgumentMatchers.eq 43 import org.mockito.Mock 44 import org.mockito.Mockito.`when` 45 import org.mockito.Mockito.clearInvocations 46 import org.mockito.Mockito.doAnswer 47 import org.mockito.Mockito.never 48 import org.mockito.Mockito.spy 49 import org.mockito.Mockito.times 50 import org.mockito.Mockito.verify 51 import org.mockito.Mockito.verifyNoMoreInteractions 52 import org.mockito.Mockito.verifyZeroInteractions 53 import org.mockito.junit.MockitoJUnit 54 import org.mockito.stubbing.OngoingStubbing 55 import java.util.concurrent.CountDownLatch 56 import java.util.concurrent.TimeUnit 57 58 /** 59 * Tests for {@link InputManagerService}. 60 * 61 * Build/Install/Run: 62 * atest FrameworksServicesTests:InputManagerServiceTests 63 */ 64 @Presubmit 65 class InputManagerServiceTests { 66 67 @get:Rule 68 val mockitoRule = MockitoJUnit.rule()!! 69 70 @get:Rule 71 val fakeSettingsProviderRule = FakeSettingsProvider.rule()!! 72 73 @Mock 74 private lateinit var native: NativeInputManagerService 75 76 @Mock 77 private lateinit var wmCallbacks: InputManagerService.WindowManagerCallbacks 78 79 @Mock 80 private lateinit var uEventManager: UEventManager 81 82 private lateinit var service: InputManagerService 83 private lateinit var localService: InputManagerInternal 84 private lateinit var context: Context 85 private lateinit var testLooper: TestLooper 86 private lateinit var contentResolver: MockContentResolver 87 88 @Before 89 fun setup() { 90 context = spy(ContextWrapper(InstrumentationRegistry.getContext())) 91 contentResolver = MockContentResolver(context) 92 contentResolver.addProvider(Settings.AUTHORITY, FakeSettingsProvider()) 93 whenever(context.contentResolver).thenReturn(contentResolver) 94 testLooper = TestLooper() 95 service = 96 InputManagerService(object : InputManagerService.Injector( 97 context, testLooper.looper, uEventManager) { 98 override fun getNativeService( 99 service: InputManagerService? 100 ): NativeInputManagerService { 101 return native 102 } 103 104 override fun registerLocalService(service: InputManagerInternal?) { 105 localService = service!! 106 } 107 }) 108 assertTrue("Local service must be registered", this::localService.isInitialized) 109 service.setWindowManagerCallbacks(wmCallbacks) 110 } 111 112 @Test 113 fun testStart() { 114 verifyZeroInteractions(native) 115 116 service.start() 117 verify(native).start() 118 } 119 120 @Test 121 fun testInputSettingsUpdatedOnSystemRunning() { 122 verifyZeroInteractions(native) 123 124 service.systemRunning() 125 126 verify(native).setPointerSpeed(anyInt()) 127 verify(native).setTouchpadPointerSpeed(anyInt()) 128 verify(native).setTouchpadNaturalScrollingEnabled(anyBoolean()) 129 verify(native).setTouchpadTapToClickEnabled(anyBoolean()) 130 verify(native).setTouchpadRightClickZoneEnabled(anyBoolean()) 131 verify(native).setShowTouches(anyBoolean()) 132 verify(native).reloadPointerIcons() 133 verify(native).notifyKeyGestureTimeoutsChanged() 134 verify(native).setMotionClassifierEnabled(anyBoolean()) 135 verify(native).setMaximumObscuringOpacityForTouch(anyFloat()) 136 verify(native).setStylusPointerIconEnabled(anyBoolean()) 137 } 138 139 @Test 140 fun testPointerDisplayUpdatesWhenDisplayViewportsChanged() { 141 val displayId = 123 142 whenever(wmCallbacks.pointerDisplayId).thenReturn(displayId) 143 val viewports = listOf<DisplayViewport>() 144 localService.setDisplayViewports(viewports) 145 verify(native).setDisplayViewports(any(Array<DisplayViewport>::class.java)) 146 verify(native).setPointerDisplayId(displayId) 147 148 val x = 42f 149 val y = 314f 150 service.onPointerDisplayIdChanged(displayId, x, y) 151 testLooper.dispatchNext() 152 verify(wmCallbacks).notifyPointerDisplayIdChanged(displayId, x, y) 153 } 154 155 @Test 156 fun testSetVirtualMousePointerDisplayId() { 157 // Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked 158 // until the native callback happens. 159 var countDownLatch = CountDownLatch(1) 160 val overrideDisplayId = 123 161 Thread { 162 assertTrue("Setting virtual pointer display should succeed", 163 localService.setVirtualMousePointerDisplayId(overrideDisplayId)) 164 countDownLatch.countDown() 165 }.start() 166 assertFalse("Setting virtual pointer display should block", 167 countDownLatch.await(100, TimeUnit.MILLISECONDS)) 168 169 val x = 42f 170 val y = 314f 171 service.onPointerDisplayIdChanged(overrideDisplayId, x, y) 172 testLooper.dispatchNext() 173 verify(wmCallbacks).notifyPointerDisplayIdChanged(overrideDisplayId, x, y) 174 assertTrue("Native callback unblocks calling thread", 175 countDownLatch.await(100, TimeUnit.MILLISECONDS)) 176 verify(native).setPointerDisplayId(overrideDisplayId) 177 178 // Ensure that setting the same override again succeeds immediately. 179 assertTrue("Setting the same virtual mouse pointer displayId again should succeed", 180 localService.setVirtualMousePointerDisplayId(overrideDisplayId)) 181 182 // Ensure that we did not query WM for the pointerDisplayId when setting the override 183 verify(wmCallbacks, never()).pointerDisplayId 184 185 // Unset the virtual mouse pointer displayId, and ensure that we query WM for the new 186 // pointer displayId and the calling thread is blocked until the native callback happens. 187 countDownLatch = CountDownLatch(1) 188 val pointerDisplayId = 42 189 `when`(wmCallbacks.pointerDisplayId).thenReturn(pointerDisplayId) 190 Thread { 191 assertTrue("Unsetting virtual mouse pointer displayId should succeed", 192 localService.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY)) 193 countDownLatch.countDown() 194 }.start() 195 assertFalse("Unsetting virtual mouse pointer displayId should block", 196 countDownLatch.await(100, TimeUnit.MILLISECONDS)) 197 198 service.onPointerDisplayIdChanged(pointerDisplayId, x, y) 199 testLooper.dispatchNext() 200 verify(wmCallbacks).notifyPointerDisplayIdChanged(pointerDisplayId, x, y) 201 assertTrue("Native callback unblocks calling thread", 202 countDownLatch.await(100, TimeUnit.MILLISECONDS)) 203 verify(native).setPointerDisplayId(pointerDisplayId) 204 } 205 206 @Test 207 fun testSetVirtualMousePointerDisplayId_unsuccessfulUpdate() { 208 // Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked 209 // until the native callback happens. 210 val countDownLatch = CountDownLatch(1) 211 val overrideDisplayId = 123 212 Thread { 213 assertFalse("Setting virtual pointer display should be unsuccessful", 214 localService.setVirtualMousePointerDisplayId(overrideDisplayId)) 215 countDownLatch.countDown() 216 }.start() 217 assertFalse("Setting virtual pointer display should block", 218 countDownLatch.await(100, TimeUnit.MILLISECONDS)) 219 220 val x = 42f 221 val y = 314f 222 // Assume the native callback updates the pointerDisplayId to the incorrect value. 223 service.onPointerDisplayIdChanged(Display.INVALID_DISPLAY, x, y) 224 testLooper.dispatchNext() 225 verify(wmCallbacks).notifyPointerDisplayIdChanged(Display.INVALID_DISPLAY, x, y) 226 assertTrue("Native callback unblocks calling thread", 227 countDownLatch.await(100, TimeUnit.MILLISECONDS)) 228 verify(native).setPointerDisplayId(overrideDisplayId) 229 } 230 231 @Test 232 fun testSetVirtualMousePointerDisplayId_competingRequests() { 233 val firstRequestSyncLatch = CountDownLatch(1) 234 doAnswer { 235 firstRequestSyncLatch.countDown() 236 }.`when`(native).setPointerDisplayId(anyInt()) 237 238 val firstRequestLatch = CountDownLatch(1) 239 val firstOverride = 123 240 Thread { 241 assertFalse("Setting virtual pointer display from thread 1 should be unsuccessful", 242 localService.setVirtualMousePointerDisplayId(firstOverride)) 243 firstRequestLatch.countDown() 244 }.start() 245 assertFalse("Setting virtual pointer display should block", 246 firstRequestLatch.await(100, TimeUnit.MILLISECONDS)) 247 248 assertTrue("Wait for first thread's request should succeed", 249 firstRequestSyncLatch.await(100, TimeUnit.MILLISECONDS)) 250 251 val secondRequestLatch = CountDownLatch(1) 252 val secondOverride = 42 253 Thread { 254 assertTrue("Setting virtual mouse pointer from thread 2 should be successful", 255 localService.setVirtualMousePointerDisplayId(secondOverride)) 256 secondRequestLatch.countDown() 257 }.start() 258 assertFalse("Setting virtual mouse pointer should block", 259 secondRequestLatch.await(100, TimeUnit.MILLISECONDS)) 260 261 val x = 42f 262 val y = 314f 263 // Assume the native callback updates directly to the second request. 264 service.onPointerDisplayIdChanged(secondOverride, x, y) 265 testLooper.dispatchNext() 266 verify(wmCallbacks).notifyPointerDisplayIdChanged(secondOverride, x, y) 267 assertTrue("Native callback unblocks first thread", 268 firstRequestLatch.await(100, TimeUnit.MILLISECONDS)) 269 assertTrue("Native callback unblocks second thread", 270 secondRequestLatch.await(100, TimeUnit.MILLISECONDS)) 271 verify(native, times(2)).setPointerDisplayId(anyInt()) 272 } 273 274 @Test 275 fun onDisplayRemoved_resetAllAdditionalInputProperties() { 276 setVirtualMousePointerDisplayIdAndVerify(10) 277 278 localService.setPointerIconVisible(false, 10) 279 verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL)) 280 localService.setPointerAcceleration(5f, 10) 281 verify(native).setPointerAcceleration(eq(5f)) 282 283 service.onDisplayRemoved(10) 284 verify(native).displayRemoved(eq(10)) 285 verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED)) 286 verify(native).setPointerAcceleration( 287 eq(IInputConstants.DEFAULT_POINTER_ACCELERATION.toFloat())) 288 verifyNoMoreInteractions(native) 289 290 // This call should not block because the virtual mouse pointer override was never removed. 291 localService.setVirtualMousePointerDisplayId(10) 292 293 verify(native).setPointerDisplayId(eq(10)) 294 verifyNoMoreInteractions(native) 295 } 296 297 @Test 298 fun updateAdditionalInputPropertiesForOverrideDisplay() { 299 setVirtualMousePointerDisplayIdAndVerify(10) 300 301 localService.setPointerIconVisible(false, 10) 302 verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL)) 303 localService.setPointerAcceleration(5f, 10) 304 verify(native).setPointerAcceleration(eq(5f)) 305 306 localService.setPointerIconVisible(true, 10) 307 verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED)) 308 localService.setPointerAcceleration(1f, 10) 309 verify(native).setPointerAcceleration(eq(1f)) 310 311 // Verify that setting properties on a different display is not propagated until the 312 // pointer is moved to that display. 313 localService.setPointerIconVisible(false, 20) 314 localService.setPointerAcceleration(6f, 20) 315 verifyNoMoreInteractions(native) 316 317 clearInvocations(native) 318 setVirtualMousePointerDisplayIdAndVerify(20) 319 320 verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL)) 321 verify(native).setPointerAcceleration(eq(6f)) 322 } 323 324 @Test 325 fun setAdditionalInputPropertiesBeforeOverride() { 326 localService.setPointerIconVisible(false, 10) 327 localService.setPointerAcceleration(5f, 10) 328 329 verifyNoMoreInteractions(native) 330 331 setVirtualMousePointerDisplayIdAndVerify(10) 332 333 verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL)) 334 verify(native).setPointerAcceleration(eq(5f)) 335 } 336 337 @Test 338 fun setDeviceTypeAssociation_setsDeviceTypeAssociation() { 339 val inputPort = "inputPort" 340 val type = "type" 341 342 localService.setTypeAssociation(inputPort, type) 343 344 assertThat(service.getDeviceTypeAssociations()).asList().containsExactly(inputPort, type) 345 .inOrder() 346 } 347 348 @Test 349 fun setAndUnsetDeviceTypeAssociation_deviceTypeAssociationIsMissing() { 350 val inputPort = "inputPort" 351 val type = "type" 352 353 localService.setTypeAssociation(inputPort, type) 354 localService.unsetTypeAssociation(inputPort) 355 356 assertTrue(service.getDeviceTypeAssociations().isEmpty()) 357 } 358 359 @Test 360 fun testAddAndRemoveVirtualmKeyboardLayoutAssociation() { 361 val inputPort = "input port" 362 val languageTag = "language" 363 val layoutType = "layoutType" 364 localService.addKeyboardLayoutAssociation(inputPort, languageTag, layoutType) 365 verify(native).changeKeyboardLayoutAssociation() 366 367 localService.removeKeyboardLayoutAssociation(inputPort) 368 verify(native, times(2)).changeKeyboardLayoutAssociation() 369 } 370 371 private fun setVirtualMousePointerDisplayIdAndVerify(overrideDisplayId: Int) { 372 val thread = Thread { localService.setVirtualMousePointerDisplayId(overrideDisplayId) } 373 thread.start() 374 375 // Allow some time for the set override call to park while waiting for the native callback. 376 Thread.sleep(100 /*millis*/) 377 verify(native).setPointerDisplayId(overrideDisplayId) 378 379 service.onPointerDisplayIdChanged(overrideDisplayId, 0f, 0f) 380 testLooper.dispatchNext() 381 verify(wmCallbacks).notifyPointerDisplayIdChanged(overrideDisplayId, 0f, 0f) 382 thread.join(100 /*millis*/) 383 } 384 } 385 386 private fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall) 387