1 /*
2  * Copyright (C) 2023 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.wm.shell.windowdecor
18 
19 import android.os.SystemClock
20 import android.testing.AndroidTestingRunner
21 import android.view.MotionEvent
22 import android.view.InputDevice
23 import androidx.test.filters.SmallTest
24 import org.junit.After
25 import org.junit.Assert.assertFalse
26 import org.junit.Assert.assertTrue
27 import org.junit.Before
28 import org.junit.Test
29 import org.junit.runner.RunWith
30 import org.mockito.Mock
31 import org.mockito.MockitoAnnotations
32 import org.mockito.Mockito.`when`
33 import org.mockito.Mockito.any
34 import org.mockito.Mockito.argThat
35 import org.mockito.Mockito.never
36 import org.mockito.Mockito.verify
37 
38 /**
39  * Tests for [DragDetector].
40  *
41  * Build/Install/Run:
42  * atest WMShellUnitTests:DragDetectorTest
43  */
44 @SmallTest
45 @RunWith(AndroidTestingRunner::class)
46 class DragDetectorTest {
47     private val motionEvents = mutableListOf<MotionEvent>()
48 
49     @Mock
50     private lateinit var eventHandler: DragDetector.MotionEventHandler
51 
52     private lateinit var dragDetector: DragDetector
53 
54     @Before
setUpnull55     fun setUp() {
56         MockitoAnnotations.initMocks(this)
57 
58         `when`(eventHandler.handleMotionEvent(any(), any())).thenReturn(true)
59 
60         dragDetector = DragDetector(eventHandler)
61         dragDetector.setTouchSlop(SLOP)
62     }
63 
64     @After
tearDownnull65     fun tearDown() {
66         motionEvents.forEach {
67             it.recycle()
68         }
69         motionEvents.clear()
70     }
71 
72     @Test
testNoMove_passesDownAndUpnull73     fun testNoMove_passesDownAndUp() {
74         assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
75         verify(eventHandler).handleMotionEvent(any(), argThat {
76             return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
77                     it.source == InputDevice.SOURCE_TOUCHSCREEN
78         })
79 
80         assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP)))
81         verify(eventHandler).handleMotionEvent(any(), argThat {
82             return@argThat it.action == MotionEvent.ACTION_UP && it.x == X && it.y == Y &&
83                     it.source == InputDevice.SOURCE_TOUCHSCREEN
84         })
85     }
86 
87     @Test
testMoveInSlop_touch_passesDownAndUpnull88     fun testMoveInSlop_touch_passesDownAndUp() {
89         `when`(eventHandler.handleMotionEvent(any(), argThat {
90             return@argThat it.action == MotionEvent.ACTION_DOWN
91         })).thenReturn(false)
92 
93         assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
94         verify(eventHandler).handleMotionEvent(any(), argThat {
95             return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
96                     it.source == InputDevice.SOURCE_TOUCHSCREEN
97         })
98 
99         val newX = X + SLOP - 1
100         assertFalse(
101                 dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y)))
102         verify(eventHandler, never()).handleMotionEvent(any(), argThat {
103             return@argThat it.action == MotionEvent.ACTION_MOVE
104         })
105 
106         assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y)))
107         verify(eventHandler).handleMotionEvent(any(), argThat {
108             return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
109                     it.source == InputDevice.SOURCE_TOUCHSCREEN
110         })
111     }
112 
113     @Test
testMoveInSlop_mouse_passesDownMoveAndUpnull114     fun testMoveInSlop_mouse_passesDownMoveAndUp() {
115         `when`(eventHandler.handleMotionEvent(any(), argThat {
116             it.action == MotionEvent.ACTION_DOWN
117         })).thenReturn(false)
118 
119         assertFalse(dragDetector.onMotionEvent(
120                 createMotionEvent(MotionEvent.ACTION_DOWN, isTouch = false)))
121         verify(eventHandler).handleMotionEvent(any(), argThat {
122             return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
123                     it.source == InputDevice.SOURCE_MOUSE
124         })
125 
126         val newX = X + SLOP - 1
127         assertTrue(dragDetector.onMotionEvent(
128                 createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y, isTouch = false)))
129         verify(eventHandler).handleMotionEvent(any(), argThat {
130             return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
131                     it.source == InputDevice.SOURCE_MOUSE
132         })
133 
134         assertTrue(dragDetector.onMotionEvent(
135                 createMotionEvent(MotionEvent.ACTION_UP, newX, Y, isTouch = false)))
136         verify(eventHandler).handleMotionEvent(any(), argThat {
137             return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
138                     it.source == InputDevice.SOURCE_MOUSE
139         })
140     }
141 
142     @Test
testMoveBeyondSlop_passesDownMoveAndUpnull143     fun testMoveBeyondSlop_passesDownMoveAndUp() {
144         `when`(eventHandler.handleMotionEvent(any(), argThat {
145             it.action == MotionEvent.ACTION_DOWN
146         })).thenReturn(false)
147 
148         assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
149         verify(eventHandler).handleMotionEvent(any(), argThat {
150             return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
151                     it.source == InputDevice.SOURCE_TOUCHSCREEN
152         })
153 
154         val newX = X + SLOP + 1
155         assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y)))
156         verify(eventHandler).handleMotionEvent(any(), argThat {
157             return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
158                     it.source == InputDevice.SOURCE_TOUCHSCREEN
159         })
160 
161         assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y)))
162         verify(eventHandler).handleMotionEvent(any(), argThat {
163             return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
164                     it.source == InputDevice.SOURCE_TOUCHSCREEN
165         })
166     }
167 
168     @Test
testPassesHoverEnternull169     fun testPassesHoverEnter() {
170         `when`(eventHandler.handleMotionEvent(any(), argThat {
171             it.action == MotionEvent.ACTION_HOVER_ENTER
172         })).thenReturn(false)
173 
174         assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_ENTER)))
175         verify(eventHandler).handleMotionEvent(any(), argThat {
176             return@argThat it.action == MotionEvent.ACTION_HOVER_ENTER && it.x == X && it.y == Y
177         })
178     }
179 
180     @Test
testPassesHoverMovenull181     fun testPassesHoverMove() {
182         assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_MOVE)))
183         verify(eventHandler).handleMotionEvent(any(), argThat {
184             return@argThat it.action == MotionEvent.ACTION_HOVER_MOVE && it.x == X && it.y == Y
185         })
186     }
187 
188     @Test
testPassesHoverExitnull189     fun testPassesHoverExit() {
190         assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_EXIT)))
191         verify(eventHandler).handleMotionEvent(any(), argThat {
192             return@argThat it.action == MotionEvent.ACTION_HOVER_EXIT && it.x == X && it.y == Y
193         })
194     }
195 
createMotionEventnull196     private fun createMotionEvent(action: Int, x: Float = X, y: Float = Y, isTouch: Boolean = true):
197             MotionEvent {
198         val time = SystemClock.uptimeMillis()
199         val ev = MotionEvent.obtain(time, time, action, x, y, 0)
200         ev.source = if (isTouch) InputDevice.SOURCE_TOUCHSCREEN else InputDevice.SOURCE_MOUSE
201         motionEvents.add(ev)
202         return ev
203     }
204 
205     companion object {
206         private const val SLOP = 10
207         private const val X = 123f
208         private const val Y = 234f
209     }
210 }