1 /*
2  * Copyright (C) 2024 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.systemui.touchpad.data.repository
18 
19 import android.hardware.input.FakeInputManager
20 import android.hardware.input.InputManager.InputDeviceListener
21 import android.hardware.input.fakeInputManager
22 import android.testing.TestableLooper
23 import android.view.InputDevice
24 import androidx.test.ext.junit.runners.AndroidJUnit4
25 import androidx.test.filters.SmallTest
26 import com.android.systemui.SysuiTestCase
27 import com.android.systemui.coroutines.collectLastValue
28 import com.android.systemui.inputdevice.data.repository.InputDeviceRepository
29 import com.android.systemui.testKosmos
30 import com.android.systemui.utils.os.FakeHandler
31 import com.google.common.truth.Truth.assertThat
32 import kotlinx.coroutines.CoroutineDispatcher
33 import kotlinx.coroutines.flow.first
34 import kotlinx.coroutines.test.StandardTestDispatcher
35 import kotlinx.coroutines.test.TestScope
36 import kotlinx.coroutines.test.runTest
37 import org.junit.Before
38 import org.junit.Test
39 import org.junit.runner.RunWith
40 import org.mockito.ArgumentCaptor
41 import org.mockito.ArgumentMatchers.eq
42 import org.mockito.Captor
43 import org.mockito.Mockito
44 import org.mockito.MockitoAnnotations
45 import org.mockito.kotlin.anyOrNull
46 import org.mockito.kotlin.whenever
47 
48 @SmallTest
49 @TestableLooper.RunWithLooper
50 @RunWith(AndroidJUnit4::class)
51 class TouchpadRepositoryTest : SysuiTestCase() {
52 
53     @Captor private lateinit var deviceListenerCaptor: ArgumentCaptor<InputDeviceListener>
54     private lateinit var fakeInputManager: FakeInputManager
55 
56     private lateinit var underTest: TouchpadRepository
57     private lateinit var dispatcher: CoroutineDispatcher
58     private lateinit var inputDeviceRepo: InputDeviceRepository
59     private lateinit var testScope: TestScope
60 
61     @Before
setUpnull62     fun setUp() {
63         MockitoAnnotations.initMocks(this)
64         fakeInputManager = testKosmos().fakeInputManager
65         dispatcher = StandardTestDispatcher()
66         testScope = TestScope(dispatcher)
67         val handler = FakeHandler(TestableLooper.get(this).looper)
68         inputDeviceRepo =
69             InputDeviceRepository(handler, testScope.backgroundScope, fakeInputManager.inputManager)
70         underTest =
71             TouchpadRepositoryImpl(dispatcher, fakeInputManager.inputManager, inputDeviceRepo)
72     }
73 
74     @Test
emitsDisconnected_ifNothingIsConnectednull75     fun emitsDisconnected_ifNothingIsConnected() =
76         testScope.runTest {
77             val initialState = underTest.isAnyTouchpadConnected.first()
78             assertThat(initialState).isFalse()
79         }
80 
81     @Test
emitsConnected_ifTouchpadAlreadyConnectedAtTheStartnull82     fun emitsConnected_ifTouchpadAlreadyConnectedAtTheStart() =
83         testScope.runTest {
84             fakeInputManager.addDevice(TOUCHPAD_ID, TOUCHPAD)
85             val initialValue = underTest.isAnyTouchpadConnected.first()
86             assertThat(initialValue).isTrue()
87         }
88 
89     @Test
emitsConnected_whenNewTouchpadConnectsnull90     fun emitsConnected_whenNewTouchpadConnects() =
91         testScope.runTest {
92             captureDeviceListener()
93             val isTouchpadConnected by collectLastValue(underTest.isAnyTouchpadConnected)
94 
95             fakeInputManager.addDevice(TOUCHPAD_ID, TOUCHPAD)
96 
97             assertThat(isTouchpadConnected).isTrue()
98         }
99 
100     @Test
emitsDisconnected_whenDeviceWithIdDoesNotExistnull101     fun emitsDisconnected_whenDeviceWithIdDoesNotExist() =
102         testScope.runTest {
103             captureDeviceListener()
104             val isTouchpadConnected by collectLastValue(underTest.isAnyTouchpadConnected)
105             whenever(fakeInputManager.inputManager.getInputDevice(eq(NULL_DEVICE_ID)))
106                 .thenReturn(null)
107             fakeInputManager.addDevice(NULL_DEVICE_ID, InputDevice.SOURCE_UNKNOWN)
108             assertThat(isTouchpadConnected).isFalse()
109         }
110 
111     @Test
emitsDisconnected_whenTouchpadDisconnectsnull112     fun emitsDisconnected_whenTouchpadDisconnects() =
113         testScope.runTest {
114             captureDeviceListener()
115             val isTouchpadConnected by collectLastValue(underTest.isAnyTouchpadConnected)
116 
117             fakeInputManager.addDevice(TOUCHPAD_ID, TOUCHPAD)
118             assertThat(isTouchpadConnected).isTrue()
119 
120             fakeInputManager.removeDevice(TOUCHPAD_ID)
121             assertThat(isTouchpadConnected).isFalse()
122         }
123 
captureDeviceListenernull124     private suspend fun captureDeviceListener() {
125         underTest.isAnyTouchpadConnected.first()
126         Mockito.verify(fakeInputManager.inputManager)
127             .registerInputDeviceListener(deviceListenerCaptor.capture(), anyOrNull())
128         fakeInputManager.registerInputDeviceListener(deviceListenerCaptor.value)
129     }
130 
131     @Test
emitsDisconnected_whenNonTouchpadConnectsnull132     fun emitsDisconnected_whenNonTouchpadConnects() =
133         testScope.runTest {
134             captureDeviceListener()
135             val isTouchpadConnected by collectLastValue(underTest.isAnyTouchpadConnected)
136 
137             fakeInputManager.addDevice(NON_TOUCHPAD_ID, InputDevice.SOURCE_KEYBOARD)
138             assertThat(isTouchpadConnected).isFalse()
139         }
140 
141     @Test
emitsDisconnected_whenTouchpadDisconnectsAndWasAlreadyConnectedAtTheStartnull142     fun emitsDisconnected_whenTouchpadDisconnectsAndWasAlreadyConnectedAtTheStart() =
143         testScope.runTest {
144             captureDeviceListener()
145             val isTouchpadConnected by collectLastValue(underTest.isAnyTouchpadConnected)
146 
147             fakeInputManager.removeDevice(TOUCHPAD_ID)
148             assertThat(isTouchpadConnected).isFalse()
149         }
150 
151     @Test
emitsConnected_whenAnotherDeviceDisconnectsnull152     fun emitsConnected_whenAnotherDeviceDisconnects() =
153         testScope.runTest {
154             captureDeviceListener()
155             val isTouchpadConnected by collectLastValue(underTest.isAnyTouchpadConnected)
156 
157             fakeInputManager.addDevice(TOUCHPAD_ID, TOUCHPAD)
158             fakeInputManager.removeDevice(NON_TOUCHPAD_ID)
159 
160             assertThat(isTouchpadConnected).isTrue()
161         }
162 
163     @Test
emitsConnected_whenOneTouchpadDisconnectsButAnotherRemainsConnectednull164     fun emitsConnected_whenOneTouchpadDisconnectsButAnotherRemainsConnected() =
165         testScope.runTest {
166             captureDeviceListener()
167             val isTouchpadConnected by collectLastValue(underTest.isAnyTouchpadConnected)
168 
169             fakeInputManager.addDevice(TOUCHPAD_ID, TOUCHPAD)
170             fakeInputManager.addDevice(ANOTHER_TOUCHPAD_ID, TOUCHPAD)
171             fakeInputManager.removeDevice(TOUCHPAD_ID)
172 
173             assertThat(isTouchpadConnected).isTrue()
174         }
175 
176     private companion object {
177         private const val TOUCHPAD_ID = 1
178         private const val NON_TOUCHPAD_ID = 2
179         private const val ANOTHER_TOUCHPAD_ID = 3
180         private const val NULL_DEVICE_ID = 4
181 
182         private const val TOUCHPAD = InputDevice.SOURCE_TOUCHPAD
183     }
184 }
185