/* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.eyedropper.util import android.graphics.PointF import android.hardware.display.DisplayManager import android.hardware.input.InputManager import android.os.Handler import android.view.InputDevice import androidx.compose.ui.geometry.Offset import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.eyedropper.di.MainThreadHandlerModule import com.android.eyedropper.di.SystemServiceModule import com.google.common.truth.Truth.assertThat import dagger.hilt.android.testing.BindValue import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.UninstallModules import javax.inject.Inject import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.stub import org.mockito.kotlin.verify /** * Test class for [InputHelper] * * Usage: atest EyeDropperUnitTests:InputHelperTest */ @UninstallModules(SystemServiceModule::class, MainThreadHandlerModule::class) @HiltAndroidTest @RunWith(AndroidJUnit4::class) class InputHelperTest { @get:Rule val hiltRule = HiltAndroidRule(this) private val mockMouse1 = createMockDevice(MOUSE1_ID, isMouse = true) private val mockKeyboard1 = createMockDevice(KEYBOARD1_ID, isFullKeyboard = true) private val mockVirtualMouse = createMockDevice(VIRTUAL_MOUSE_ID, isMouse = true, isVirtual = true) private val mockOtherDevice = createMockDevice(OTHER_DEVICE_ID) private val mockDisplayIds = setOf(DISPLAY1_ID, DISPLAY2_ID) @BindValue val mockInputManager = mock() @BindValue val mockWindowHelper = mock() @BindValue val mockScreenCaptureHelper = mock() @BindValue val mainThreadHandler = mock() @BindValue val mockDisplayManager = mock() @Inject lateinit var inputHelper: InputHelper @Before fun setup() { hiltRule.inject() } // (1) Test highest level behavior -> desktop mode every scenario @Test fun isDesktopMode_validMouseAndKeyboard_returnsTrue() { setupInputDevices(intArrayOf(MOUSE1_ID, KEYBOARD1_ID)) setupDeviceMock(MOUSE1_ID, mockMouse1) setupDeviceMock(KEYBOARD1_ID, mockKeyboard1) assertThat(inputHelper.isDesktopMode()).isTrue() } @Test fun isDesktopMode_noDevices_returnsFalse() { setupInputDevices(intArrayOf()) assertThat(inputHelper.isDesktopMode()).isFalse() } @Test fun isDesktopMode_mouseOnly_returnsFalse() { setupInputDevices(intArrayOf(MOUSE1_ID)) setupDeviceMock(MOUSE1_ID, mockMouse1) assertThat(inputHelper.isDesktopMode()).isFalse() } @Test fun isDesktopMode_keyboardOnly_returnsFalse() { setupInputDevices(intArrayOf(KEYBOARD1_ID)) setupDeviceMock(KEYBOARD1_ID, mockKeyboard1) assertThat(inputHelper.isDesktopMode()).isFalse() } // (2) Spot test low-level functions @Test fun isFullKeyboard_deviceNotFound_returnsFalse() { setupDeviceMock(KEYBOARD1_ID, null) assertThat(inputHelper.isFullKeyboard(KEYBOARD1_ID)).isFalse() } @Test fun isMouse_deviceNotFound_returnsFalse() { setupDeviceMock(MOUSE1_ID, null) assertThat(inputHelper.isMouse(MOUSE1_ID)).isFalse() } @Test fun isMouse_notEnabled_returnsFalse() { val mockDevice = createMockDevice(MOUSE1_ID, isMouse = true, isEnabled = false) setupDeviceMock(MOUSE1_ID, mockDevice) assertThat(inputHelper.isMouse(MOUSE1_ID)).isFalse() } @Test fun hasMouseConnected_onlyVirtualMouse_returnsFalse() { setupInputDevices(intArrayOf(VIRTUAL_MOUSE_ID)) setupDeviceMock(VIRTUAL_MOUSE_ID, mockVirtualMouse) assertThat(inputHelper.hasMouseConnected()).isFalse() } @Test fun hasMouseConnected_otherDevice_returnsFalse() { setupInputDevices(intArrayOf(OTHER_DEVICE_ID)) setupDeviceMock(OTHER_DEVICE_ID, mockOtherDevice) assertThat(inputHelper.hasMouseConnected()).isFalse() } // (3) Test listener registration @Test fun registerInputDeviceListener_registersWithInputManager() { val listener = mock() inputHelper.registerInputDeviceListener(listener) verify(mockInputManager).registerInputDeviceListener(listener, mainThreadHandler) } @Test fun unregisterInputDeviceListener_unregistersWithInputManager() { val listener = mock() inputHelper.unregisterInputDeviceListener(listener) verify(mockInputManager).unregisterInputDeviceListener(listener) } // (4) Get cursor position @Test fun getCursorPosition_onSingleDisplay_findsCursor() { val singleDisplay = setOf(DISPLAY1_ID) val expectedPos = PointF(10f, 10f) val expectedRes = Pair(DISPLAY1_ID, Offset(10f, 10f)) mockInputManager.stub { on { getCursorPosition(DISPLAY1_ID) } doReturn expectedPos } val actualPos = inputHelper.getCursorPosition(singleDisplay) assertThat(expectedRes ).isEqualTo(actualPos) verify(mockInputManager).getCursorPosition(DISPLAY1_ID) } @Test fun getCursorPosition_onMultipleDisplays_findsCursor() { val expectedPos = PointF(10f, 10f) val expectedRes = Pair(DISPLAY2_ID, Offset(10f, 10f)) mockInputManager.stub { on { getCursorPosition(DISPLAY1_ID) } doReturn null on { getCursorPosition(DISPLAY2_ID )} doReturn expectedPos } val actualPos = inputHelper.getCursorPosition(mockDisplayIds) assertThat(expectedRes).isEqualTo(actualPos) verify(mockInputManager).getCursorPosition(DISPLAY2_ID) } @Test fun getCursorPosition_noCursorFound_returnsNull() { mockInputManager.stub { on { getCursorPosition(any()) } doReturn null } val actualPos = inputHelper.getCursorPosition(mockDisplayIds) assertThat(actualPos).isNull() verify(mockInputManager).getCursorPosition(DISPLAY1_ID) verify(mockInputManager).getCursorPosition(DISPLAY2_ID) } private fun createMockDevice( id: Int, isEnabled: Boolean = true, isVirtual: Boolean = false, isFullKeyboard: Boolean = false, isMouse: Boolean = false ) = mock { on { this.id } doReturn id on { this.isEnabled } doReturn isEnabled on { this.isVirtual } doReturn isVirtual on { this.isFullKeyboard } doReturn isFullKeyboard on { supportsSource(InputDevice.SOURCE_MOUSE) } doReturn isMouse } private fun setupDeviceMock(deviceId: Int, device: InputDevice?) { mockInputManager.stub { on { getInputDevice(deviceId) } doReturn device } } private fun setupInputDevices(ids: IntArray) { mockInputManager.stub { on { inputDeviceIds } doReturn ids } } companion object { private const val MOUSE1_ID = 1 private const val KEYBOARD1_ID = 2 private const val VIRTUAL_MOUSE_ID = 3 private const val OTHER_DEVICE_ID = 4 private const val DISPLAY1_ID = 5 private const val DISPLAY2_ID = 6 } }