1 /* 2 * Copyright (C) 2019 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; 18 19 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 20 21 import static org.junit.Assert.assertEquals; 22 import static org.mockito.ArgumentMatchers.any; 23 import static org.mockito.ArgumentMatchers.anyInt; 24 import static org.mockito.Mockito.doReturn; 25 import static org.mockito.Mockito.never; 26 import static org.mockito.Mockito.spy; 27 import static org.mockito.Mockito.verify; 28 29 import android.content.Context; 30 import android.graphics.Region; 31 import android.platform.test.annotations.Presubmit; 32 33 import androidx.test.filters.SmallTest; 34 35 import org.junit.Test; 36 37 38 /** 39 * Test basic functions of ViewGroup. 40 * 41 * Build/Install/Run: 42 * atest FrameworksCoreTests:ViewGroupTest 43 */ 44 @Presubmit 45 @SmallTest 46 public class ViewGroupTest { 47 48 @Test testDispatchMouseEventsUnderCursor()49 public void testDispatchMouseEventsUnderCursor() { 50 final Context context = getInstrumentation().getContext(); 51 final TestView viewGroup = new TestView(context, 0 /* left */, 0 /* top */, 52 200 /* right */, 100 /* bottom */); 53 final TestView viewA = spy(new TestView(context, 0 /* left */, 0 /* top */, 54 100 /* right */, 100 /* bottom */)); 55 final TestView viewB = spy(new TestView(context, 100 /* left */, 0 /* top */, 56 200 /* right */, 100 /* bottom */)); 57 58 viewGroup.addView(viewA); 59 viewGroup.addView(viewB); 60 61 // Make sure all of them handle touch events dispatched to them. 62 doReturn(true).when(viewA).dispatchTouchEvent(any()); 63 doReturn(true).when(viewB).dispatchTouchEvent(any()); 64 65 MotionEvent.PointerProperties[] properties = new MotionEvent.PointerProperties[2]; 66 properties[0] = new MotionEvent.PointerProperties(); 67 properties[0].id = 0; 68 properties[0].toolType = MotionEvent.TOOL_TYPE_FINGER; 69 properties[1] = new MotionEvent.PointerProperties(); 70 properties[1].id = 1; 71 properties[1].toolType = MotionEvent.TOOL_TYPE_FINGER; 72 73 MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[2]; 74 coords[0] = new MotionEvent.PointerCoords(); 75 coords[0].x = 80; 76 coords[0].y = 50; 77 coords[1] = new MotionEvent.PointerCoords(); 78 coords[1].x = 240; 79 coords[1].y = 50; 80 81 MotionEvent event; 82 // Make sure the down event is active with a pointer which coordinate is different from the 83 // cursor position, which is the midpoint of all 2 pointers above. 84 event = MotionEvent.obtain(0 /* downTime */, 0 /* eventTime */, MotionEvent.ACTION_DOWN, 85 2 /* pointerCount */, properties, coords, 0 /* metaState */, 0 /* buttonState */, 86 0 /* xPrecision */, 0 /* yPrecision */, 0 /* deviceId */, 0 /* edgeFlags */, 87 InputDevice.SOURCE_MOUSE, 0 /* flags */); 88 viewGroup.dispatchTouchEvent(event); 89 verify(viewB).dispatchTouchEvent(event); 90 91 viewGroup.onResolvePointerIcon(event, 0 /* pointerIndex */); 92 verify(viewB).onResolvePointerIcon(event, 0); 93 94 event.setAction(MotionEvent.ACTION_SCROLL); 95 viewGroup.dispatchGenericMotionEvent(event); 96 verify(viewB).dispatchGenericMotionEvent(event); 97 98 event = MotionEvent.obtain(0 /* downTime */, 0 /* eventTime */, 99 MotionEvent.ACTION_POINTER_DOWN | (1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT), 100 2 /* pointerCount */, properties, coords, 0 /* metaState */, 0 /* buttonState */, 101 0 /* xPrecision */, 0 /* yPrecision */, 0 /* deviceId */, 0 /* edgeFlags */, 102 InputDevice.SOURCE_MOUSE, 0 /* flags */); 103 viewGroup.dispatchTouchEvent(event); 104 verify(viewB).dispatchTouchEvent(event); 105 106 viewGroup.onResolvePointerIcon(event, 1 /* pointerIndex */); 107 verify(viewB).onResolvePointerIcon(event, 1); 108 109 event.setAction(MotionEvent.ACTION_SCROLL); 110 viewGroup.dispatchGenericMotionEvent(event); 111 verify(viewB).dispatchGenericMotionEvent(event); 112 113 verify(viewA, never()).dispatchTouchEvent(any()); 114 verify(viewA, never()).onResolvePointerIcon(any(), anyInt()); 115 verify(viewA, never()).dispatchGenericMotionEvent(any()); 116 } 117 118 /** 119 * Test if {@link ViewGroup#subtractObscuredTouchableRegion} works as expected. 120 * 121 * The view hierarchy: 122 * A---B---C 123 * \ \ 124 * \ --D 125 * \ 126 * E---F 127 * 128 * The layer and bounds of each view: 129 * F -- (invisible) 130 * E -- 131 * D ---- 132 * C ---------- 133 * B ------ 134 * A -------- 135 */ 136 @Test testSubtractObscuredTouchableRegion()137 public void testSubtractObscuredTouchableRegion() { 138 final Context context = getInstrumentation().getContext(); 139 final TestView viewA = new TestView(context, 8 /* right */); 140 final TestView viewB = new TestView(context, 6 /* right */); 141 final TestView viewC = new TestView(context, 10 /* right */); 142 final TestView viewD = new TestView(context, 4 /* right */); 143 final TestView viewE = new TestView(context, 2 /* right */); 144 final TestView viewF = new TestView(context, 2 /* right */); 145 146 viewA.addView(viewB); 147 viewA.addView(viewE); 148 viewB.addView(viewC); 149 viewB.addView(viewD); 150 viewE.addView(viewF); 151 152 viewF.setVisibility(View.INVISIBLE); 153 154 final Region r = new Region(); 155 156 getUnobscuredTouchableRegion(r, viewA); 157 assertRegionContainPoint(1 /* x */, r, true /* contain */); 158 assertRegionContainPoint(3 /* x */, r, true /* contain */); 159 assertRegionContainPoint(5 /* x */, r, true /* contain */); 160 assertRegionContainPoint(7 /* x */, r, true /* contain */); 161 assertRegionContainPoint(9 /* x */, r, false /* contain */); // Outside of bounds 162 163 getUnobscuredTouchableRegion(r, viewB); 164 assertRegionContainPoint(1 /* x */, r, false /* contain */); // Obscured by E 165 assertRegionContainPoint(3 /* x */, r, true /* contain */); 166 assertRegionContainPoint(5 /* x */, r, true /* contain */); 167 assertRegionContainPoint(7 /* x */, r, false /* contain */); // Outside of bounds 168 169 getUnobscuredTouchableRegion(r, viewC); 170 assertRegionContainPoint(1 /* x */, r, false /* contain */); // Obscured by D and E 171 assertRegionContainPoint(3 /* x */, r, false /* contain */); // Obscured by D 172 assertRegionContainPoint(5 /* x */, r, true /* contain */); 173 assertRegionContainPoint(7 /* x */, r, false /* contain */); // Outside of parent bounds 174 175 getUnobscuredTouchableRegion(r, viewD); 176 assertRegionContainPoint(1 /* x */, r, false /* contain */); // Obscured by E 177 assertRegionContainPoint(3 /* x */, r, true /* contain */); 178 assertRegionContainPoint(5 /* x */, r, false /* contain */); // Outside of bounds 179 180 getUnobscuredTouchableRegion(r, viewE); 181 assertRegionContainPoint(1 /* x */, r, true /* contain */); 182 assertRegionContainPoint(3 /* x */, r, false /* contain */); // Outside of bounds 183 } 184 getUnobscuredTouchableRegion(Region outRegion, View view)185 private static void getUnobscuredTouchableRegion(Region outRegion, View view) { 186 outRegion.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()); 187 final ViewParent parent = view.getParent(); 188 if (parent != null) { 189 parent.subtractObscuredTouchableRegion(outRegion, view); 190 } 191 } 192 assertRegionContainPoint(int x, Region region, boolean contain)193 private static void assertRegionContainPoint(int x, Region region, boolean contain) { 194 assertEquals(String.format("Touchable region must%s contain (%s, 0).", 195 (contain ? "" : " not"), x), contain, region.contains(x, 0 /* y */)); 196 } 197 198 public static class TestView extends ViewGroup { TestView(Context context, int right)199 TestView(Context context, int right) { 200 this(context, 0 /* left */, 0 /* top */, right, 1 /* bottom */); 201 } 202 TestView(Context context, int left, int top, int right, int bottom)203 TestView(Context context, int left, int top, int right, int bottom) { 204 super(context); 205 setFrame(left, top, right, bottom); 206 } 207 208 @Override onLayout(boolean changed, int l, int t, int r, int b)209 protected void onLayout(boolean changed, int l, int t, int r, int b) { 210 // We don't layout this view. 211 } 212 } 213 } 214