• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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