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

Commit a770e5d7 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Prevent handling touchpad gesture on desktop window decorations" into main

parents 4b69aca6 1d58d5a3
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -156,6 +156,7 @@ import com.android.wm.shell.windowdecor.common.WindowDecorationGestureExclusionT
import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import com.android.wm.shell.windowdecor.extension.InsetsStateKt;
import com.android.wm.shell.windowdecor.extension.MotionEventKt;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel;
import com.android.wm.shell.windowdecor.tiling.SnapEventHandler;
@@ -1197,6 +1198,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
                    && id != R.id.maximize_window && id != R.id.minimize_window) {
                return false;
            }
            if (MotionEventKt.isTouchpadGesture(e)) {
                // Touchpad finger gestures are ignored.
                return false;
            }

            final boolean isAppHandle = !taskInfo.isFreeform();
            final int actionMasked = e.getActionMasked();
            final boolean isDown = actionMasked == MotionEvent.ACTION_DOWN;
+7 −0
Original line number Diff line number Diff line
@@ -34,6 +34,8 @@ import android.view.View;

import androidx.annotation.Nullable;

import com.android.wm.shell.windowdecor.extension.MotionEventKt;

/**
 * A detector for touch inputs that differentiates between drag and click inputs. It receives a flow
 * of {@link MotionEvent} and generates a new flow of motion events with slop in consideration to
@@ -90,6 +92,11 @@ public class DragDetector {
     * {@link #mEventHandler} handles the previous down event if the event shouldn't be passed
     */
    public boolean onMotionEvent(View v, MotionEvent ev) {
        if (MotionEventKt.isTouchpadGesture(ev)) {
            // Touchpad finger gestures are ignored.
            return false;
        }

        final boolean isTouchScreen =
                (ev.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
        if (!isTouchScreen) {
+32 −0
Original line number Diff line number Diff line
/*
 * Copyright 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.wm.shell.windowdecor.extension

import android.view.InputDevice
import android.view.MotionEvent

/**
 * Checks if the [MotionEvent] is a part of a synthesized touchpad gesture.
 *
 * @return true if the [MotionEvent] is a touchpad gesture, false otherwise.
 */
fun MotionEvent.isTouchpadGesture(): Boolean =
    source == InputDevice.SOURCE_MOUSE
        && getToolType(0) == MotionEvent.TOOL_TYPE_FINGER
        && (classification == MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE
        || classification == MotionEvent.CLASSIFICATION_PINCH
        || classification == MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE)
+86 −0
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.Display.DEFAULT_DISPLAY
import android.view.ISystemGestureExclusionListener
import android.view.InputDevice
import android.view.InsetsSource
import android.view.InsetsState
import android.view.KeyEvent
@@ -67,6 +68,7 @@ import com.android.wm.shell.desktopmode.DesktopImmersiveController
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
@@ -1085,6 +1087,54 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
        assertThat(hierarchyOp.container).isEqualTo(decor.mTaskInfo.token.asBinder())
    }

    @Test
    fun testOnTouchWithClassification_doesNothing() {
        val onClickListenerCaptor = argumentCaptor<View.OnClickListener>()
        val onTouchListenerCaptor = argumentCaptor<View.OnTouchListener>()
        val decor = createOpenTaskDecoration(
            windowingMode = WINDOWING_MODE_FREEFORM,
            onCaptionButtonClickListener = onClickListenerCaptor,
            onCaptionButtonTouchListener = onTouchListenerCaptor,
        )

        val view = mock<View> {
            on { id } doReturn R.id.desktop_mode_caption
        }

        val onTouchListener = onTouchListenerCaptor.firstValue
        assertFalse(
            onTouchListener.onTouch(
                view,
                createMotionEvent(
                    MotionEvent.ACTION_DOWN,
                    x = 0f,
                    y = 0f,
                    source = InputDevice.SOURCE_MOUSE,
                    classification = MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE
                )
            )
        )

        verify(mockDesktopTasksController, never()).moveTaskToFront(
            anyOrNull<RunningTaskInfo>(),
            anyOrNull(),
            anyOrNull<UnminimizeReason>(),
        )

        assertFalse(
            onTouchListener.onTouch(
                view,
                createMotionEvent(
                    MotionEvent.ACTION_UP,
                    x = 0f,
                    y = 0f,
                    source = InputDevice.SOURCE_MOUSE,
                    classification = MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE
                )
            )
        )
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
    fun testImmersiveRestoreButtonClick_exitsImmersiveMode() {
@@ -1628,6 +1678,42 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
        )
    }

    private fun createMotionEvent(
        action: Int,
        x: Float = 0f,
        y: Float = 0f,
        source: Int = InputDevice.SOURCE_TOUCHSCREEN,
        classification: Int = MotionEvent.CLASSIFICATION_NONE,
    ): MotionEvent {
        val pointerProperties = arrayOf(MotionEvent.PointerProperties().apply {
            this.id = 0
            this.toolType = MotionEvent.TOOL_TYPE_FINGER
        })
        val pointerCoords = arrayOf(MotionEvent.PointerCoords().apply {
            this.x = x
            this.y = y
        })
        val ev = MotionEvent.obtain(
            /* downTime= */ SystemClock.uptimeMillis(),
            /* eventTime= */ SystemClock.uptimeMillis(),
            action,
            /* pointerCount= */ 1,
            pointerProperties,
            pointerCoords,
            /* metaState= */ 0,
            /* buttonState= */ 0,
            /* xPrecision= */ 0f,
            /* yPrecision= */ 0f,
            /* deviceId= */ 0,
            /* edgeFlags= */ 0,
            source,
            /* displayId= */ 0,
            /* flags= */ 0,
            classification,
        )!!
        return ev
    }

    private companion object {
        const val SECOND_DISPLAY = 2
        private val BOUNDS_AFTER_FIRST_MOVE = Rect(10, 10, 110, 110)
+57 −6
Original line number Diff line number Diff line
@@ -18,8 +18,8 @@ package com.android.wm.shell.windowdecor

import android.os.SystemClock
import android.testing.AndroidTestingRunner
import android.view.MotionEvent
import android.view.InputDevice
import android.view.MotionEvent
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
import org.junit.After
@@ -29,12 +29,12 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when`
import org.mockito.Mockito.any
import org.mockito.Mockito.argThat
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.times

/**
@@ -467,16 +467,67 @@ class DragDetectorTest : ShellTestCase() {
            .handleMotionEvent(any(), argThat { ev -> ev.action == MotionEvent.ACTION_MOVE })
    }

    @Test
    fun testEventWithMotionClassification_doesNothing() {
        val dragDetector = createDragDetector()
        assertFalse(
            dragDetector.onMotionEvent(
                createMotionEvent(
                    MotionEvent.ACTION_DOWN,
                    isTouch = false,
                    classification = MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE
                )
            )
        )
        verify(eventHandler, never()).handleMotionEvent(any(), any())

        assertFalse(
            dragDetector.onMotionEvent(
                createMotionEvent(
                    MotionEvent.ACTION_UP,
                    isTouch = false,
                    classification = MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE
                )
            )
        )
        verify(eventHandler, never()).handleMotionEvent(any(), any())
    }

    private fun createMotionEvent(
        action: Int,
        x: Float = X,
        y: Float = Y,
        isTouch: Boolean = true,
        downTime: Long = SystemClock.uptimeMillis(),
        eventTime: Long = SystemClock.uptimeMillis()
        eventTime: Long = SystemClock.uptimeMillis(),
        classification: Int = MotionEvent.CLASSIFICATION_NONE,
    ): MotionEvent {
        val ev = MotionEvent.obtain(downTime, eventTime, action, x, y, 0)
        ev.source = if (isTouch) InputDevice.SOURCE_TOUCHSCREEN else InputDevice.SOURCE_MOUSE
        val pointerProperties = arrayOf(MotionEvent.PointerProperties().apply {
            this.id = 0
            this.toolType = MotionEvent.TOOL_TYPE_FINGER
        })
        val pointerCoords = arrayOf(MotionEvent.PointerCoords().apply {
            this.x = x
            this.y = y
        })
        val ev = MotionEvent.obtain(
            downTime,
            eventTime,
            action,
            /* pointerCount= */ 1,
            pointerProperties,
            pointerCoords,
            /* metaState= */ 0,
            /* buttonState= */ 0,
            /* xPrecision= */ 0f,
            /* yPrecision= */ 0f,
            /* deviceId= */ 0,
            /* edgeFlags= */ 0,
            if (isTouch) InputDevice.SOURCE_TOUCHSCREEN else InputDevice.SOURCE_MOUSE,
            /* displayId= */ 0,
            /* flags= */ 0,
            classification,
        )!!
        motionEvents.add(ev)
        return ev
    }