Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 41a612e2 authored by Gavin Williams's avatar Gavin Williams Committed by Android (Google) Code Review
Browse files

Merge "Autoclick Tests: Create click type tests" into main

parents 36e5b324 b93ee3fe
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -144,6 +144,7 @@
                android:resource="@xml/test_magnification_a11y_service" />
        </service>
        <activity android:name="com.android.server.accessibility.integration.FullScreenMagnificationMouseFollowingTest$TestActivity" />
        <activity android:name="com.android.server.accessibility.integration.AutoclickClickTypeTests$TestClickActivity" />

        <service android:name="com.android.server.accounts.TestAccountType1AuthenticatorService"
             android:exported="false">
+281 −0
Original line number Diff line number Diff line
/*
 * 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.server.accessibility.integration

import android.app.Activity
import android.app.Instrumentation
import android.app.UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES
import android.os.Bundle
import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.flag.junit.CheckFlagsRule
import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.provider.Settings
import android.view.Display.DEFAULT_DISPLAY
import android.view.GestureDetector
import android.view.InputDevice
import android.view.MotionEvent
import android.view.View
import android.widget.Button
import android.widget.LinearLayout
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.Configurator
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
import com.android.compatibility.common.util.PollingCheck
import com.android.compatibility.common.util.PollingCheck.waitFor
import com.android.compatibility.common.util.SettingsStateChangerRule
import com.android.server.accessibility.Flags
import kotlin.time.Duration.Companion.seconds
import org.junit.AfterClass
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import platform.test.desktop.DesktopMouseTestRule

@RunWith(JUnit4::class)
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
class AutoclickClickTypeTests {
    @Rule(order = 0)
    @JvmField
    val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()

    @Rule(order = 1)
    @JvmField
    val autoclickEnabledSettingRule: SettingsStateChangerRule =
        SettingsStateChangerRule(
            InstrumentationRegistry.getInstrumentation().context,
            Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
            "1"
        )

    @Rule(order = 2)
    @JvmField
    val desktopMouseTestRule = DesktopMouseTestRule()

    @Rule
    @JvmField
    val activityScenarioRule: ActivityScenarioRule<TestClickActivity> =
        ActivityScenarioRule(TestClickActivity::class.java)

    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()

    private lateinit var uiDevice: UiDevice
    private lateinit var testClickButton: Button

    @Before
    fun setup() {
        Configurator.getInstance().setUiAutomationFlags(FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES)
        uiDevice = UiDevice.getInstance(instrumentation)

        activityScenarioRule.scenario.onActivity { activity ->
            testClickButton = activity.findViewById(TEST_BUTTON_ID)
        }
    }

    private fun findObject(selector: BySelector): UiObject2 {
        return uiDevice.wait(Until.findObject(selector), FIND_OBJECT_TIMEOUT.inWholeMilliseconds)
    }

    private fun moveMouseToView(view: View) {
        val xOnScreen = view.locationOnScreen[0]
        val yOnScreen = view.locationOnScreen[1]
        val centerX = xOnScreen + (view.width / 2)
        val centerY = yOnScreen + (view.height / 2)
        desktopMouseTestRule.move(DEFAULT_DISPLAY, centerX, centerY)
    }

    // The panel is considered open when more than one click type button is visible.
    private fun isAutoclickPanelOpen(): Boolean {
        val clickTypeButtonGroupContainer = findObject(
            By.res(CLICK_TYPE_BUTTON_GROUP_ID)
        )
        return clickTypeButtonGroupContainer.childCount > 1
    }

    private fun changeClickType(clickTypeResourceId: String) {
        // First move the cursor to the edge of the screen so the next move will trigger an
        // autoclick.
        desktopMouseTestRule.move(DEFAULT_DISPLAY, targetXPx = 0, targetYPx = 0)

        // The click type button group starts closed so click it to open the panel.
        val clickTypeButtonGroup = findObject(
            By.res(CLICK_TYPE_BUTTON_GROUP_ID)
        )
        desktopMouseTestRule.move(
            DEFAULT_DISPLAY, clickTypeButtonGroup.visibleCenter.x, clickTypeButtonGroup.visibleCenter.y
        )

        // Wait for the panel to fully open before attempting to select a click type.
        waitAndAssert {
            isAutoclickPanelOpen()
        }

        desktopMouseTestRule.move(DEFAULT_DISPLAY, targetXPx = 0, targetYPx = 0)
        val targetClickTypeButton = findObject(By.res(clickTypeResourceId))
        desktopMouseTestRule.move(
            DEFAULT_DISPLAY,
            targetClickTypeButton.visibleCenter.x,
            targetClickTypeButton.visibleCenter.y
        )

        // Wait for the panel to close as the signal that the click type was selected.
        waitAndAssert {
            !isAutoclickPanelOpen()
        }
    }

    private fun waitAndAssert(condition: PollingCheck.PollingCheckCondition) {
        waitFor(FIND_OBJECT_TIMEOUT.inWholeMilliseconds, condition)
    }

    @Test
    fun performLeftClick_buttonReflectsClickType() {
        changeClickType(LEFT_CLICK_BUTTON_LAYOUT_ID)
        moveMouseToView(testClickButton)

        waitAndAssert {
            testClickButton.text == LEFT_CLICK_TEXT
        }
    }

    @Test
    fun performDoubleClick_buttonReflectsClickType() {
        changeClickType(DOUBLE_CLICK_BUTTON_LAYOUT_ID)
        moveMouseToView(testClickButton)

        waitAndAssert {
            testClickButton.text == DOUBLE_CLICK_TEXT
        }
    }

    @Test
    fun performRightClick_buttonReflectsClickType() {
        changeClickType(RIGHT_CLICK_BUTTON_LAYOUT_ID)
        moveMouseToView(testClickButton)

        waitAndAssert {
            testClickButton.text == RIGHT_CLICK_TEXT
        }
    }

    @Test
    fun performLongPress_buttonReflectsClickType() {
        changeClickType(LONG_PRESS_BUTTON_LAYOUT_ID)
        moveMouseToView(testClickButton)

        waitAndAssert {
            testClickButton.text == LONG_PRESS_TEXT
        }
    }

    // Test activity responsible for receiving clicks and updating its UI depending on the click
    // type.
    class TestClickActivity : Activity() {
        private lateinit var gestureDetector: GestureDetector

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            val contentLayout = LinearLayout(this)
            contentLayout.setOrientation(LinearLayout.VERTICAL)

            val testButton = Button(this)
            testButton.id = TEST_BUTTON_ID

            gestureDetector =
                GestureDetector(this, object : GestureDetector.SimpleOnGestureListener() {
                    override fun onDoubleTap(e: MotionEvent): Boolean {
                        testButton.text = DOUBLE_CLICK_TEXT
                        return true
                    }
                    override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
                        testButton.text = LEFT_CLICK_TEXT
                        return true
                    }

                    override fun onLongPress(e: MotionEvent) {
                        testButton.text = LONG_PRESS_TEXT
                    }
                })
            testButton.setOnTouchListener { _, event ->
                gestureDetector.onTouchEvent(event)
            }

            // Right click listener.
            val genericMotionListener = View.OnGenericMotionListener { _, motionEvent ->
                if (motionEvent.isFromSource(InputDevice.SOURCE_MOUSE)
                    && motionEvent.action == MotionEvent.ACTION_BUTTON_PRESS
                    && motionEvent.actionButton == MotionEvent.BUTTON_SECONDARY
                ) {
                    testButton.text = RIGHT_CLICK_TEXT
                    true
                }
                false
            }
            testButton.setOnGenericMotionListener(genericMotionListener)

            contentLayout.addView(testButton)
            setContentView(contentLayout)
        }
    }

    private companion object {
        private val FIND_OBJECT_TIMEOUT = 5.seconds
        private val TEST_BUTTON_ID = View.generateViewId()

        // Button text.
        private val LEFT_CLICK_TEXT = "Left Clicked!"
        private val DOUBLE_CLICK_TEXT = "Double Clicked!"
        private val RIGHT_CLICK_TEXT = "Right Clicked!"
        private val LONG_PRESS_TEXT = "Long Press Clicked!"

        // Autoclick panel resource ids.
        private val LEFT_CLICK_BUTTON_LAYOUT_ID =
            "android:id/accessibility_autoclick_left_click_layout"
        private val LONG_PRESS_BUTTON_LAYOUT_ID =
            "android:id/accessibility_autoclick_long_press_layout"
        private val RIGHT_CLICK_BUTTON_LAYOUT_ID =
            "android:id/accessibility_autoclick_right_click_layout"
        private val DOUBLE_CLICK_BUTTON_LAYOUT_ID =
            "android:id/accessibility_autoclick_double_click_layout"
        private val CLICK_TYPE_BUTTON_GROUP_ID =
            "android:id/accessibility_autoclick_click_type_button_group_container"

        @BeforeClass
        @JvmStatic
        fun setupBeforeClass() {
            // Disables showing an SDK version dialog to prevent it from interfering with the test.
            InstrumentationRegistry.getInstrumentation().uiAutomation
                .executeShellCommand("setprop debug.wm.disable_deprecated_target_sdk_dialog 1")
                .close()
        }

        @AfterClass
        @JvmStatic
        fun teardownAfterClass() {
            // Re-enable SDK version dialog.
            InstrumentationRegistry.getInstrumentation().uiAutomation
                .executeShellCommand("setprop debug.wm.disable_deprecated_target_sdk_dialog 0")
                .close()
        }
    }
}