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