1 /* 2 * Copyright (C) 2009 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 android.view.cts; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertNotNull; 21 import static org.junit.Assert.assertNull; 22 23 import android.app.Instrumentation; 24 import android.os.SystemClock; 25 import android.support.test.InstrumentationRegistry; 26 import android.support.test.filters.MediumTest; 27 import android.support.test.rule.ActivityTestRule; 28 import android.support.test.runner.AndroidJUnit4; 29 import android.view.InputDevice; 30 import android.view.MotionEvent; 31 32 33 import org.junit.Before; 34 import org.junit.Rule; 35 import org.junit.Test; 36 import org.junit.runner.RunWith; 37 38 @MediumTest 39 @RunWith(AndroidJUnit4.class) 40 public class TouchDelegateTest { 41 private Instrumentation mInstrumentation; 42 private TouchDelegateTestActivity mActivity; 43 44 @Rule 45 public ActivityTestRule<TouchDelegateTestActivity> mActivityRule = 46 new ActivityTestRule<>(TouchDelegateTestActivity.class); 47 48 @Before 49 public void setup() throws Throwable { 50 mActivity = mActivityRule.getActivity(); 51 mActivity.resetCounters(); 52 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 53 mInstrumentation.waitForIdleSync(); 54 } 55 56 @Test 57 public void testParentClick() { 58 // If only clicking parent, button should not receive click 59 clickParent(); 60 assertEquals(0, mActivity.getButtonClickCount()); 61 assertEquals(1, mActivity.getParentClickCount()); 62 63 // When clicking TouchDelegate area, both parent and button 64 // should receive DOWN and UP events. However, click will only be generated for the button 65 mActivity.resetCounters(); 66 clickTouchDelegateArea(); 67 assertEquals(1, mActivity.getButtonClickCount()); 68 assertEquals(0, mActivity.getParentClickCount()); 69 70 // Ensure parent can still receive clicks after TouchDelegate has been activated once 71 mActivity.resetCounters(); 72 clickParent(); 73 assertEquals(0, mActivity.getButtonClickCount()); 74 assertEquals(1, mActivity.getParentClickCount()); 75 } 76 77 @Test 78 public void testCancelEvent() { 79 // Ensure events with ACTION_CANCEL are received by the TouchDelegate 80 final long downTime = SystemClock.uptimeMillis(); 81 dispatchMotionEventToActivity(MotionEvent.ACTION_DOWN, mActivity.touchDelegateY, 82 downTime); 83 dispatchMotionEventToActivity(MotionEvent.ACTION_CANCEL, mActivity.touchDelegateY, 84 downTime); 85 mInstrumentation.waitForIdleSync(); 86 87 ensureOldestActionEquals(MotionEvent.ACTION_DOWN); 88 ensureOldestActionEquals(MotionEvent.ACTION_CANCEL); 89 90 assertNull(mActivity.removeOldestButtonEvent()); 91 assertEquals(0, mActivity.getButtonClickCount()); 92 assertEquals(0, mActivity.getParentClickCount()); 93 } 94 95 @Test 96 public void testTwoPointers() { 97 // Ensure ACTION_POINTER_DOWN and ACTION_POINTER_UP are forwarded to the target view 98 // by the TouchDelegate 99 final long downTime = SystemClock.uptimeMillis(); 100 dispatchMotionEventToActivity(MotionEvent.ACTION_DOWN, mActivity.touchDelegateY, downTime); 101 dispatchMotionEventToActivity(MotionEvent.ACTION_MOVE, mActivity.touchDelegateY, downTime); 102 int actionPointer1Down = 103 (1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT) + MotionEvent.ACTION_POINTER_DOWN; 104 dispatchMultiTouchMotionEventToActivity(actionPointer1Down, 2, 105 mActivity.touchDelegateY, downTime); 106 dispatchMultiTouchMotionEventToActivity(MotionEvent.ACTION_MOVE, 2, 107 mActivity.touchDelegateY, downTime); 108 int actionPointer1Up = 109 (1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT) + MotionEvent.ACTION_POINTER_UP; 110 dispatchMultiTouchMotionEventToActivity(actionPointer1Up, 2, 111 mActivity.touchDelegateY, downTime); 112 dispatchMotionEventToActivity(MotionEvent.ACTION_UP, mActivity.touchDelegateY, downTime); 113 mInstrumentation.waitForIdleSync(); 114 115 ensureOldestActionEquals(MotionEvent.ACTION_DOWN); 116 ensureOldestActionEquals(MotionEvent.ACTION_MOVE); 117 ensureOldestActionEquals(MotionEvent.ACTION_POINTER_DOWN); 118 ensureOldestActionEquals(MotionEvent.ACTION_MOVE); 119 ensureOldestActionEquals(MotionEvent.ACTION_POINTER_UP); 120 ensureOldestActionEquals(MotionEvent.ACTION_UP); 121 } 122 123 private void ensureOldestActionEquals(int action) { 124 MotionEvent event = mActivity.removeOldestButtonEvent(); 125 assertNotNull(event); 126 assertEquals(action, event.getActionMasked()); 127 event.recycle(); 128 } 129 130 private void clickParent() { 131 click(mActivity.parentViewY); 132 } 133 134 private void clickTouchDelegateArea() { 135 click(mActivity.touchDelegateY); 136 } 137 138 // Low-level input-handling functions for the activity 139 140 private void click(int y) { 141 final long downTime = SystemClock.uptimeMillis(); 142 dispatchMotionEventToActivity(MotionEvent.ACTION_DOWN, y, downTime); 143 dispatchMotionEventToActivity(MotionEvent.ACTION_UP, y, downTime); 144 mInstrumentation.waitForIdleSync(); 145 } 146 147 private void dispatchMotionEventToActivity(int action, int y, long downTime) { 148 mActivity.runOnUiThread(() -> { 149 final long eventTime = SystemClock.uptimeMillis(); 150 final MotionEvent event = MotionEvent.obtain(downTime, eventTime, action, 151 mActivity.x, y, 0); 152 event.setSource(InputDevice.SOURCE_TOUCHSCREEN); 153 mActivity.dispatchTouchEvent(event); 154 event.recycle(); 155 }); 156 } 157 158 private void dispatchMultiTouchMotionEventToActivity(int action, int pointerCount, 159 int y, long downTime) { 160 mActivity.runOnUiThread(() -> { 161 final long eventTime = SystemClock.uptimeMillis(); 162 MotionEvent.PointerProperties[] properties = 163 new MotionEvent.PointerProperties[pointerCount]; 164 MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[pointerCount]; 165 166 for (int i = 0; i < pointerCount; i++) { 167 properties[i] = new MotionEvent.PointerProperties(); 168 properties[i].id = i; 169 properties[i].toolType = MotionEvent.TOOL_TYPE_FINGER; 170 coords[i] = new MotionEvent.PointerCoords(); 171 coords[i].x = mActivity.x + i * 10; // small offset so that pointers do not overlap 172 coords[i].y = y; 173 } 174 175 final MotionEvent event = MotionEvent.obtain(downTime, eventTime, action, pointerCount, 176 properties, coords, 0, 0, 0, 0, 177 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0); 178 179 mActivity.dispatchTouchEvent(event); 180 event.recycle(); 181 }); 182 } 183 } 184