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