1 /*
2  * Copyright (C) 2008 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.widget.cts;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertNull;
22 import static org.junit.Assert.assertSame;
23 import static org.junit.Assert.assertTrue;
24 import static org.mockito.Matchers.anyInt;
25 import static org.mockito.Mockito.any;
26 import static org.mockito.Mockito.mock;
27 import static org.mockito.Mockito.never;
28 import static org.mockito.Mockito.times;
29 import static org.mockito.Mockito.verify;
30 import static org.mockito.Mockito.when;
31 
32 import android.app.Instrumentation;
33 import android.content.Context;
34 import android.content.pm.ActivityInfo;
35 import android.content.res.Configuration;
36 import android.graphics.Color;
37 import android.graphics.Rect;
38 import android.graphics.drawable.ColorDrawable;
39 import android.graphics.drawable.Drawable;
40 import android.os.SystemClock;
41 import android.support.test.InstrumentationRegistry;
42 import android.support.test.annotation.UiThreadTest;
43 import android.support.test.filters.FlakyTest;
44 import android.support.test.filters.SmallTest;
45 import android.support.test.rule.ActivityTestRule;
46 import android.support.test.runner.AndroidJUnit4;
47 import android.transition.Transition;
48 import android.transition.Transition.TransitionListener;
49 import android.transition.TransitionValues;
50 import android.util.AttributeSet;
51 import android.util.DisplayMetrics;
52 import android.view.Display;
53 import android.view.Gravity;
54 import android.view.MotionEvent;
55 import android.view.View;
56 import android.view.View.OnTouchListener;
57 import android.view.ViewGroup;
58 import android.view.ViewGroup.LayoutParams;
59 import android.view.WindowManager;
60 import android.widget.ImageView;
61 import android.widget.PopupWindow;
62 import android.widget.PopupWindow.OnDismissListener;
63 import android.widget.TextView;
64 
65 import com.android.compatibility.common.util.WidgetTestUtils;
66 
67 import org.junit.Before;
68 import org.junit.Rule;
69 import org.junit.Test;
70 import org.junit.runner.RunWith;
71 
72 @FlakyTest
73 @SmallTest
74 @RunWith(AndroidJUnit4.class)
75 public class PopupWindowTest {
76     private static final int WINDOW_SIZE_DP = 50;
77     private static final int CONTENT_SIZE_DP = 30;
78 
79     private Instrumentation mInstrumentation;
80     private PopupWindowCtsActivity mActivity;
81     private PopupWindow mPopupWindow;
82     private TextView mTextView;
83 
84     @Rule
85     public ActivityTestRule<PopupWindowCtsActivity> mActivityRule =
86             new ActivityTestRule<>(PopupWindowCtsActivity.class);
87 
88     @Before
setup()89     public void setup() {
90         mInstrumentation = InstrumentationRegistry.getInstrumentation();
91         mActivity = mActivityRule.getActivity();
92     }
93 
94     @Test
testConstructor()95     public void testConstructor() {
96         new PopupWindow(mActivity);
97 
98         new PopupWindow(mActivity, null);
99 
100         new PopupWindow(mActivity, null, android.R.attr.popupWindowStyle);
101 
102         new PopupWindow(mActivity, null, 0, android.R.style.Widget_DeviceDefault_PopupWindow);
103 
104         new PopupWindow(mActivity, null, 0, android.R.style.Widget_DeviceDefault_Light_PopupWindow);
105 
106         new PopupWindow(mActivity, null, 0, android.R.style.Widget_Material_PopupWindow);
107 
108         new PopupWindow(mActivity, null, 0, android.R.style.Widget_Material_Light_PopupWindow);
109     }
110 
111     @UiThreadTest
112     @Test
testSize()113     public void testSize() {
114         mPopupWindow = new PopupWindow();
115         assertEquals(0, mPopupWindow.getWidth());
116         assertEquals(0, mPopupWindow.getHeight());
117 
118         mPopupWindow = new PopupWindow(50, 50);
119         assertEquals(50, mPopupWindow.getWidth());
120         assertEquals(50, mPopupWindow.getHeight());
121 
122         mPopupWindow = new PopupWindow(-1, -1);
123         assertEquals(-1, mPopupWindow.getWidth());
124         assertEquals(-1, mPopupWindow.getHeight());
125 
126         TextView contentView = new TextView(mActivity);
127         mPopupWindow = new PopupWindow(contentView);
128         assertSame(contentView, mPopupWindow.getContentView());
129 
130         mPopupWindow = new PopupWindow(contentView, 0, 0);
131         assertEquals(0, mPopupWindow.getWidth());
132         assertEquals(0, mPopupWindow.getHeight());
133         assertSame(contentView, mPopupWindow.getContentView());
134 
135         mPopupWindow = new PopupWindow(contentView, 50, 50);
136         assertEquals(50, mPopupWindow.getWidth());
137         assertEquals(50, mPopupWindow.getHeight());
138         assertSame(contentView, mPopupWindow.getContentView());
139 
140         mPopupWindow = new PopupWindow(contentView, -1, -1);
141         assertEquals(-1, mPopupWindow.getWidth());
142         assertEquals(-1, mPopupWindow.getHeight());
143         assertSame(contentView, mPopupWindow.getContentView());
144 
145         mPopupWindow = new PopupWindow(contentView, 0, 0, true);
146         assertEquals(0, mPopupWindow.getWidth());
147         assertEquals(0, mPopupWindow.getHeight());
148         assertSame(contentView, mPopupWindow.getContentView());
149         assertTrue(mPopupWindow.isFocusable());
150 
151         mPopupWindow = new PopupWindow(contentView, 50, 50, false);
152         assertEquals(50, mPopupWindow.getWidth());
153         assertEquals(50, mPopupWindow.getHeight());
154         assertSame(contentView, mPopupWindow.getContentView());
155         assertFalse(mPopupWindow.isFocusable());
156 
157         mPopupWindow = new PopupWindow(contentView, -1, -1, true);
158         assertEquals(-1, mPopupWindow.getWidth());
159         assertEquals(-1, mPopupWindow.getHeight());
160         assertSame(contentView, mPopupWindow.getContentView());
161         assertTrue(mPopupWindow.isFocusable());
162     }
163 
164     @Test
testAccessEnterExitTransitions()165     public void testAccessEnterExitTransitions() {
166         PopupWindow w = new PopupWindow(mActivity, null, 0, 0);
167         assertNull(w.getEnterTransition());
168         assertNull(w.getExitTransition());
169 
170         w = new PopupWindow(mActivity, null, 0, R.style.PopupWindow_NullTransitions);
171         assertNull(w.getEnterTransition());
172         assertNull(w.getExitTransition());
173 
174         w = new PopupWindow(mActivity, null, 0, R.style.PopupWindow_CustomTransitions);
175         assertTrue(w.getEnterTransition() instanceof CustomTransition);
176         assertTrue(w.getExitTransition() instanceof CustomTransition);
177 
178         Transition enterTransition = new CustomTransition();
179         Transition exitTransition = new CustomTransition();
180         w = new PopupWindow(mActivity, null, 0, 0);
181         w.setEnterTransition(enterTransition);
182         w.setExitTransition(exitTransition);
183         assertEquals(enterTransition, w.getEnterTransition());
184         assertEquals(exitTransition, w.getExitTransition());
185 
186         w.setEnterTransition(null);
187         w.setExitTransition(null);
188         assertNull(w.getEnterTransition());
189         assertNull(w.getExitTransition());
190     }
191 
192     public static class CustomTransition extends Transition {
CustomTransition()193         public CustomTransition() {
194         }
195 
196         // This constructor is needed for reflection-based creation of a transition when
197         // the transition is defined in layout XML via attribute.
198         @SuppressWarnings("unused")
CustomTransition(Context context, AttributeSet attrs)199         public CustomTransition(Context context, AttributeSet attrs) {
200             super(context, attrs);
201         }
202 
203         @Override
captureStartValues(TransitionValues transitionValues)204         public void captureStartValues(TransitionValues transitionValues) {}
205 
206         @Override
captureEndValues(TransitionValues transitionValues)207         public void captureEndValues(TransitionValues transitionValues) {}
208     }
209 
210     @Test
testAccessBackground()211     public void testAccessBackground() {
212         mPopupWindow = new PopupWindow(mActivity);
213 
214         Drawable drawable = new ColorDrawable();
215         mPopupWindow.setBackgroundDrawable(drawable);
216         assertSame(drawable, mPopupWindow.getBackground());
217 
218         mPopupWindow.setBackgroundDrawable(null);
219         assertNull(mPopupWindow.getBackground());
220     }
221 
222     @Test
testAccessAnimationStyle()223     public void testAccessAnimationStyle() {
224         mPopupWindow = new PopupWindow(mActivity);
225         // default is -1
226         assertEquals(-1, mPopupWindow.getAnimationStyle());
227 
228         mPopupWindow.setAnimationStyle(android.R.style.Animation_Toast);
229         assertEquals(android.R.style.Animation_Toast,
230                 mPopupWindow.getAnimationStyle());
231 
232         // abnormal values
233         mPopupWindow.setAnimationStyle(-100);
234         assertEquals(-100, mPopupWindow.getAnimationStyle());
235     }
236 
237     @Test
testAccessContentView()238     public void testAccessContentView() throws Throwable {
239         mPopupWindow = new PopupWindow(mActivity);
240         assertNull(mPopupWindow.getContentView());
241 
242         mActivityRule.runOnUiThread(() -> mTextView = new TextView(mActivity));
243         mInstrumentation.waitForIdleSync();
244         mPopupWindow.setContentView(mTextView);
245         assertSame(mTextView, mPopupWindow.getContentView());
246 
247         mPopupWindow.setContentView(null);
248         assertNull(mPopupWindow.getContentView());
249 
250         // can not set the content if the old content is shown
251         mPopupWindow.setContentView(mTextView);
252         assertFalse(mPopupWindow.isShowing());
253         showPopup();
254         ImageView img = new ImageView(mActivity);
255         assertTrue(mPopupWindow.isShowing());
256         mPopupWindow.setContentView(img);
257         assertSame(mTextView, mPopupWindow.getContentView());
258         dismissPopup();
259     }
260 
261     @Test
testAccessFocusable()262     public void testAccessFocusable() {
263         mPopupWindow = new PopupWindow(mActivity);
264         assertFalse(mPopupWindow.isFocusable());
265 
266         mPopupWindow.setFocusable(true);
267         assertTrue(mPopupWindow.isFocusable());
268 
269         mPopupWindow.setFocusable(false);
270         assertFalse(mPopupWindow.isFocusable());
271     }
272 
273     @Test
testAccessHeight()274     public void testAccessHeight() {
275         mPopupWindow = new PopupWindow(mActivity);
276         assertEquals(WindowManager.LayoutParams.WRAP_CONTENT, mPopupWindow.getHeight());
277 
278         int height = getDisplay().getHeight() / 2;
279         mPopupWindow.setHeight(height);
280         assertEquals(height, mPopupWindow.getHeight());
281 
282         height = getDisplay().getHeight();
283         mPopupWindow.setHeight(height);
284         assertEquals(height, mPopupWindow.getHeight());
285 
286         mPopupWindow.setHeight(0);
287         assertEquals(0, mPopupWindow.getHeight());
288 
289         height = getDisplay().getHeight() * 2;
290         mPopupWindow.setHeight(height);
291         assertEquals(height, mPopupWindow.getHeight());
292 
293         height = -getDisplay().getHeight() / 2;
294         mPopupWindow.setHeight(height);
295         assertEquals(height, mPopupWindow.getHeight());
296     }
297 
298     /**
299      * Gets the display.
300      *
301      * @return the display
302      */
getDisplay()303     private Display getDisplay() {
304         WindowManager wm = (WindowManager) mActivity.getSystemService(Context.WINDOW_SERVICE);
305         return wm.getDefaultDisplay();
306     }
307 
308     @Test
testAccessWidth()309     public void testAccessWidth() {
310         mPopupWindow = new PopupWindow(mActivity);
311         assertEquals(WindowManager.LayoutParams.WRAP_CONTENT, mPopupWindow.getWidth());
312 
313         int width = getDisplay().getWidth() / 2;
314         mPopupWindow.setWidth(width);
315         assertEquals(width, mPopupWindow.getWidth());
316 
317         width = getDisplay().getWidth();
318         mPopupWindow.setWidth(width);
319         assertEquals(width, mPopupWindow.getWidth());
320 
321         mPopupWindow.setWidth(0);
322         assertEquals(0, mPopupWindow.getWidth());
323 
324         width = getDisplay().getWidth() * 2;
325         mPopupWindow.setWidth(width);
326         assertEquals(width, mPopupWindow.getWidth());
327 
328         width = - getDisplay().getWidth() / 2;
329         mPopupWindow.setWidth(width);
330         assertEquals(width, mPopupWindow.getWidth());
331     }
332 
333     private static final int TOP = 0x00;
334     private static final int BOTTOM = 0x01;
335 
336     private static final int LEFT = 0x00;
337     private static final int RIGHT = 0x01;
338 
339     private static final int GREATER_THAN = 1;
340     private static final int LESS_THAN = -1;
341     private static final int EQUAL_TO = 0;
342 
343     @Test
testShowAsDropDown()344     public void testShowAsDropDown() throws Throwable {
345         final PopupWindow popup = createPopupWindow(createPopupContent(CONTENT_SIZE_DP,
346                 CONTENT_SIZE_DP));
347         popup.setClipToScreenEnabled(false);
348         popup.setOverlapAnchor(false);
349         popup.setAnimationStyle(0);
350         popup.setExitTransition(null);
351         popup.setEnterTransition(null);
352 
353         verifyPosition(popup, R.id.anchor_upper_left,
354                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM);
355         verifyPosition(popup, R.id.anchor_upper,
356                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM);
357         verifyPosition(popup, R.id.anchor_upper_right,
358                 RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, BOTTOM);
359 
360         verifyPosition(popup, R.id.anchor_middle_left,
361                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM);
362         verifyPosition(popup, R.id.anchor_middle,
363                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM);
364         verifyPosition(popup, R.id.anchor_middle_right,
365                 RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, BOTTOM);
366 
367         verifyPosition(popup, R.id.anchor_lower_left,
368                 LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP);
369         verifyPosition(popup, R.id.anchor_lower,
370                 LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP);
371         verifyPosition(popup, R.id.anchor_lower_right,
372                 RIGHT, EQUAL_TO, RIGHT, BOTTOM, EQUAL_TO, TOP);
373     }
374 
375     @Test
testShowAsDropDown_ClipToScreen()376     public void testShowAsDropDown_ClipToScreen() throws Throwable {
377         final PopupWindow popup = createPopupWindow(createPopupContent(CONTENT_SIZE_DP,
378                 CONTENT_SIZE_DP));
379         popup.setClipToScreenEnabled(true);
380         popup.setOverlapAnchor(false);
381         popup.setAnimationStyle(0);
382         popup.setExitTransition(null);
383         popup.setEnterTransition(null);
384 
385         verifyPosition(popup, R.id.anchor_upper_left,
386                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM);
387         verifyPosition(popup, R.id.anchor_upper,
388                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM);
389         verifyPosition(popup, R.id.anchor_upper_right,
390                 RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, BOTTOM);
391 
392         verifyPosition(popup, R.id.anchor_middle_left,
393                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM);
394         verifyPosition(popup, R.id.anchor_middle,
395                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM);
396         verifyPosition(popup, R.id.anchor_middle_right,
397                 RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, BOTTOM);
398 
399         verifyPosition(popup, R.id.anchor_lower_left,
400                 LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP);
401         verifyPosition(popup, R.id.anchor_lower,
402                 LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP);
403         verifyPosition(popup, R.id.anchor_lower_right,
404                 RIGHT, EQUAL_TO, RIGHT, BOTTOM, EQUAL_TO, TOP);
405     }
406 
407     @Test
testShowAsDropDown_ClipToScreen_Overlap()408     public void testShowAsDropDown_ClipToScreen_Overlap() throws Throwable {
409         final PopupWindow popup = createPopupWindow(createPopupContent(CONTENT_SIZE_DP,
410                 CONTENT_SIZE_DP));
411         popup.setClipToScreenEnabled(true);
412         popup.setOverlapAnchor(true);
413         popup.setAnimationStyle(0);
414         popup.setExitTransition(null);
415         popup.setEnterTransition(null);
416 
417         verifyPosition(popup, R.id.anchor_upper_left,
418                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP);
419         verifyPosition(popup, R.id.anchor_upper,
420                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP);
421         verifyPosition(popup, R.id.anchor_upper_right,
422                 RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, TOP);
423 
424         verifyPosition(popup, R.id.anchor_middle_left,
425                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP);
426         verifyPosition(popup, R.id.anchor_middle,
427                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP);
428         verifyPosition(popup, R.id.anchor_middle_right,
429                 RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, TOP);
430 
431         verifyPosition(popup, R.id.anchor_lower_left,
432                 LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP);
433         verifyPosition(popup, R.id.anchor_lower,
434                 LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP);
435         verifyPosition(popup, R.id.anchor_lower_right,
436                 RIGHT, EQUAL_TO, RIGHT, BOTTOM, EQUAL_TO, TOP);
437     }
438 
439     @Test
testShowAsDropDown_ClipToScreen_Overlap_Offset()440     public void testShowAsDropDown_ClipToScreen_Overlap_Offset() throws Throwable {
441         final PopupWindow popup = createPopupWindow(createPopupContent(CONTENT_SIZE_DP,
442                 CONTENT_SIZE_DP));
443         popup.setClipToScreenEnabled(true);
444         popup.setOverlapAnchor(true);
445         popup.setAnimationStyle(0);
446         popup.setExitTransition(null);
447         popup.setEnterTransition(null);
448 
449         final int offsetX = mActivity.findViewById(R.id.anchor_upper).getWidth() / 2;
450         final int offsetY = mActivity.findViewById(R.id.anchor_upper).getHeight() / 2;
451         final int gravity = Gravity.TOP | Gravity.START;
452 
453         verifyPosition(popup, R.id.anchor_upper_left,
454                 LEFT, GREATER_THAN, LEFT, TOP, GREATER_THAN, TOP,
455                 offsetX, offsetY, gravity);
456         verifyPosition(popup, R.id.anchor_upper,
457                 LEFT, GREATER_THAN, LEFT, TOP, GREATER_THAN, TOP,
458                 offsetX, offsetY, gravity);
459         verifyPosition(popup, R.id.anchor_upper_right,
460                 RIGHT, EQUAL_TO, RIGHT, TOP, GREATER_THAN, TOP,
461                 offsetX, offsetY, gravity);
462 
463         verifyPosition(popup, R.id.anchor_middle_left,
464                 LEFT, GREATER_THAN, LEFT, TOP, GREATER_THAN, TOP,
465                 offsetX, offsetY, gravity);
466         verifyPosition(popup, R.id.anchor_middle,
467                 LEFT, GREATER_THAN, LEFT, TOP, GREATER_THAN, TOP,
468                 offsetX, offsetY, gravity);
469         verifyPosition(popup, R.id.anchor_middle_right,
470                 RIGHT, EQUAL_TO, RIGHT, TOP, GREATER_THAN, TOP,
471                 offsetX, offsetY, gravity);
472 
473         verifyPosition(popup, R.id.anchor_lower_left,
474                 LEFT, GREATER_THAN, LEFT, BOTTOM, LESS_THAN, BOTTOM,
475                 offsetX, offsetY, gravity);
476         verifyPosition(popup, R.id.anchor_lower,
477                 LEFT, GREATER_THAN, LEFT, BOTTOM, LESS_THAN, BOTTOM,
478                 offsetX, offsetY, gravity);
479         verifyPosition(popup, R.id.anchor_lower_right,
480                 RIGHT, EQUAL_TO, RIGHT, BOTTOM, LESS_THAN, BOTTOM,
481                 offsetX, offsetY, gravity);
482     }
483 
484     @Test
testShowAsDropDown_ClipToScreen_TooBig()485     public void testShowAsDropDown_ClipToScreen_TooBig() throws Throwable {
486         final View rootView = mActivity.findViewById(R.id.anchor_upper_left).getRootView();
487         final int width = rootView.getWidth() * 2;
488         final int height = rootView.getHeight() * 2;
489 
490         final PopupWindow popup = createPopupWindow(createPopupContent(width, height));
491         popup.setWidth(width);
492         popup.setHeight(height);
493 
494         popup.setClipToScreenEnabled(true);
495         popup.setOverlapAnchor(false);
496         popup.setAnimationStyle(0);
497         popup.setExitTransition(null);
498         popup.setEnterTransition(null);
499 
500         verifyPosition(popup, R.id.anchor_upper_left,
501                 LEFT, EQUAL_TO, LEFT, TOP, LESS_THAN, TOP);
502         verifyPosition(popup, R.id.anchor_upper,
503                 LEFT, LESS_THAN, LEFT, TOP, LESS_THAN, TOP);
504         verifyPosition(popup, R.id.anchor_upper_right,
505                 RIGHT, EQUAL_TO, RIGHT, TOP, LESS_THAN, TOP);
506 
507         verifyPosition(popup, R.id.anchor_middle_left,
508                 LEFT, EQUAL_TO, LEFT, TOP, LESS_THAN, TOP);
509         verifyPosition(popup, R.id.anchor_middle,
510                 LEFT, LESS_THAN, LEFT, TOP, LESS_THAN, TOP);
511         verifyPosition(popup, R.id.anchor_middle_right,
512                 RIGHT, EQUAL_TO, RIGHT, TOP, LESS_THAN, TOP);
513 
514         verifyPosition(popup, R.id.anchor_lower_left,
515                 LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, BOTTOM);
516         verifyPosition(popup, R.id.anchor_lower,
517                 LEFT, LESS_THAN, LEFT, BOTTOM, EQUAL_TO, BOTTOM);
518         verifyPosition(popup, R.id.anchor_lower_right,
519                 RIGHT, EQUAL_TO, RIGHT, BOTTOM, EQUAL_TO, BOTTOM);
520     }
521 
verifyPosition(PopupWindow popup, int anchorId, int contentEdgeX, int operatorX, int anchorEdgeX, int contentEdgeY, int operatorY, int anchorEdgeY)522     private void verifyPosition(PopupWindow popup, int anchorId,
523             int contentEdgeX, int operatorX, int anchorEdgeX,
524             int contentEdgeY, int operatorY, int anchorEdgeY) throws Throwable {
525         verifyPosition(popup, mActivity.findViewById(anchorId),
526                 contentEdgeX, operatorX, anchorEdgeX,
527                 contentEdgeY, operatorY, anchorEdgeY,
528                 0, 0, Gravity.TOP | Gravity.START);
529     }
530 
verifyPosition(PopupWindow popup, int anchorId, int contentEdgeX, int operatorX, int anchorEdgeX, int contentEdgeY, int operatorY, int anchorEdgeY, int offsetX, int offsetY, int gravity)531     private void verifyPosition(PopupWindow popup, int anchorId,
532             int contentEdgeX, int operatorX, int anchorEdgeX,
533             int contentEdgeY, int operatorY, int anchorEdgeY,
534             int offsetX, int offsetY, int gravity) throws Throwable {
535         verifyPosition(popup, mActivity.findViewById(anchorId),
536                 contentEdgeX, operatorX, anchorEdgeX,
537                 contentEdgeY, operatorY, anchorEdgeY, offsetX, offsetY, gravity);
538     }
539 
verifyPosition(PopupWindow popup, View anchor, int contentEdgeX, int operatorX, int anchorEdgeX, int contentEdgeY, int operatorY, int anchorEdgeY)540     private void verifyPosition(PopupWindow popup, View anchor,
541             int contentEdgeX, int operatorX, int anchorEdgeX,
542             int contentEdgeY, int operatorY, int anchorEdgeY) throws Throwable {
543         verifyPosition(popup, anchor,
544                 contentEdgeX, operatorX, anchorEdgeX,
545                 contentEdgeY, operatorY, anchorEdgeY,
546                 0, 0, Gravity.TOP | Gravity.START);
547     }
548 
verifyPosition(PopupWindow popup, View anchor, int contentEdgeX, int operatorX, int anchorEdgeX, int contentEdgeY, int operatorY, int anchorEdgeY, int offsetX, int offsetY, int gravity)549     private void verifyPosition(PopupWindow popup, View anchor,
550             int contentEdgeX, int operatorX, int anchorEdgeX,
551             int contentEdgeY, int operatorY, int anchorEdgeY,
552             int offsetX, int offsetY, int gravity) throws Throwable {
553         final View content = popup.getContentView();
554 
555         mActivityRule.runOnUiThread(() -> popup.showAsDropDown(
556                 anchor, offsetX, offsetY, gravity));
557         mInstrumentation.waitForIdleSync();
558 
559         assertTrue(popup.isShowing());
560         verifyPositionX(content, contentEdgeX, operatorX, anchor, anchorEdgeX);
561         verifyPositionY(content, contentEdgeY, operatorY, anchor, anchorEdgeY);
562 
563         // Make sure it fits in the display frame.
564         final Rect displayFrame = new Rect();
565         anchor.getWindowVisibleDisplayFrame(displayFrame);
566         final Rect contentFrame = new Rect();
567         content.getBoundsOnScreen(contentFrame);
568         assertTrue("Content (" + contentFrame + ") extends outside display ("
569                 + displayFrame + ")", displayFrame.contains(contentFrame));
570 
571         mActivityRule.runOnUiThread(popup::dismiss);
572         mInstrumentation.waitForIdleSync();
573 
574         assertFalse(popup.isShowing());
575     }
576 
verifyPositionY(View content, int contentEdge, int flags, View anchor, int anchorEdge)577     private void verifyPositionY(View content, int contentEdge, int flags,
578             View anchor, int anchorEdge) {
579         final int[] anchorOnScreenXY = new int[2];
580         anchor.getLocationOnScreen(anchorOnScreenXY);
581         int anchorY = anchorOnScreenXY[1];
582         if ((anchorEdge & BOTTOM) == BOTTOM) {
583             anchorY += anchor.getHeight();
584         }
585 
586         final int[] contentOnScreenXY = new int[2];
587         content.getLocationOnScreen(contentOnScreenXY);
588         int contentY = contentOnScreenXY[1];
589         if ((contentEdge & BOTTOM) == BOTTOM) {
590             contentY += content.getHeight();
591         }
592 
593         assertComparison(contentY, flags, anchorY);
594     }
595 
verifyPositionX(View content, int contentEdge, int flags, View anchor, int anchorEdge)596     private void verifyPositionX(View content, int contentEdge, int flags,
597             View anchor, int anchorEdge) {
598         final int[] anchorOnScreenXY = new int[2];
599         anchor.getLocationOnScreen(anchorOnScreenXY);
600         int anchorX = anchorOnScreenXY[0];
601         if ((anchorEdge & RIGHT) == RIGHT) {
602             anchorX += anchor.getWidth();
603         }
604 
605         final int[] contentOnScreenXY = new int[2];
606         content.getLocationOnScreen(contentOnScreenXY);
607         int contentX = contentOnScreenXY[0];
608         if ((contentEdge & RIGHT) == RIGHT) {
609             contentX += content.getWidth();
610         }
611 
612         assertComparison(contentX, flags, anchorX);
613     }
614 
assertComparison(int left, int operator, int right)615     private void assertComparison(int left, int operator, int right) {
616         switch (operator) {
617             case GREATER_THAN:
618                 assertTrue(left + " <= " + right, left > right);
619                 break;
620             case LESS_THAN:
621                 assertTrue(left + " >= " + right, left < right);
622                 break;
623             case EQUAL_TO:
624                 assertTrue(left + " != " + right, left == right);
625                 break;
626         }
627     }
628 
629     @Test
testShowAtLocation()630     public void testShowAtLocation() throws Throwable {
631         int[] popupContentViewInWindowXY = new int[2];
632         int[] popupContentViewOnScreenXY = new int[2];
633         Rect containingRect = new Rect();
634 
635         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
636         // Do not attach within the decor; we will be measuring location
637         // with regard to screen coordinates.
638         mPopupWindow.setAttachedInDecor(false);
639         assertFalse(mPopupWindow.isAttachedInDecor());
640 
641         final View upperAnchor = mActivity.findViewById(R.id.anchor_upper);
642 
643         final int xOff = 10;
644         final int yOff = 21;
645         assertFalse(mPopupWindow.isShowing());
646         mPopupWindow.getContentView().getLocationInWindow(popupContentViewInWindowXY);
647         assertEquals(0, popupContentViewInWindowXY[0]);
648         assertEquals(0, popupContentViewInWindowXY[1]);
649 
650         mActivityRule.runOnUiThread(
651                 () -> mPopupWindow.showAtLocation(upperAnchor, Gravity.NO_GRAVITY, xOff, yOff));
652         mInstrumentation.waitForIdleSync();
653 
654         assertTrue(mPopupWindow.isShowing());
655         mPopupWindow.getContentView().getLocationInWindow(popupContentViewInWindowXY);
656         mPopupWindow.getContentView().getLocationOnScreen(popupContentViewOnScreenXY);
657         upperAnchor.getWindowDisplayFrame(containingRect);
658 
659         assertTrue(popupContentViewInWindowXY[0] >= 0);
660         assertTrue(popupContentViewInWindowXY[1] >= 0);
661         assertEquals(containingRect.left + popupContentViewInWindowXY[0] + xOff,
662                 popupContentViewOnScreenXY[0]);
663         assertEquals(containingRect.top + popupContentViewInWindowXY[1] + yOff,
664                 popupContentViewOnScreenXY[1]);
665 
666         dismissPopup();
667     }
668 
669     @Test
testShowAsDropDownWithOffsets()670     public void testShowAsDropDownWithOffsets() throws Throwable {
671         int[] anchorXY = new int[2];
672         int[] viewOnScreenXY = new int[2];
673         int[] viewInWindowXY = new int[2];
674 
675         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
676         final View upperAnchor = mActivity.findViewById(R.id.anchor_upper);
677         upperAnchor.getLocationOnScreen(anchorXY);
678         int height = upperAnchor.getHeight();
679 
680         final int xOff = 11;
681         final int yOff = 12;
682 
683         mActivityRule.runOnUiThread(() -> mPopupWindow.showAsDropDown(upperAnchor, xOff, yOff));
684         mInstrumentation.waitForIdleSync();
685 
686         mPopupWindow.getContentView().getLocationOnScreen(viewOnScreenXY);
687         mPopupWindow.getContentView().getLocationInWindow(viewInWindowXY);
688         assertEquals(anchorXY[0] + xOff + viewInWindowXY[0], viewOnScreenXY[0]);
689         assertEquals(anchorXY[1] + height + yOff + viewInWindowXY[1], viewOnScreenXY[1]);
690 
691         dismissPopup();
692     }
693 
694     @Test
testOverlapAnchor()695     public void testOverlapAnchor() throws Throwable {
696         int[] anchorXY = new int[2];
697         int[] viewOnScreenXY = new int[2];
698         int[] viewInWindowXY = new int[2];
699 
700         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
701         final View upperAnchor = mActivity.findViewById(R.id.anchor_upper);
702         upperAnchor.getLocationOnScreen(anchorXY);
703 
704         assertFalse(mPopupWindow.getOverlapAnchor());
705         mPopupWindow.setOverlapAnchor(true);
706         assertTrue(mPopupWindow.getOverlapAnchor());
707 
708         mActivityRule.runOnUiThread(() -> mPopupWindow.showAsDropDown(upperAnchor, 0, 0));
709         mInstrumentation.waitForIdleSync();
710 
711         mPopupWindow.getContentView().getLocationOnScreen(viewOnScreenXY);
712         mPopupWindow.getContentView().getLocationInWindow(viewInWindowXY);
713         assertEquals(anchorXY[0] + viewInWindowXY[0], viewOnScreenXY[0]);
714         assertEquals(anchorXY[1] + viewInWindowXY[1], viewOnScreenXY[1]);
715     }
716 
717     @Test
testAccessWindowLayoutType()718     public void testAccessWindowLayoutType() {
719         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
720         assertEquals(WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,
721                 mPopupWindow.getWindowLayoutType());
722         mPopupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
723         assertEquals(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL,
724                 mPopupWindow.getWindowLayoutType());
725     }
726 
727     @Test
testGetMaxAvailableHeight()728     public void testGetMaxAvailableHeight() {
729         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
730         Rect displayFrame = new Rect();
731         View anchorView = mActivity.findViewById(R.id.anchor_upper);
732         anchorView.getWindowVisibleDisplayFrame(displayFrame);
733         int displayHeight = displayFrame.height();
734         int available = displayHeight - anchorView.getHeight();
735         int maxAvailableHeight = mPopupWindow.getMaxAvailableHeight(anchorView);
736         int maxAvailableHeightIgnoringBottomDecoration =
737                 mPopupWindow.getMaxAvailableHeight(anchorView, 0, true);
738         assertTrue(maxAvailableHeight > 0);
739         assertTrue(maxAvailableHeight <= available);
740         assertTrue(maxAvailableHeightIgnoringBottomDecoration >= maxAvailableHeight);
741         assertTrue(maxAvailableHeightIgnoringBottomDecoration <= available);
742 
743         int maxAvailableHeightWithOffset = mPopupWindow.getMaxAvailableHeight(anchorView, 2);
744         assertEquals(maxAvailableHeight - 2, maxAvailableHeightWithOffset);
745 
746         maxAvailableHeightWithOffset =
747                 mPopupWindow.getMaxAvailableHeight(anchorView, maxAvailableHeight);
748         assertTrue(maxAvailableHeightWithOffset > 0);
749         assertTrue(maxAvailableHeightWithOffset <= available);
750 
751         maxAvailableHeightWithOffset =
752                 mPopupWindow.getMaxAvailableHeight(anchorView, maxAvailableHeight / 2 - 1);
753         assertTrue(maxAvailableHeightWithOffset > 0);
754         assertTrue(maxAvailableHeightWithOffset <= available);
755 
756         maxAvailableHeightWithOffset = mPopupWindow.getMaxAvailableHeight(anchorView, -1);
757         assertTrue(maxAvailableHeightWithOffset > 0);
758         assertTrue(maxAvailableHeightWithOffset <= available);
759 
760         int maxAvailableHeightWithOffsetIgnoringBottomDecoration =
761                 mPopupWindow.getMaxAvailableHeight(anchorView, 2, true);
762         assertEquals(maxAvailableHeightIgnoringBottomDecoration - 2,
763                 maxAvailableHeightWithOffsetIgnoringBottomDecoration);
764 
765         maxAvailableHeightWithOffsetIgnoringBottomDecoration =
766                 mPopupWindow.getMaxAvailableHeight(anchorView, maxAvailableHeight, true);
767         assertTrue(maxAvailableHeightWithOffsetIgnoringBottomDecoration > 0);
768         assertTrue(maxAvailableHeightWithOffsetIgnoringBottomDecoration <= available);
769 
770         maxAvailableHeightWithOffsetIgnoringBottomDecoration =
771                 mPopupWindow.getMaxAvailableHeight(anchorView, maxAvailableHeight / 2 - 1, true);
772         assertTrue(maxAvailableHeightWithOffsetIgnoringBottomDecoration > 0);
773         assertTrue(maxAvailableHeightWithOffsetIgnoringBottomDecoration <= available);
774 
775         maxAvailableHeightWithOffsetIgnoringBottomDecoration =
776                 mPopupWindow.getMaxAvailableHeight(anchorView, -1, true);
777         assertTrue(maxAvailableHeightWithOffsetIgnoringBottomDecoration > 0);
778         assertTrue(maxAvailableHeightWithOffsetIgnoringBottomDecoration <= available);
779 
780         anchorView = mActivity.findViewById(R.id.anchor_lower);
781         // On some devices the view might actually have larger size than the physical display
782         // due to chin and content will be laid out as if outside of the display. We need to use
783         // larger from the display height and the main view height.
784         available = Math.max(displayHeight,
785                 mActivity.findViewById(android.R.id.content).getHeight()) - anchorView.getHeight();
786         maxAvailableHeight = mPopupWindow.getMaxAvailableHeight(anchorView);
787         assertTrue(maxAvailableHeight > 0);
788         assertTrue(maxAvailableHeight <= available);
789 
790         anchorView = mActivity.findViewById(R.id.anchor_middle_left);
791         available = displayHeight - anchorView.getHeight()
792                 - mActivity.findViewById(R.id.anchor_upper).getHeight();
793         maxAvailableHeight = mPopupWindow.getMaxAvailableHeight(anchorView);
794         assertTrue(maxAvailableHeight > 0);
795         assertTrue(maxAvailableHeight <= available);
796     }
797 
798     @UiThreadTest
799     @Test
testDismiss()800     public void testDismiss() {
801         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
802         assertFalse(mPopupWindow.isShowing());
803         View anchorView = mActivity.findViewById(R.id.anchor_upper);
804         mPopupWindow.showAsDropDown(anchorView);
805 
806         mPopupWindow.dismiss();
807         assertFalse(mPopupWindow.isShowing());
808 
809         mPopupWindow.dismiss();
810         assertFalse(mPopupWindow.isShowing());
811     }
812 
813     @Test
testSetOnDismissListener()814     public void testSetOnDismissListener() throws Throwable {
815         mActivityRule.runOnUiThread(() -> mTextView = new TextView(mActivity));
816         mInstrumentation.waitForIdleSync();
817         mPopupWindow = new PopupWindow(mTextView);
818         mPopupWindow.setOnDismissListener(null);
819 
820         OnDismissListener onDismissListener = mock(OnDismissListener.class);
821         mPopupWindow.setOnDismissListener(onDismissListener);
822         showPopup();
823         dismissPopup();
824         verify(onDismissListener, times(1)).onDismiss();
825 
826         showPopup();
827         dismissPopup();
828         verify(onDismissListener, times(2)).onDismiss();
829 
830         mPopupWindow.setOnDismissListener(null);
831         showPopup();
832         dismissPopup();
833         verify(onDismissListener, times(2)).onDismiss();
834     }
835 
836     @Test
testUpdate()837     public void testUpdate() throws Throwable {
838         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
839         mPopupWindow.setBackgroundDrawable(null);
840         showPopup();
841 
842         mPopupWindow.setIgnoreCheekPress();
843         mPopupWindow.setFocusable(true);
844         mPopupWindow.setTouchable(false);
845         mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
846         mPopupWindow.setClippingEnabled(false);
847         mPopupWindow.setOutsideTouchable(true);
848 
849         WindowManager.LayoutParams p = (WindowManager.LayoutParams)
850                 mPopupWindow.getContentView().getRootView().getLayoutParams();
851 
852         assertEquals(0, WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES & p.flags);
853         assertEquals(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
854                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE & p.flags);
855         assertEquals(0, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE & p.flags);
856         assertEquals(0, WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH & p.flags);
857         assertEquals(0, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS & p.flags);
858         assertEquals(0, WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM & p.flags);
859 
860         mActivityRule.runOnUiThread(mPopupWindow::update);
861         mInstrumentation.waitForIdleSync();
862 
863         assertEquals(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES,
864                 WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES & p.flags);
865         assertEquals(0, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE & p.flags);
866         assertEquals(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
867                 WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE & p.flags);
868         assertEquals(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
869                 WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH & p.flags);
870         assertEquals(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
871                 WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS & p.flags);
872         assertEquals(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
873                 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM & p.flags);
874     }
875 
876     @Test
testEnterExitInterruption()877     public void testEnterExitInterruption() throws Throwable {
878         final View anchorView = mActivity.findViewById(R.id.anchor_upper);
879         verifyEnterExitTransition(
880                 () -> mPopupWindow.showAsDropDown(anchorView, 0, 0), true);
881     }
882 
883     @Test
testEnterExitTransitionAsDropDown()884     public void testEnterExitTransitionAsDropDown() throws Throwable {
885         final View anchorView = mActivity.findViewById(R.id.anchor_upper);
886         verifyEnterExitTransition(
887                 () -> mPopupWindow.showAsDropDown(anchorView, 0, 0), false);
888     }
889 
890     @Test
testEnterExitTransitionAtLocation()891     public void testEnterExitTransitionAtLocation() throws Throwable {
892         final View anchorView = mActivity.findViewById(R.id.anchor_upper);
893         verifyEnterExitTransition(
894                 () -> mPopupWindow.showAtLocation(anchorView, Gravity.BOTTOM, 0, 0), false);
895     }
896 
verifyEnterExitTransition(Runnable showRunnable, boolean showAgain)897     private void verifyEnterExitTransition(Runnable showRunnable, boolean showAgain)
898             throws Throwable {
899         TransitionListener enterListener = mock(TransitionListener.class);
900         Transition enterTransition = new BaseTransition();
901         enterTransition.addListener(enterListener);
902 
903         TransitionListener exitListener = mock(TransitionListener.class);
904         Transition exitTransition = new BaseTransition();
905         exitTransition.addListener(exitListener);
906 
907         OnDismissListener dismissListener = mock(OnDismissListener.class);
908 
909         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
910         mPopupWindow.setEnterTransition(enterTransition);
911         mPopupWindow.setExitTransition(exitTransition);
912         mPopupWindow.setOnDismissListener(dismissListener);
913 
914         mActivityRule.runOnUiThread(showRunnable);
915         mInstrumentation.waitForIdleSync();
916         verify(enterListener, times(1)).onTransitionStart(any(Transition.class));
917         verify(exitListener, never()).onTransitionStart(any(Transition.class));
918         verify(dismissListener, never()).onDismiss();
919 
920         mActivityRule.runOnUiThread(mPopupWindow::dismiss);
921 
922         int times;
923         if (showAgain) {
924             // Interrupt dismiss by calling show again, then actually dismiss.
925             mActivityRule.runOnUiThread(showRunnable);
926             mInstrumentation.waitForIdleSync();
927             mActivityRule.runOnUiThread(mPopupWindow::dismiss);
928 
929             times = 2;
930         } else {
931             times = 1;
932         }
933 
934         mInstrumentation.waitForIdleSync();
935         verify(enterListener, times(times)).onTransitionStart(any(Transition.class));
936         verify(exitListener, times(times)).onTransitionStart(any(Transition.class));
937         verify(dismissListener, times(times)).onDismiss();
938     }
939 
940     @Test
testUpdatePositionAndDimension()941     public void testUpdatePositionAndDimension() throws Throwable {
942         int[] fstXY = new int[2];
943         int[] sndXY = new int[2];
944         int[] viewInWindowXY = new int[2];
945         Rect containingRect = new Rect();
946 
947         mActivityRule.runOnUiThread(() -> {
948             mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
949             // Do not attach within the decor; we will be measuring location
950             // with regard to screen coordinates.
951             mPopupWindow.setAttachedInDecor(false);
952         });
953 
954         mInstrumentation.waitForIdleSync();
955         // Do not update if it is not shown
956         assertFalse(mPopupWindow.isShowing());
957         assertFalse(mPopupWindow.isAttachedInDecor());
958         assertEquals(WINDOW_SIZE_DP, mPopupWindow.getWidth());
959         assertEquals(WINDOW_SIZE_DP, mPopupWindow.getHeight());
960 
961         showPopup();
962         mPopupWindow.getContentView().getLocationInWindow(viewInWindowXY);
963         final View containerView = mActivity.findViewById(R.id.main_container);
964         containerView.getWindowDisplayFrame(containingRect);
965 
966         // update if it is not shown
967         mActivityRule.runOnUiThread(() -> mPopupWindow.update(80, 80));
968 
969         mInstrumentation.waitForIdleSync();
970         assertTrue(mPopupWindow.isShowing());
971         assertEquals(80, mPopupWindow.getWidth());
972         assertEquals(80, mPopupWindow.getHeight());
973 
974         // update if it is not shown
975         mActivityRule.runOnUiThread(() -> mPopupWindow.update(20, 50, 50, 50));
976 
977         mInstrumentation.waitForIdleSync();
978         assertTrue(mPopupWindow.isShowing());
979         assertEquals(50, mPopupWindow.getWidth());
980         assertEquals(50, mPopupWindow.getHeight());
981 
982         mPopupWindow.getContentView().getLocationOnScreen(fstXY);
983         assertEquals(containingRect.left + viewInWindowXY[0] + 20, fstXY[0]);
984         assertEquals(containingRect.top + viewInWindowXY[1] + 50, fstXY[1]);
985 
986         // ignore if width or height is -1
987         mActivityRule.runOnUiThread(() -> mPopupWindow.update(4, 0, -1, -1, true));
988         mInstrumentation.waitForIdleSync();
989 
990         assertTrue(mPopupWindow.isShowing());
991         assertEquals(50, mPopupWindow.getWidth());
992         assertEquals(50, mPopupWindow.getHeight());
993 
994         mPopupWindow.getContentView().getLocationOnScreen(sndXY);
995         assertEquals(containingRect.left + viewInWindowXY[0] + 4, sndXY[0]);
996         assertEquals(containingRect.top + viewInWindowXY[1], sndXY[1]);
997 
998         dismissPopup();
999     }
1000 
1001     @Test
testUpdateDimensionAndAlignAnchorView()1002     public void testUpdateDimensionAndAlignAnchorView() throws Throwable {
1003         mActivityRule.runOnUiThread(
1004                 () -> mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP,
1005                         CONTENT_SIZE_DP)));
1006         mInstrumentation.waitForIdleSync();
1007 
1008         final View anchorView = mActivity.findViewById(R.id.anchor_upper);
1009         mPopupWindow.update(anchorView, 50, 50);
1010         // Do not update if it is not shown
1011         assertFalse(mPopupWindow.isShowing());
1012         assertEquals(WINDOW_SIZE_DP, mPopupWindow.getWidth());
1013         assertEquals(WINDOW_SIZE_DP, mPopupWindow.getHeight());
1014 
1015         mActivityRule.runOnUiThread(() -> mPopupWindow.showAsDropDown(anchorView));
1016         mInstrumentation.waitForIdleSync();
1017         // update if it is shown
1018         mActivityRule.runOnUiThread(() -> mPopupWindow.update(anchorView, 50, 50));
1019         mInstrumentation.waitForIdleSync();
1020         assertTrue(mPopupWindow.isShowing());
1021         assertEquals(50, mPopupWindow.getWidth());
1022         assertEquals(50, mPopupWindow.getHeight());
1023 
1024         // ignore if width or height is -1
1025         mActivityRule.runOnUiThread(() -> mPopupWindow.update(anchorView, -1, -1));
1026         mInstrumentation.waitForIdleSync();
1027         assertTrue(mPopupWindow.isShowing());
1028         assertEquals(50, mPopupWindow.getWidth());
1029         assertEquals(50, mPopupWindow.getHeight());
1030 
1031         mActivityRule.runOnUiThread(mPopupWindow::dismiss);
1032         mInstrumentation.waitForIdleSync();
1033     }
1034 
1035     @Test
testUpdateDimensionAndAlignAnchorViewWithOffsets()1036     public void testUpdateDimensionAndAlignAnchorViewWithOffsets() throws Throwable {
1037         int[] anchorXY = new int[2];
1038         int[] viewInWindowOff = new int[2];
1039         int[] viewXY = new int[2];
1040 
1041         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
1042         final View anchorView = mActivity.findViewById(R.id.anchor_upper);
1043         // Do not update if it is not shown
1044         assertFalse(mPopupWindow.isShowing());
1045         assertEquals(WINDOW_SIZE_DP, mPopupWindow.getWidth());
1046         assertEquals(WINDOW_SIZE_DP, mPopupWindow.getHeight());
1047 
1048         showPopup();
1049         anchorView.getLocationOnScreen(anchorXY);
1050         mPopupWindow.getContentView().getLocationInWindow(viewInWindowOff);
1051 
1052         // update if it is not shown
1053         mActivityRule.runOnUiThread(() -> mPopupWindow.update(anchorView, 20, 50, 50, 50));
1054 
1055         mInstrumentation.waitForIdleSync();
1056 
1057         assertTrue(mPopupWindow.isShowing());
1058         assertEquals(50, mPopupWindow.getWidth());
1059         assertEquals(50, mPopupWindow.getHeight());
1060 
1061         mPopupWindow.getContentView().getLocationOnScreen(viewXY);
1062 
1063         // The popup should appear below and to right with an offset.
1064         assertEquals(anchorXY[0] + 20 + viewInWindowOff[0], viewXY[0]);
1065         assertEquals(anchorXY[1] + anchorView.getHeight() + 50 + viewInWindowOff[1], viewXY[1]);
1066 
1067         // ignore width and height but change location
1068         mActivityRule.runOnUiThread(() -> mPopupWindow.update(anchorView, 10, 50, -1, -1));
1069         mInstrumentation.waitForIdleSync();
1070 
1071         assertTrue(mPopupWindow.isShowing());
1072         assertEquals(50, mPopupWindow.getWidth());
1073         assertEquals(50, mPopupWindow.getHeight());
1074 
1075         mPopupWindow.getContentView().getLocationOnScreen(viewXY);
1076 
1077         // The popup should appear below and to right with an offset.
1078         assertEquals(anchorXY[0] + 10 + viewInWindowOff[0], viewXY[0]);
1079         assertEquals(anchorXY[1] + anchorView.getHeight() + 50 + viewInWindowOff[1], viewXY[1]);
1080 
1081         final View anotherView = mActivity.findViewById(R.id.anchor_middle_left);
1082         mActivityRule.runOnUiThread(() -> mPopupWindow.update(anotherView, 0, 0, 60, 60));
1083         mInstrumentation.waitForIdleSync();
1084 
1085         assertTrue(mPopupWindow.isShowing());
1086         assertEquals(60, mPopupWindow.getWidth());
1087         assertEquals(60, mPopupWindow.getHeight());
1088 
1089         int[] newXY = new int[2];
1090         anotherView.getLocationOnScreen(newXY);
1091         mPopupWindow.getContentView().getLocationOnScreen(viewXY);
1092 
1093         // The popup should appear below and to the right.
1094         assertEquals(newXY[0] + viewInWindowOff[0], viewXY[0]);
1095         assertEquals(newXY[1] + anotherView.getHeight() + viewInWindowOff[1], viewXY[1]);
1096 
1097         dismissPopup();
1098     }
1099 
1100     @Test
testAccessInputMethodMode()1101     public void testAccessInputMethodMode() {
1102         mPopupWindow = new PopupWindow(mActivity);
1103         assertEquals(0, mPopupWindow.getInputMethodMode());
1104 
1105         mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_FROM_FOCUSABLE);
1106         assertEquals(PopupWindow.INPUT_METHOD_FROM_FOCUSABLE, mPopupWindow.getInputMethodMode());
1107 
1108         mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
1109         assertEquals(PopupWindow.INPUT_METHOD_NEEDED, mPopupWindow.getInputMethodMode());
1110 
1111         mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
1112         assertEquals(PopupWindow.INPUT_METHOD_NOT_NEEDED, mPopupWindow.getInputMethodMode());
1113 
1114         mPopupWindow.setInputMethodMode(-1);
1115         assertEquals(-1, mPopupWindow.getInputMethodMode());
1116     }
1117 
1118     @Test
testAccessClippingEnabled()1119     public void testAccessClippingEnabled() {
1120         mPopupWindow = new PopupWindow(mActivity);
1121         assertTrue(mPopupWindow.isClippingEnabled());
1122 
1123         mPopupWindow.setClippingEnabled(false);
1124         assertFalse(mPopupWindow.isClippingEnabled());
1125     }
1126 
1127     @Test
testAccessOutsideTouchable()1128     public void testAccessOutsideTouchable() {
1129         mPopupWindow = new PopupWindow(mActivity);
1130         assertFalse(mPopupWindow.isOutsideTouchable());
1131 
1132         mPopupWindow.setOutsideTouchable(true);
1133         assertTrue(mPopupWindow.isOutsideTouchable());
1134     }
1135 
1136     @Test
testAccessTouchable()1137     public void testAccessTouchable() {
1138         mPopupWindow = new PopupWindow(mActivity);
1139         assertTrue(mPopupWindow.isTouchable());
1140 
1141         mPopupWindow.setTouchable(false);
1142         assertFalse(mPopupWindow.isTouchable());
1143     }
1144 
1145     @Test
testIsAboveAnchor()1146     public void testIsAboveAnchor() throws Throwable {
1147         mActivityRule.runOnUiThread(() -> mPopupWindow = createPopupWindow(createPopupContent(
1148                 CONTENT_SIZE_DP, CONTENT_SIZE_DP)));
1149         mInstrumentation.waitForIdleSync();
1150         final View upperAnchor = mActivity.findViewById(R.id.anchor_upper);
1151 
1152         mActivityRule.runOnUiThread(() -> mPopupWindow.showAsDropDown(upperAnchor));
1153         mInstrumentation.waitForIdleSync();
1154         assertFalse(mPopupWindow.isAboveAnchor());
1155         dismissPopup();
1156 
1157         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
1158         final View lowerAnchor = mActivity.findViewById(R.id.anchor_lower);
1159 
1160         mActivityRule.runOnUiThread(() -> mPopupWindow.showAsDropDown(lowerAnchor, 0, 0));
1161         mInstrumentation.waitForIdleSync();
1162         assertTrue(mPopupWindow.isAboveAnchor());
1163         dismissPopup();
1164     }
1165 
1166     @Test
testSetTouchInterceptor()1167     public void testSetTouchInterceptor() throws Throwable {
1168         mActivityRule.runOnUiThread(() -> mTextView = new TextView(mActivity));
1169         mInstrumentation.waitForIdleSync();
1170         mPopupWindow = new PopupWindow(mTextView);
1171 
1172         OnTouchListener onTouchListener = mock(OnTouchListener.class);
1173         when(onTouchListener.onTouch(any(View.class), any(MotionEvent.class))).thenReturn(true);
1174 
1175         mPopupWindow.setTouchInterceptor(onTouchListener);
1176         mPopupWindow.setFocusable(true);
1177         mPopupWindow.setOutsideTouchable(true);
1178         Drawable drawable = new ColorDrawable();
1179         mPopupWindow.setBackgroundDrawable(drawable);
1180         showPopup();
1181 
1182         int[] xy = new int[2];
1183         mPopupWindow.getContentView().getLocationOnScreen(xy);
1184         final int viewWidth = mPopupWindow.getContentView().getWidth();
1185         final int viewHeight = mPopupWindow.getContentView().getHeight();
1186         final float x = xy[0] + (viewWidth / 2.0f);
1187         float y = xy[1] + (viewHeight / 2.0f);
1188 
1189         long downTime = SystemClock.uptimeMillis();
1190         long eventTime = SystemClock.uptimeMillis();
1191         MotionEvent event = MotionEvent.obtain(downTime, eventTime,
1192                 MotionEvent.ACTION_DOWN, x, y, 0);
1193         mInstrumentation.sendPointerSync(event);
1194         verify(onTouchListener, times(1)).onTouch(any(View.class), any(MotionEvent.class));
1195 
1196         downTime = SystemClock.uptimeMillis();
1197         eventTime = SystemClock.uptimeMillis();
1198         event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
1199         mInstrumentation.sendPointerSync(event);
1200         verify(onTouchListener, times(2)).onTouch(any(View.class), any(MotionEvent.class));
1201 
1202         mPopupWindow.setTouchInterceptor(null);
1203         downTime = SystemClock.uptimeMillis();
1204         eventTime = SystemClock.uptimeMillis();
1205         event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0);
1206         mInstrumentation.sendPointerSync(event);
1207         verify(onTouchListener, times(2)).onTouch(any(View.class), any(MotionEvent.class));
1208     }
1209 
1210     @Test
testSetWindowLayoutMode()1211     public void testSetWindowLayoutMode() throws Throwable {
1212         mActivityRule.runOnUiThread(() -> mTextView = new TextView(mActivity));
1213         mInstrumentation.waitForIdleSync();
1214         mPopupWindow = new PopupWindow(mTextView);
1215         showPopup();
1216 
1217         ViewGroup.LayoutParams p = mPopupWindow.getContentView().getRootView().getLayoutParams();
1218         assertEquals(0, p.width);
1219         assertEquals(0, p.height);
1220 
1221         mPopupWindow.setWindowLayoutMode(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
1222         mActivityRule.runOnUiThread(() -> mPopupWindow.update(20, 50, 50, 50));
1223 
1224         assertEquals(LayoutParams.WRAP_CONTENT, p.width);
1225         assertEquals(LayoutParams.MATCH_PARENT, p.height);
1226     }
1227 
1228     @Test
testAccessElevation()1229     public void testAccessElevation() throws Throwable {
1230         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
1231         mActivityRule.runOnUiThread(() -> mPopupWindow.setElevation(2.0f));
1232 
1233         showPopup();
1234         assertEquals(2.0f, mPopupWindow.getElevation(), 0.0f);
1235 
1236         dismissPopup();
1237         mActivityRule.runOnUiThread(() -> mPopupWindow.setElevation(4.0f));
1238         showPopup();
1239         assertEquals(4.0f, mPopupWindow.getElevation(), 0.0f);
1240 
1241         dismissPopup();
1242         mActivityRule.runOnUiThread(() -> mPopupWindow.setElevation(10.0f));
1243         showPopup();
1244         assertEquals(10.0f, mPopupWindow.getElevation(), 0.0f);
1245     }
1246 
1247     @Test
testAccessSoftInputMode()1248     public void testAccessSoftInputMode() throws Throwable {
1249         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
1250         mActivityRule.runOnUiThread(
1251                 () -> mPopupWindow.setSoftInputMode(
1252                         WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE));
1253 
1254         showPopup();
1255         assertEquals(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE,
1256                 mPopupWindow.getSoftInputMode());
1257 
1258         dismissPopup();
1259         mActivityRule.runOnUiThread(
1260                 () -> mPopupWindow.setSoftInputMode(
1261                         WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN));
1262         showPopup();
1263         assertEquals(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN,
1264                 mPopupWindow.getSoftInputMode());
1265     }
1266 
1267     @Test
testAccessSplitTouchEnabled()1268     public void testAccessSplitTouchEnabled() throws Throwable {
1269         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
1270         mActivityRule.runOnUiThread(() -> mPopupWindow.setSplitTouchEnabled(true));
1271 
1272         showPopup();
1273         assertTrue(mPopupWindow.isSplitTouchEnabled());
1274 
1275         dismissPopup();
1276         mActivityRule.runOnUiThread(() -> mPopupWindow.setSplitTouchEnabled(false));
1277         showPopup();
1278         assertFalse(mPopupWindow.isSplitTouchEnabled());
1279 
1280         dismissPopup();
1281         mActivityRule.runOnUiThread(() -> mPopupWindow.setSplitTouchEnabled(true));
1282         showPopup();
1283         assertTrue(mPopupWindow.isSplitTouchEnabled());
1284     }
1285 
1286     @Test
testVerticallyClippedBeforeAdjusted()1287     public void testVerticallyClippedBeforeAdjusted() throws Throwable {
1288         View parentWindowView = mActivity.getWindow().getDecorView();
1289         int parentWidth = parentWindowView.getMeasuredWidth();
1290         int parentHeight = parentWindowView.getMeasuredHeight();
1291 
1292         // We make a popup which is too large to fit within the parent window.
1293         // After showing it, we verify that it is shrunk to fit the window,
1294         // rather than adjusted up.
1295         mPopupWindow = createPopupWindow(createPopupContent(parentWidth*2, parentHeight*2));
1296         mPopupWindow.setWidth(WindowManager.LayoutParams.WRAP_CONTENT);
1297         mPopupWindow.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
1298 
1299         showPopup(R.id.anchor_middle);
1300 
1301         View popupRoot = mPopupWindow.getContentView();
1302         int measuredWidth = popupRoot.getMeasuredWidth();
1303         int measuredHeight = popupRoot.getMeasuredHeight();
1304         View anchor = mActivity.findViewById(R.id.anchor_middle);
1305 
1306         // The popup should occupy all available vertical space.
1307         int[] anchorLocationInWindowXY = new int[2];
1308         anchor.getLocationInWindow(anchorLocationInWindowXY);
1309         assertEquals(measuredHeight,
1310                 parentHeight - (anchorLocationInWindowXY[1] + anchor.getHeight()));
1311 
1312         // The popup should be vertically aligned to the anchor's bottom edge.
1313         int[] anchorLocationOnScreenXY = new int[2];
1314         anchor.getLocationOnScreen(anchorLocationOnScreenXY);
1315         int[] popupLocationOnScreenXY = new int[2];
1316         popupRoot.getLocationOnScreen(popupLocationOnScreenXY);
1317         assertEquals(anchorLocationOnScreenXY[1] + anchor.getHeight(), popupLocationOnScreenXY[1]);
1318     }
1319 
1320     @Test
testClipToScreenClipsToInsets()1321     public void testClipToScreenClipsToInsets() throws Throwable {
1322         int[] orientationValues = {ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
1323                 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT};
1324         int currentOrientation = mActivity.getResources().getConfiguration().orientation;
1325         if (currentOrientation == Configuration.ORIENTATION_LANDSCAPE) {
1326             orientationValues[0] = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
1327             orientationValues[1] = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
1328         }
1329 
1330         for (int i = 0; i < 2; i++) {
1331             final int orientation = orientationValues[i];
1332             mActivity.runOnUiThread(() ->
1333                     mActivity.setRequestedOrientation(orientation));
1334             mActivity.waitForConfigurationChanged();
1335             // Wait for main thread to be idle to make sure layout and draw have been performed
1336             // before continuing.
1337             mInstrumentation.waitForIdleSync();
1338 
1339             View parentWindowView = mActivity.getWindow().getDecorView();
1340             int parentWidth = parentWindowView.getMeasuredWidth();
1341             int parentHeight = parentWindowView.getMeasuredHeight();
1342 
1343             mPopupWindow = createPopupWindow(createPopupContent(parentWidth*2, parentHeight*2));
1344             mPopupWindow.setWidth(WindowManager.LayoutParams.MATCH_PARENT);
1345             mPopupWindow.setHeight(WindowManager.LayoutParams.MATCH_PARENT);
1346             mPopupWindow.setClipToScreenEnabled(true);
1347 
1348             showPopup(R.id.anchor_upper_left);
1349 
1350             View popupRoot = mPopupWindow.getContentView().getRootView();
1351             int measuredWidth  = popupRoot.getMeasuredWidth();
1352             int measuredHeight = popupRoot.getMeasuredHeight();
1353 
1354             // The visible frame will not include the insets.
1355             Rect visibleFrame = new Rect();
1356             parentWindowView.getWindowVisibleDisplayFrame(visibleFrame);
1357 
1358             assertEquals(measuredWidth, visibleFrame.width());
1359             assertEquals(measuredHeight, visibleFrame.height());
1360         }
1361     }
1362 
1363     @Test
testPositionAfterParentScroll()1364     public void testPositionAfterParentScroll() throws Throwable {
1365         View.OnScrollChangeListener scrollChangeListener = mock(
1366                 View.OnScrollChangeListener.class);
1367 
1368         mActivityRule.runOnUiThread(() -> {
1369             mActivity.setContentView(R.layout.popup_window_scrollable);
1370 
1371             View anchor = mActivity.findViewById(R.id.anchor_upper);
1372             PopupWindow window = createPopupWindow();
1373             window.showAsDropDown(anchor);
1374         });
1375 
1376         mActivityRule.runOnUiThread(() -> {
1377             View parent = mActivity.findViewById(R.id.main_container);
1378             parent.scrollBy(0, 500);
1379             parent.setOnScrollChangeListener(scrollChangeListener);
1380         });
1381 
1382         verify(scrollChangeListener, never()).onScrollChange(
1383                 any(View.class), anyInt(), anyInt(), anyInt(), anyInt());
1384     }
1385 
1386     @Test
testPositionAfterAnchorRemoval()1387     public void testPositionAfterAnchorRemoval() throws Throwable {
1388         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
1389         showPopup(R.id.anchor_middle);
1390 
1391         final ViewGroup container = (ViewGroup) mActivity.findViewById(R.id.main_container);
1392         final View anchor = mActivity.findViewById(R.id.anchor_middle);
1393         final LayoutParams anchorLayoutParams = anchor.getLayoutParams();
1394 
1395         final int[] originalLocation = mPopupWindow.getContentView().getLocationOnScreen();
1396 
1397         final int deltaX = 30;
1398         final int deltaY = 20;
1399 
1400         // Scroll the container, the popup should move along with the anchor.
1401         WidgetTestUtils.runOnMainAndLayoutSync(
1402                 mActivityRule,
1403                 mPopupWindow.getContentView().getRootView(),
1404                 () -> container.scrollBy(deltaX, deltaY),
1405                 false  /* force layout */);
1406         // Since the first layout might have been caused by the original scroll event (and not by
1407         // the anchor change), we need to wait until all traversals are done.
1408         mInstrumentation.waitForIdleSync();
1409         assertPopupLocation(originalLocation, deltaX, deltaY);
1410 
1411         // Detach the anchor, the popup should stay in the same location.
1412         WidgetTestUtils.runOnMainAndLayoutSync(
1413                 mActivityRule,
1414                 mActivity.getWindow().getDecorView(),
1415                 () -> container.removeView(anchor),
1416                 false  /* force layout */);
1417         assertPopupLocation(originalLocation, deltaX, deltaY);
1418 
1419         // Scroll the container while the anchor is detached, the popup should not move.
1420         WidgetTestUtils.runOnMainAndLayoutSync(
1421                 mActivityRule,
1422                 mActivity.getWindow().getDecorView(),
1423                 () -> container.scrollBy(deltaX, deltaY),
1424                 true  /* force layout */);
1425         mInstrumentation.waitForIdleSync();
1426         assertPopupLocation(originalLocation, deltaX, deltaY);
1427 
1428         // Re-attach the anchor, the popup should snap back to the new anchor location.
1429         WidgetTestUtils.runOnMainAndLayoutSync(
1430                 mActivityRule,
1431                 mPopupWindow.getContentView().getRootView(),
1432                 () -> container.addView(anchor, anchorLayoutParams),
1433                 false  /* force layout */);
1434         assertPopupLocation(originalLocation, deltaX * 2, deltaY * 2);
1435     }
1436 
1437     @Test
testAnchorInPopup()1438     public void testAnchorInPopup() throws Throwable {
1439         DisplayMetrics displayMetrics = mActivity.getResources().getDisplayMetrics();
1440         float dpWidth = displayMetrics.widthPixels / displayMetrics.density;
1441         float dpHeight = displayMetrics.heightPixels / displayMetrics.density;
1442         final int minDisplaySize = 320;
1443         if (dpWidth < minDisplaySize || dpHeight < minDisplaySize) {
1444             // On smaller screens the popups that this test is creating
1445             // are not guaranteed to be properly aligned to their anchors.
1446             return;
1447         }
1448 
1449         mPopupWindow = createPopupWindow(
1450                 mActivity.getLayoutInflater().inflate(R.layout.popup_window, null));
1451 
1452         final PopupWindow subPopup =
1453                 createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
1454 
1455         // Check alignment without overlapping the anchor.
1456         assertFalse(subPopup.getOverlapAnchor());
1457 
1458         verifySubPopupPosition(subPopup, R.id.anchor_upper_left, R.id.anchor_lower_right,
1459                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM);
1460         verifySubPopupPosition(subPopup, R.id.anchor_middle_left, R.id.anchor_lower_right,
1461                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM);
1462         verifySubPopupPosition(subPopup, R.id.anchor_lower_left, R.id.anchor_lower_right,
1463                 LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP);
1464 
1465         verifySubPopupPosition(subPopup, R.id.anchor_upper, R.id.anchor_lower_right,
1466                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM);
1467         verifySubPopupPosition(subPopup, R.id.anchor_middle, R.id.anchor_lower_right,
1468                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM);
1469         verifySubPopupPosition(subPopup, R.id.anchor_lower, R.id.anchor_lower_right,
1470                 LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP);
1471 
1472         verifySubPopupPosition(subPopup, R.id.anchor_upper_right, R.id.anchor_lower_right,
1473                 RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, BOTTOM);
1474         verifySubPopupPosition(subPopup, R.id.anchor_middle_right, R.id.anchor_lower_right,
1475                 RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, BOTTOM);
1476         verifySubPopupPosition(subPopup, R.id.anchor_lower_right, R.id.anchor_lower_right,
1477                 RIGHT, EQUAL_TO, RIGHT, BOTTOM, EQUAL_TO, TOP);
1478 
1479         // Check alignment while overlapping the anchor.
1480         subPopup.setOverlapAnchor(true);
1481 
1482         final int anchorHeight = mActivity.findViewById(R.id.anchor_lower_right).getHeight();
1483         // To simplify the math assert that all three lower anchors are the same height.
1484         assertEquals(anchorHeight, mActivity.findViewById(R.id.anchor_lower_left).getHeight());
1485         assertEquals(anchorHeight, mActivity.findViewById(R.id.anchor_lower).getHeight());
1486 
1487         final int verticalSpaceBelowAnchor = anchorHeight * 2;
1488         // Ensure that the subpopup is flipped vertically.
1489         subPopup.setHeight(verticalSpaceBelowAnchor + 1);
1490 
1491         verifySubPopupPosition(subPopup, R.id.anchor_upper_left, R.id.anchor_lower_right,
1492                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP);
1493         verifySubPopupPosition(subPopup, R.id.anchor_middle_left, R.id.anchor_lower_right,
1494                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP);
1495         verifySubPopupPosition(subPopup, R.id.anchor_lower_left, R.id.anchor_lower_right,
1496                 LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP);
1497 
1498         verifySubPopupPosition(subPopup, R.id.anchor_upper, R.id.anchor_lower_right,
1499                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP);
1500         verifySubPopupPosition(subPopup, R.id.anchor_middle, R.id.anchor_lower_right,
1501                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP);
1502         verifySubPopupPosition(subPopup, R.id.anchor_lower, R.id.anchor_lower_right,
1503                 LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP);
1504 
1505         verifySubPopupPosition(subPopup, R.id.anchor_upper_right, R.id.anchor_lower_right,
1506                 RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, TOP);
1507         verifySubPopupPosition(subPopup, R.id.anchor_middle_right, R.id.anchor_lower_right,
1508                 RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, TOP);
1509         verifySubPopupPosition(subPopup, R.id.anchor_lower_right, R.id.anchor_lower_right,
1510                 RIGHT, EQUAL_TO, RIGHT, BOTTOM, EQUAL_TO, TOP);
1511 
1512         // Re-test for the bottom anchor row ensuring that the subpopup not flipped vertically.
1513         subPopup.setHeight(verticalSpaceBelowAnchor - 1);
1514 
1515         verifySubPopupPosition(subPopup, R.id.anchor_lower_left, R.id.anchor_lower_right,
1516                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP);
1517         verifySubPopupPosition(subPopup, R.id.anchor_lower, R.id.anchor_lower_right,
1518                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP);
1519         verifySubPopupPosition(subPopup, R.id.anchor_lower_right, R.id.anchor_lower_right,
1520                 RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, TOP);
1521 
1522         // Check that scrolling scrolls the sub popup along with the main popup.
1523         showPopup(R.id.anchor_middle);
1524 
1525         mActivityRule.runOnUiThread(() -> subPopup.showAsDropDown(
1526                 mPopupWindow.getContentView().findViewById(R.id.anchor_middle)));
1527         mInstrumentation.waitForIdleSync();
1528 
1529         final int[] popupLocation = mPopupWindow.getContentView().getLocationOnScreen();
1530         final int[] subPopupLocation = subPopup.getContentView().getLocationOnScreen();
1531 
1532         final int deltaX = 20;
1533         final int deltaY = 30;
1534 
1535         final ViewGroup container = (ViewGroup) mActivity.findViewById(R.id.main_container);
1536         WidgetTestUtils.runOnMainAndLayoutSync(
1537                 mActivityRule,
1538                 subPopup.getContentView().getRootView(),
1539                 () -> container.scrollBy(deltaX, deltaY),
1540                 false  /* force layout */);
1541 
1542         // Since the first layout might have been caused by the original scroll event (and not by
1543         // the anchor change), we need to wait until all traversals are done.
1544         mInstrumentation.waitForIdleSync();
1545 
1546         final int[] newPopupLocation = mPopupWindow.getContentView().getLocationOnScreen();
1547         assertEquals(popupLocation[0] - deltaX, newPopupLocation[0]);
1548         assertEquals(popupLocation[1] - deltaY, newPopupLocation[1]);
1549 
1550         final int[] newSubPopupLocation = subPopup.getContentView().getLocationOnScreen();
1551         assertEquals(subPopupLocation[0] - deltaX, newSubPopupLocation[0]);
1552         assertEquals(subPopupLocation[1] - deltaY, newSubPopupLocation[1]);
1553     }
1554 
verifySubPopupPosition(PopupWindow subPopup, int mainAnchorId, int subAnchorId, int contentEdgeX, int operatorX, int anchorEdgeX, int contentEdgeY, int operatorY, int anchorEdgeY)1555     private void verifySubPopupPosition(PopupWindow subPopup, int mainAnchorId, int subAnchorId,
1556             int contentEdgeX, int operatorX, int anchorEdgeX,
1557             int contentEdgeY, int operatorY, int anchorEdgeY) throws Throwable {
1558         showPopup(mainAnchorId);
1559         verifyPosition(subPopup, mPopupWindow.getContentView().findViewById(subAnchorId),
1560                 contentEdgeX, operatorX, anchorEdgeX, contentEdgeY, operatorY, anchorEdgeY);
1561         dismissPopup();
1562     }
1563 
assertPopupLocation(int[] originalLocation, int deltaX, int deltaY)1564     private void assertPopupLocation(int[] originalLocation, int deltaX, int deltaY) {
1565         final int[] actualLocation = mPopupWindow.getContentView().getLocationOnScreen();
1566         assertEquals(originalLocation[0] - deltaX, actualLocation[0]);
1567         assertEquals(originalLocation[1] - deltaY, actualLocation[1]);
1568     }
1569 
1570     private static class BaseTransition extends Transition {
1571         @Override
captureStartValues(TransitionValues transitionValues)1572         public void captureStartValues(TransitionValues transitionValues) {}
1573 
1574         @Override
captureEndValues(TransitionValues transitionValues)1575         public void captureEndValues(TransitionValues transitionValues) {}
1576     }
1577 
createPopupContent(int width, int height)1578     private View createPopupContent(int width, int height) {
1579         final View popupView = new View(mActivity);
1580         popupView.setLayoutParams(new ViewGroup.LayoutParams(width, height));
1581         popupView.setBackgroundColor(Color.MAGENTA);
1582 
1583         return popupView;
1584     }
1585 
createPopupWindow()1586     private PopupWindow createPopupWindow() {
1587         PopupWindow window = new PopupWindow(mActivity);
1588         window.setWidth(WINDOW_SIZE_DP);
1589         window.setHeight(WINDOW_SIZE_DP);
1590         window.setBackgroundDrawable(new ColorDrawable(Color.YELLOW));
1591         return window;
1592     }
1593 
createPopupWindow(View content)1594     private PopupWindow createPopupWindow(View content) {
1595         PopupWindow window = createPopupWindow();
1596         window.setContentView(content);
1597         return window;
1598     }
1599 
showPopup(int resourceId)1600     private void showPopup(int resourceId) throws Throwable {
1601         mActivityRule.runOnUiThread(() -> {
1602             if (mPopupWindow == null || mPopupWindow.isShowing()) {
1603                 return;
1604             }
1605             View anchor = mActivity.findViewById(resourceId);
1606             mPopupWindow.showAsDropDown(anchor);
1607             assertTrue(mPopupWindow.isShowing());
1608         });
1609         mInstrumentation.waitForIdleSync();
1610     }
1611 
showPopup()1612     private void showPopup() throws Throwable {
1613         showPopup(R.id.anchor_upper_left);
1614     }
1615 
dismissPopup()1616     private void dismissPopup() throws Throwable {
1617         mActivityRule.runOnUiThread(() -> {
1618             if (mPopupWindow == null || !mPopupWindow.isShowing()) {
1619                 return;
1620             }
1621             mPopupWindow.dismiss();
1622         });
1623         mInstrumentation.waitForIdleSync();
1624     }
1625 }
1626