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