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 }