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.pm.PackageManager;
36 import android.content.res.Configuration;
37 import android.graphics.Color;
38 import android.graphics.Point;
39 import android.graphics.Rect;
40 import android.graphics.drawable.ColorDrawable;
41 import android.graphics.drawable.Drawable;
42 import android.os.SystemClock;
43 import android.transition.Transition;
44 import android.transition.Transition.TransitionListener;
45 import android.transition.TransitionValues;
46 import android.util.AttributeSet;
47 import android.util.DisplayMetrics;
48 import android.view.Display;
49 import android.view.Gravity;
50 import android.view.MotionEvent;
51 import android.view.View;
52 import android.view.View.OnTouchListener;
53 import android.view.ViewGroup;
54 import android.view.ViewGroup.LayoutParams;
55 import android.view.ViewTreeObserver;
56 import android.view.WindowInsets;
57 import android.view.WindowManager;
58 import android.widget.ImageView;
59 import android.widget.PopupWindow;
60 import android.widget.PopupWindow.OnDismissListener;
61 import android.widget.TextView;
62 
63 import androidx.test.InstrumentationRegistry;
64 import androidx.test.annotation.UiThreadTest;
65 import androidx.test.filters.FlakyTest;
66 import androidx.test.filters.SmallTest;
67 import androidx.test.rule.ActivityTestRule;
68 import androidx.test.runner.AndroidJUnit4;
69 
70 import com.android.compatibility.common.util.WidgetTestUtils;
71 
72 import org.junit.Before;
73 import org.junit.Rule;
74 import org.junit.Test;
75 import org.junit.runner.RunWith;
76 import org.mockito.ArgumentCaptor;
77 
78 import java.util.concurrent.CountDownLatch;
79 import java.util.concurrent.TimeUnit;
80 
81 @FlakyTest
82 @SmallTest
83 @RunWith(AndroidJUnit4.class)
84 public class PopupWindowTest {
85     private static final int WINDOW_SIZE_DP = 50;
86     private static final int CONTENT_SIZE_DP = 30;
87     private static final boolean IGNORE_BOTTOM_DECOR = true;
88 
89     private Instrumentation mInstrumentation;
90     private Context mContext;
91     private PopupWindowCtsActivity mActivity;
92     private PopupWindow mPopupWindow;
93     private TextView mTextView;
94 
95     @Rule
96     public ActivityTestRule<PopupWindowCtsActivity> mActivityRule =
97             new ActivityTestRule<>(PopupWindowCtsActivity.class);
98 
99     @Before
setup()100     public void setup() {
101         mInstrumentation = InstrumentationRegistry.getInstrumentation();
102         mContext = InstrumentationRegistry.getContext();
103         mActivity = mActivityRule.getActivity();
104     }
105 
106     @Test
testConstructor()107     public void testConstructor() {
108         new PopupWindow(mActivity);
109 
110         new PopupWindow(mActivity, null);
111 
112         new PopupWindow(mActivity, null, android.R.attr.popupWindowStyle);
113 
114         new PopupWindow(mActivity, null, 0, android.R.style.Widget_DeviceDefault_PopupWindow);
115 
116         new PopupWindow(mActivity, null, 0, android.R.style.Widget_DeviceDefault_Light_PopupWindow);
117 
118         new PopupWindow(mActivity, null, 0, android.R.style.Widget_Material_PopupWindow);
119 
120         new PopupWindow(mActivity, null, 0, android.R.style.Widget_Material_Light_PopupWindow);
121     }
122 
123     @UiThreadTest
124     @Test
testSize()125     public void testSize() {
126         mPopupWindow = new PopupWindow();
127         assertEquals(0, mPopupWindow.getWidth());
128         assertEquals(0, mPopupWindow.getHeight());
129 
130         mPopupWindow = new PopupWindow(50, 50);
131         assertEquals(50, mPopupWindow.getWidth());
132         assertEquals(50, mPopupWindow.getHeight());
133 
134         mPopupWindow = new PopupWindow(-1, -1);
135         assertEquals(-1, mPopupWindow.getWidth());
136         assertEquals(-1, mPopupWindow.getHeight());
137 
138         TextView contentView = new TextView(mActivity);
139         mPopupWindow = new PopupWindow(contentView);
140         assertSame(contentView, mPopupWindow.getContentView());
141 
142         mPopupWindow = new PopupWindow(contentView, 0, 0);
143         assertEquals(0, mPopupWindow.getWidth());
144         assertEquals(0, mPopupWindow.getHeight());
145         assertSame(contentView, mPopupWindow.getContentView());
146 
147         mPopupWindow = new PopupWindow(contentView, 50, 50);
148         assertEquals(50, mPopupWindow.getWidth());
149         assertEquals(50, mPopupWindow.getHeight());
150         assertSame(contentView, mPopupWindow.getContentView());
151 
152         mPopupWindow = new PopupWindow(contentView, -1, -1);
153         assertEquals(-1, mPopupWindow.getWidth());
154         assertEquals(-1, mPopupWindow.getHeight());
155         assertSame(contentView, mPopupWindow.getContentView());
156 
157         mPopupWindow = new PopupWindow(contentView, 0, 0, true);
158         assertEquals(0, mPopupWindow.getWidth());
159         assertEquals(0, mPopupWindow.getHeight());
160         assertSame(contentView, mPopupWindow.getContentView());
161         assertTrue(mPopupWindow.isFocusable());
162 
163         mPopupWindow = new PopupWindow(contentView, 50, 50, false);
164         assertEquals(50, mPopupWindow.getWidth());
165         assertEquals(50, mPopupWindow.getHeight());
166         assertSame(contentView, mPopupWindow.getContentView());
167         assertFalse(mPopupWindow.isFocusable());
168 
169         mPopupWindow = new PopupWindow(contentView, -1, -1, true);
170         assertEquals(-1, mPopupWindow.getWidth());
171         assertEquals(-1, mPopupWindow.getHeight());
172         assertSame(contentView, mPopupWindow.getContentView());
173         assertTrue(mPopupWindow.isFocusable());
174     }
175 
176     @Test
testAccessEnterExitTransitions()177     public void testAccessEnterExitTransitions() {
178         PopupWindow w = new PopupWindow(mActivity, null, 0, 0);
179         assertNull(w.getEnterTransition());
180         assertNull(w.getExitTransition());
181 
182         w = new PopupWindow(mActivity, null, 0, R.style.PopupWindow_NullTransitions);
183         assertNull(w.getEnterTransition());
184         assertNull(w.getExitTransition());
185 
186         w = new PopupWindow(mActivity, null, 0, R.style.PopupWindow_CustomTransitions);
187         assertTrue(w.getEnterTransition() instanceof CustomTransition);
188         assertTrue(w.getExitTransition() instanceof CustomTransition);
189 
190         Transition enterTransition = new CustomTransition();
191         Transition exitTransition = new CustomTransition();
192         w = new PopupWindow(mActivity, null, 0, 0);
193         w.setEnterTransition(enterTransition);
194         w.setExitTransition(exitTransition);
195         assertEquals(enterTransition, w.getEnterTransition());
196         assertEquals(exitTransition, w.getExitTransition());
197 
198         w.setEnterTransition(null);
199         w.setExitTransition(null);
200         assertNull(w.getEnterTransition());
201         assertNull(w.getExitTransition());
202     }
203 
204     public static class CustomTransition extends Transition {
CustomTransition()205         public CustomTransition() {
206         }
207 
208         // This constructor is needed for reflection-based creation of a transition when
209         // the transition is defined in layout XML via attribute.
210         @SuppressWarnings("unused")
CustomTransition(Context context, AttributeSet attrs)211         public CustomTransition(Context context, AttributeSet attrs) {
212             super(context, attrs);
213         }
214 
215         @Override
captureStartValues(TransitionValues transitionValues)216         public void captureStartValues(TransitionValues transitionValues) {}
217 
218         @Override
captureEndValues(TransitionValues transitionValues)219         public void captureEndValues(TransitionValues transitionValues) {}
220     }
221 
222     @Test
testAccessBackground()223     public void testAccessBackground() {
224         mPopupWindow = new PopupWindow(mActivity);
225 
226         Drawable drawable = new ColorDrawable();
227         mPopupWindow.setBackgroundDrawable(drawable);
228         assertSame(drawable, mPopupWindow.getBackground());
229 
230         mPopupWindow.setBackgroundDrawable(null);
231         assertNull(mPopupWindow.getBackground());
232     }
233 
234     @Test
testAccessAnimationStyle()235     public void testAccessAnimationStyle() {
236         mPopupWindow = new PopupWindow(mActivity);
237         // default is -1
238         assertEquals(-1, mPopupWindow.getAnimationStyle());
239 
240         mPopupWindow.setAnimationStyle(android.R.style.Animation_Toast);
241         assertEquals(android.R.style.Animation_Toast,
242                 mPopupWindow.getAnimationStyle());
243 
244         // abnormal values
245         mPopupWindow.setAnimationStyle(-100);
246         assertEquals(-100, mPopupWindow.getAnimationStyle());
247     }
248 
249     @Test
testAccessContentView()250     public void testAccessContentView() throws Throwable {
251         mPopupWindow = new PopupWindow(mActivity);
252         assertNull(mPopupWindow.getContentView());
253 
254         mActivityRule.runOnUiThread(() -> mTextView = new TextView(mActivity));
255         mInstrumentation.waitForIdleSync();
256         mPopupWindow.setContentView(mTextView);
257         assertSame(mTextView, mPopupWindow.getContentView());
258 
259         mPopupWindow.setContentView(null);
260         assertNull(mPopupWindow.getContentView());
261 
262         // can not set the content if the old content is shown
263         mPopupWindow.setContentView(mTextView);
264         assertFalse(mPopupWindow.isShowing());
265         showPopup();
266         ImageView img = new ImageView(mActivity);
267         assertTrue(mPopupWindow.isShowing());
268         mPopupWindow.setContentView(img);
269         assertSame(mTextView, mPopupWindow.getContentView());
270         dismissPopup();
271     }
272 
273     @Test
testAccessFocusable()274     public void testAccessFocusable() {
275         mPopupWindow = new PopupWindow(mActivity);
276         assertFalse(mPopupWindow.isFocusable());
277 
278         mPopupWindow.setFocusable(true);
279         assertTrue(mPopupWindow.isFocusable());
280 
281         mPopupWindow.setFocusable(false);
282         assertFalse(mPopupWindow.isFocusable());
283     }
284 
285     @Test
testAccessHeight()286     public void testAccessHeight() {
287         mPopupWindow = new PopupWindow(mActivity);
288         assertEquals(WindowManager.LayoutParams.WRAP_CONTENT, mPopupWindow.getHeight());
289 
290         int height = getDisplay().getHeight() / 2;
291         mPopupWindow.setHeight(height);
292         assertEquals(height, mPopupWindow.getHeight());
293 
294         height = getDisplay().getHeight();
295         mPopupWindow.setHeight(height);
296         assertEquals(height, mPopupWindow.getHeight());
297 
298         mPopupWindow.setHeight(0);
299         assertEquals(0, mPopupWindow.getHeight());
300 
301         height = getDisplay().getHeight() * 2;
302         mPopupWindow.setHeight(height);
303         assertEquals(height, mPopupWindow.getHeight());
304 
305         height = -getDisplay().getHeight() / 2;
306         mPopupWindow.setHeight(height);
307         assertEquals(height, mPopupWindow.getHeight());
308     }
309 
310     /**
311      * Gets the display.
312      *
313      * @return the display
314      */
getDisplay()315     private Display getDisplay() {
316         WindowManager wm = (WindowManager) mActivity.getSystemService(Context.WINDOW_SERVICE);
317         return wm.getDefaultDisplay();
318     }
319 
320     @Test
testAccessWidth()321     public void testAccessWidth() {
322         mPopupWindow = new PopupWindow(mActivity);
323         assertEquals(WindowManager.LayoutParams.WRAP_CONTENT, mPopupWindow.getWidth());
324 
325         int width = getDisplay().getWidth() / 2;
326         mPopupWindow.setWidth(width);
327         assertEquals(width, mPopupWindow.getWidth());
328 
329         width = getDisplay().getWidth();
330         mPopupWindow.setWidth(width);
331         assertEquals(width, mPopupWindow.getWidth());
332 
333         mPopupWindow.setWidth(0);
334         assertEquals(0, mPopupWindow.getWidth());
335 
336         width = getDisplay().getWidth() * 2;
337         mPopupWindow.setWidth(width);
338         assertEquals(width, mPopupWindow.getWidth());
339 
340         width = - getDisplay().getWidth() / 2;
341         mPopupWindow.setWidth(width);
342         assertEquals(width, mPopupWindow.getWidth());
343     }
344 
345     private static final int TOP = 0x00;
346     private static final int BOTTOM = 0x01;
347 
348     private static final int LEFT = 0x00;
349     private static final int RIGHT = 0x01;
350 
351     private static final int GREATER_THAN = 1;
352     private static final int LESS_THAN = -1;
353     private static final int EQUAL_TO = 0;
354 
355     @Test
testShowAsDropDown()356     public void testShowAsDropDown() throws Throwable {
357         final PopupWindow popup = createPopupWindow(createPopupContent(CONTENT_SIZE_DP,
358                 CONTENT_SIZE_DP));
359         popup.setIsClippedToScreen(false);
360         popup.setOverlapAnchor(false);
361         popup.setAnimationStyle(0);
362         popup.setExitTransition(null);
363         popup.setEnterTransition(null);
364 
365         verifyPosition(popup, R.id.anchor_upper_left,
366                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM);
367         verifyPosition(popup, R.id.anchor_upper,
368                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM);
369         verifyPosition(popup, R.id.anchor_upper_right,
370                 RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, BOTTOM);
371 
372         verifyPosition(popup, R.id.anchor_middle_left,
373                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM);
374         verifyPosition(popup, R.id.anchor_middle,
375                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM);
376         verifyPosition(popup, R.id.anchor_middle_right,
377                 RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, BOTTOM);
378 
379         verifyPosition(popup, R.id.anchor_lower_left,
380                 LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP);
381         verifyPosition(popup, R.id.anchor_lower,
382                 LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP);
383         verifyPosition(popup, R.id.anchor_lower_right,
384                 RIGHT, EQUAL_TO, RIGHT, BOTTOM, EQUAL_TO, TOP);
385     }
386 
387     @Test
testShowAsDropDown_ClipToScreen()388     public void testShowAsDropDown_ClipToScreen() throws Throwable {
389         final PopupWindow popup = createPopupWindow(createPopupContent(CONTENT_SIZE_DP,
390                 CONTENT_SIZE_DP));
391         popup.setIsClippedToScreen(true);
392         popup.setOverlapAnchor(false);
393         popup.setAnimationStyle(0);
394         popup.setExitTransition(null);
395         popup.setEnterTransition(null);
396 
397         verifyPosition(popup, R.id.anchor_upper_left,
398                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM);
399         verifyPosition(popup, R.id.anchor_upper,
400                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM);
401         verifyPosition(popup, R.id.anchor_upper_right,
402                 RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, BOTTOM);
403 
404         verifyPosition(popup, R.id.anchor_middle_left,
405                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM);
406         verifyPosition(popup, R.id.anchor_middle,
407                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM);
408         verifyPosition(popup, R.id.anchor_middle_right,
409                 RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, BOTTOM);
410 
411         verifyPosition(popup, R.id.anchor_lower_left,
412                 LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP);
413         verifyPosition(popup, R.id.anchor_lower,
414                 LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP);
415         verifyPosition(popup, R.id.anchor_lower_right,
416                 RIGHT, EQUAL_TO, RIGHT, BOTTOM, EQUAL_TO, TOP);
417     }
418 
419     @Test
testShowAsDropDown_ClipToScreen_Overlap()420     public void testShowAsDropDown_ClipToScreen_Overlap() throws Throwable {
421         final PopupWindow popup = createPopupWindow(createPopupContent(CONTENT_SIZE_DP,
422                 CONTENT_SIZE_DP));
423         popup.setIsClippedToScreen(true);
424         popup.setOverlapAnchor(true);
425         popup.setAnimationStyle(0);
426         popup.setExitTransition(null);
427         popup.setEnterTransition(null);
428 
429         verifyPosition(popup, R.id.anchor_upper_left,
430                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP);
431         verifyPosition(popup, R.id.anchor_upper,
432                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP);
433         verifyPosition(popup, R.id.anchor_upper_right,
434                 RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, TOP);
435 
436         verifyPosition(popup, R.id.anchor_middle_left,
437                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP);
438         verifyPosition(popup, R.id.anchor_middle,
439                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP);
440         verifyPosition(popup, R.id.anchor_middle_right,
441                 RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, TOP);
442 
443         verifyPosition(popup, R.id.anchor_lower_left,
444                 LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP);
445         verifyPosition(popup, R.id.anchor_lower,
446                 LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP);
447         verifyPosition(popup, R.id.anchor_lower_right,
448                 RIGHT, EQUAL_TO, RIGHT, BOTTOM, EQUAL_TO, TOP);
449     }
450 
451     @Test
testShowAsDropDown_ClipToScreen_Overlap_Offset()452     public void testShowAsDropDown_ClipToScreen_Overlap_Offset() throws Throwable {
453         final PopupWindow popup = createPopupWindow(createPopupContent(CONTENT_SIZE_DP,
454                 CONTENT_SIZE_DP));
455         popup.setIsClippedToScreen(true);
456         popup.setOverlapAnchor(true);
457         popup.setAnimationStyle(0);
458         popup.setExitTransition(null);
459         popup.setEnterTransition(null);
460 
461         final int offsetX = mActivity.findViewById(R.id.anchor_upper).getWidth() / 2;
462         final int offsetY = mActivity.findViewById(R.id.anchor_upper).getHeight() / 2;
463         final int gravity = Gravity.TOP | Gravity.START;
464 
465         verifyPosition(popup, R.id.anchor_upper_left,
466                 LEFT, GREATER_THAN, LEFT, TOP, GREATER_THAN, TOP,
467                 offsetX, offsetY, gravity);
468         verifyPosition(popup, R.id.anchor_upper,
469                 LEFT, GREATER_THAN, LEFT, TOP, GREATER_THAN, TOP,
470                 offsetX, offsetY, gravity);
471         verifyPosition(popup, R.id.anchor_upper_right,
472                 RIGHT, EQUAL_TO, RIGHT, TOP, GREATER_THAN, TOP,
473                 offsetX, offsetY, gravity);
474 
475         verifyPosition(popup, R.id.anchor_middle_left,
476                 LEFT, GREATER_THAN, LEFT, TOP, GREATER_THAN, TOP,
477                 offsetX, offsetY, gravity);
478         verifyPosition(popup, R.id.anchor_middle,
479                 LEFT, GREATER_THAN, LEFT, TOP, GREATER_THAN, TOP,
480                 offsetX, offsetY, gravity);
481         verifyPosition(popup, R.id.anchor_middle_right,
482                 RIGHT, EQUAL_TO, RIGHT, TOP, GREATER_THAN, TOP,
483                 offsetX, offsetY, gravity);
484 
485         verifyPosition(popup, R.id.anchor_lower_left,
486                 LEFT, GREATER_THAN, LEFT, BOTTOM, LESS_THAN, BOTTOM,
487                 offsetX, offsetY, gravity);
488         verifyPosition(popup, R.id.anchor_lower,
489                 LEFT, GREATER_THAN, LEFT, BOTTOM, LESS_THAN, BOTTOM,
490                 offsetX, offsetY, gravity);
491         verifyPosition(popup, R.id.anchor_lower_right,
492                 RIGHT, EQUAL_TO, RIGHT, BOTTOM, LESS_THAN, BOTTOM,
493                 offsetX, offsetY, gravity);
494     }
495 
496     @Test
testShowAsDropDown_ClipToScreen_TooBig()497     public void testShowAsDropDown_ClipToScreen_TooBig() throws Throwable {
498         final View rootView = mActivity.findViewById(R.id.anchor_upper_left).getRootView();
499         final int width = rootView.getWidth() * 2;
500         final int height = rootView.getHeight() * 2;
501 
502         final PopupWindow popup = createPopupWindow(createPopupContent(width, height));
503         popup.setWidth(width);
504         popup.setHeight(height);
505 
506         popup.setIsClippedToScreen(true);
507         popup.setOverlapAnchor(false);
508         popup.setAnimationStyle(0);
509         popup.setExitTransition(null);
510         popup.setEnterTransition(null);
511 
512         verifyPosition(popup, R.id.anchor_upper_left,
513                 LEFT, EQUAL_TO, LEFT, TOP, LESS_THAN, TOP);
514         verifyPosition(popup, R.id.anchor_upper,
515                 LEFT, LESS_THAN, LEFT, TOP, LESS_THAN, TOP);
516         verifyPosition(popup, R.id.anchor_upper_right,
517                 RIGHT, EQUAL_TO, RIGHT, TOP, LESS_THAN, TOP);
518 
519         verifyPosition(popup, R.id.anchor_middle_left,
520                 LEFT, EQUAL_TO, LEFT, TOP, LESS_THAN, TOP);
521         verifyPosition(popup, R.id.anchor_middle,
522                 LEFT, LESS_THAN, LEFT, TOP, LESS_THAN, TOP);
523         verifyPosition(popup, R.id.anchor_middle_right,
524                 RIGHT, EQUAL_TO, RIGHT, TOP, LESS_THAN, TOP);
525 
526         verifyPosition(popup, R.id.anchor_lower_left,
527                 LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, BOTTOM);
528         verifyPosition(popup, R.id.anchor_lower,
529                 LEFT, LESS_THAN, LEFT, BOTTOM, EQUAL_TO, BOTTOM);
530         verifyPosition(popup, R.id.anchor_lower_right,
531                 RIGHT, EQUAL_TO, RIGHT, BOTTOM, EQUAL_TO, BOTTOM);
532     }
533 
verifyPosition(PopupWindow popup, int anchorId, int contentEdgeX, int operatorX, int anchorEdgeX, int contentEdgeY, int operatorY, int anchorEdgeY)534     private void verifyPosition(PopupWindow popup, int anchorId,
535             int contentEdgeX, int operatorX, int anchorEdgeX,
536             int contentEdgeY, int operatorY, int anchorEdgeY) throws Throwable {
537         verifyPosition(popup, mActivity.findViewById(anchorId),
538                 contentEdgeX, operatorX, anchorEdgeX,
539                 contentEdgeY, operatorY, anchorEdgeY,
540                 0, 0, Gravity.TOP | Gravity.START);
541     }
542 
verifyPosition(PopupWindow popup, int anchorId, int contentEdgeX, int operatorX, int anchorEdgeX, int contentEdgeY, int operatorY, int anchorEdgeY, int offsetX, int offsetY, int gravity)543     private void verifyPosition(PopupWindow popup, int anchorId,
544             int contentEdgeX, int operatorX, int anchorEdgeX,
545             int contentEdgeY, int operatorY, int anchorEdgeY,
546             int offsetX, int offsetY, int gravity) throws Throwable {
547         verifyPosition(popup, mActivity.findViewById(anchorId),
548                 contentEdgeX, operatorX, anchorEdgeX,
549                 contentEdgeY, operatorY, anchorEdgeY, offsetX, offsetY, gravity);
550     }
551 
verifyPosition(PopupWindow popup, View anchor, int contentEdgeX, int operatorX, int anchorEdgeX, int contentEdgeY, int operatorY, int anchorEdgeY)552     private void verifyPosition(PopupWindow popup, View anchor,
553             int contentEdgeX, int operatorX, int anchorEdgeX,
554             int contentEdgeY, int operatorY, int anchorEdgeY) throws Throwable {
555         verifyPosition(popup, anchor,
556                 contentEdgeX, operatorX, anchorEdgeX,
557                 contentEdgeY, operatorY, anchorEdgeY,
558                 0, 0, Gravity.TOP | Gravity.START);
559     }
560 
verifyPosition(PopupWindow popup, View anchor, int contentEdgeX, int operatorX, int anchorEdgeX, int contentEdgeY, int operatorY, int anchorEdgeY, int offsetX, int offsetY, int gravity)561     private void verifyPosition(PopupWindow popup, View anchor,
562             int contentEdgeX, int operatorX, int anchorEdgeX,
563             int contentEdgeY, int operatorY, int anchorEdgeY,
564             int offsetX, int offsetY, int gravity) throws Throwable {
565         final View content = popup.getContentView();
566 
567         mActivityRule.runOnUiThread(() -> popup.showAsDropDown(
568                 anchor, offsetX, offsetY, gravity));
569         mInstrumentation.waitForIdleSync();
570 
571         assertTrue(popup.isShowing());
572         verifyPositionX(content, contentEdgeX, operatorX, anchor, anchorEdgeX);
573         verifyPositionY(content, contentEdgeY, operatorY, anchor, anchorEdgeY);
574 
575         // Make sure it fits in the display frame.
576         final Rect displayFrame = new Rect();
577         anchor.getWindowVisibleDisplayFrame(displayFrame);
578         final Rect contentFrame = new Rect();
579         content.getBoundsOnScreen(contentFrame);
580         assertTrue("Content (" + contentFrame + ") extends outside display ("
581                 + displayFrame + ")", displayFrame.contains(contentFrame));
582 
583         mActivityRule.runOnUiThread(popup::dismiss);
584         mInstrumentation.waitForIdleSync();
585 
586         assertFalse(popup.isShowing());
587     }
588 
verifyPositionY(View content, int contentEdge, int flags, View anchor, int anchorEdge)589     private void verifyPositionY(View content, int contentEdge, int flags,
590             View anchor, int anchorEdge) {
591         final int[] anchorOnScreenXY = new int[2];
592         anchor.getLocationOnScreen(anchorOnScreenXY);
593         int anchorY = anchorOnScreenXY[1];
594         if ((anchorEdge & BOTTOM) == BOTTOM) {
595             anchorY += anchor.getHeight();
596         }
597 
598         final int[] contentOnScreenXY = new int[2];
599         content.getLocationOnScreen(contentOnScreenXY);
600         int contentY = contentOnScreenXY[1];
601         if ((contentEdge & BOTTOM) == BOTTOM) {
602             contentY += content.getHeight();
603         }
604 
605         assertComparison(contentY, flags, anchorY);
606     }
607 
verifyPositionX(View content, int contentEdge, int flags, View anchor, int anchorEdge)608     private void verifyPositionX(View content, int contentEdge, int flags,
609             View anchor, int anchorEdge) {
610         final int[] anchorOnScreenXY = new int[2];
611         anchor.getLocationOnScreen(anchorOnScreenXY);
612         int anchorX = anchorOnScreenXY[0];
613         if ((anchorEdge & RIGHT) == RIGHT) {
614             anchorX += anchor.getWidth();
615         }
616 
617         final int[] contentOnScreenXY = new int[2];
618         content.getLocationOnScreen(contentOnScreenXY);
619         int contentX = contentOnScreenXY[0];
620         if ((contentEdge & RIGHT) == RIGHT) {
621             contentX += content.getWidth();
622         }
623 
624         assertComparison(contentX, flags, anchorX);
625     }
626 
assertComparison(int left, int operator, int right)627     private void assertComparison(int left, int operator, int right) {
628         switch (operator) {
629             case GREATER_THAN:
630                 assertTrue(left + " <= " + right, left > right);
631                 break;
632             case LESS_THAN:
633                 assertTrue(left + " >= " + right, left < right);
634                 break;
635             case EQUAL_TO:
636                 assertTrue(left + " != " + right, left == right);
637                 break;
638         }
639     }
640 
641     @Test
testShowAtLocation()642     public void testShowAtLocation() throws Throwable {
643         int[] popupContentViewInWindowXY = new int[2];
644         int[] popupContentViewOnScreenXY = new int[2];
645         Rect containingRect = new Rect();
646 
647         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
648         // Do not attach within the decor; we will be measuring location
649         // with regard to screen coordinates.
650         mPopupWindow.setAttachedInDecor(false);
651         assertFalse(mPopupWindow.isAttachedInDecor());
652 
653         final View upperAnchor = mActivity.findViewById(R.id.anchor_upper);
654         final WindowInsets windowInsets = upperAnchor.getRootWindowInsets();
655         final int xOff = windowInsets.getSystemWindowInsetLeft() + 10;
656         final int yOff = windowInsets.getSystemWindowInsetTop() + 21;
657         assertFalse(mPopupWindow.isShowing());
658         mPopupWindow.getContentView().getLocationInWindow(popupContentViewInWindowXY);
659         assertEquals(0, popupContentViewInWindowXY[0]);
660         assertEquals(0, popupContentViewInWindowXY[1]);
661 
662         mActivityRule.runOnUiThread(
663                 () -> mPopupWindow.showAtLocation(upperAnchor, Gravity.NO_GRAVITY, xOff, yOff));
664         mInstrumentation.waitForIdleSync();
665 
666         assertTrue(mPopupWindow.isShowing());
667         mPopupWindow.getContentView().getLocationInWindow(popupContentViewInWindowXY);
668         mPopupWindow.getContentView().getLocationOnScreen(popupContentViewOnScreenXY);
669         upperAnchor.getWindowDisplayFrame(containingRect);
670 
671         assertTrue(popupContentViewInWindowXY[0] >= 0);
672         assertTrue(popupContentViewInWindowXY[1] >= 0);
673         assertEquals(containingRect.left + popupContentViewInWindowXY[0] + xOff, popupContentViewOnScreenXY[0]);
674         assertEquals(containingRect.top + popupContentViewInWindowXY[1] + yOff, popupContentViewOnScreenXY[1]);
675 
676         dismissPopup();
677     }
678 
679     @Test
testShowAsDropDownWithOffsets()680     public void testShowAsDropDownWithOffsets() throws Throwable {
681         int[] anchorXY = new int[2];
682         int[] viewOnScreenXY = new int[2];
683         int[] viewInWindowXY = new int[2];
684 
685         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
686         final View upperAnchor = mActivity.findViewById(R.id.anchor_upper);
687         upperAnchor.getLocationOnScreen(anchorXY);
688         int height = upperAnchor.getHeight();
689 
690         final int xOff = 11;
691         final int yOff = 12;
692 
693         mActivityRule.runOnUiThread(() -> mPopupWindow.showAsDropDown(upperAnchor, xOff, yOff));
694         mInstrumentation.waitForIdleSync();
695 
696         mPopupWindow.getContentView().getLocationOnScreen(viewOnScreenXY);
697         mPopupWindow.getContentView().getLocationInWindow(viewInWindowXY);
698         assertEquals(anchorXY[0] + xOff + viewInWindowXY[0], viewOnScreenXY[0]);
699         assertEquals(anchorXY[1] + height + yOff + viewInWindowXY[1], viewOnScreenXY[1]);
700 
701         dismissPopup();
702     }
703 
704     @Test
testOverlapAnchor()705     public void testOverlapAnchor() throws Throwable {
706         int[] anchorXY = new int[2];
707         int[] viewOnScreenXY = new int[2];
708         int[] viewInWindowXY = new int[2];
709 
710         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
711         final View upperAnchor = mActivity.findViewById(R.id.anchor_upper);
712         upperAnchor.getLocationOnScreen(anchorXY);
713 
714         assertFalse(mPopupWindow.getOverlapAnchor());
715         mPopupWindow.setOverlapAnchor(true);
716         assertTrue(mPopupWindow.getOverlapAnchor());
717 
718         mActivityRule.runOnUiThread(() -> mPopupWindow.showAsDropDown(upperAnchor, 0, 0));
719         mInstrumentation.waitForIdleSync();
720 
721         mPopupWindow.getContentView().getLocationOnScreen(viewOnScreenXY);
722         mPopupWindow.getContentView().getLocationInWindow(viewInWindowXY);
723         assertEquals(anchorXY[0] + viewInWindowXY[0], viewOnScreenXY[0]);
724         assertEquals(anchorXY[1] + viewInWindowXY[1], viewOnScreenXY[1]);
725     }
726 
727     @Test
testAccessWindowLayoutType()728     public void testAccessWindowLayoutType() {
729         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
730         assertEquals(WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,
731                 mPopupWindow.getWindowLayoutType());
732         mPopupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
733         assertEquals(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL,
734                 mPopupWindow.getWindowLayoutType());
735     }
736 
737     // TODO: Remove this test as it is now broken down into individual tests.
738     @Test
testGetMaxAvailableHeight()739     public void testGetMaxAvailableHeight() {
740         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
741 
742         final View upperAnchorView = mActivity.findViewById(R.id.anchor_upper);
743         final Rect visibleDisplayFrame = getVisibleDisplayFrame(upperAnchorView);
744         final Rect displayFrame = getDisplayFrame(upperAnchorView);
745 
746         final int bottomDecorationHeight = displayFrame.bottom - visibleDisplayFrame.bottom;
747         final int availableBelowTopAnchor =
748                 visibleDisplayFrame.bottom - getViewBottom(upperAnchorView);
749         final int availableAboveTopAnchor = getLoc(upperAnchorView).y - visibleDisplayFrame.top;
750 
751         final int maxAvailableHeight = mPopupWindow.getMaxAvailableHeight(upperAnchorView);
752         final int maxAvailableHeightIgnoringBottomDecoration =
753                 mPopupWindow.getMaxAvailableHeight(upperAnchorView, 0, IGNORE_BOTTOM_DECOR);
754         assertTrue(maxAvailableHeight > 0);
755         assertTrue(maxAvailableHeight <= availableBelowTopAnchor);
756         assertTrue(maxAvailableHeightIgnoringBottomDecoration >= maxAvailableHeight);
757         assertTrue(maxAvailableHeightIgnoringBottomDecoration
758                 <= availableBelowTopAnchor + bottomDecorationHeight);
759 
760         final int maxAvailableHeightWithOffset2 =
761                 mPopupWindow.getMaxAvailableHeight(upperAnchorView, 2);
762         assertEquals(maxAvailableHeight - 2, maxAvailableHeightWithOffset2);
763 
764         final int maxOffset = maxAvailableHeight;
765 
766         final int maxAvailableHeightWithMaxOffset =
767                 mPopupWindow.getMaxAvailableHeight(upperAnchorView, maxOffset);
768         assertTrue(maxAvailableHeightWithMaxOffset > 0);
769         assertTrue(maxAvailableHeightWithMaxOffset <= availableAboveTopAnchor + maxOffset);
770 
771         final int maxAvailableHeightWithHalfMaxOffset =
772                 mPopupWindow.getMaxAvailableHeight(upperAnchorView, maxOffset / 2);
773         assertTrue(maxAvailableHeightWithHalfMaxOffset > 0);
774         assertTrue(maxAvailableHeightWithHalfMaxOffset <= availableBelowTopAnchor);
775         assertTrue(maxAvailableHeightWithHalfMaxOffset
776                         <= Math.max(
777                                 availableAboveTopAnchor + maxOffset / 2,
778                                 availableBelowTopAnchor - maxOffset / 2));
779 
780         // TODO(b/136178425): A negative offset can return a size that is larger than the display.
781         final int maxAvailableHeightWithNegativeOffset =
782                 mPopupWindow.getMaxAvailableHeight(upperAnchorView, -1);
783         assertTrue(maxAvailableHeightWithNegativeOffset > 0);
784         assertTrue(maxAvailableHeightWithNegativeOffset <= availableBelowTopAnchor + 1);
785 
786         final int maxAvailableHeightWithOffset2IgnoringBottomDecoration =
787                 mPopupWindow.getMaxAvailableHeight(upperAnchorView, 2, IGNORE_BOTTOM_DECOR);
788         assertEquals(maxAvailableHeightIgnoringBottomDecoration - 2,
789                 maxAvailableHeightWithOffset2IgnoringBottomDecoration);
790 
791         final int maxAvailableHeightWithMaxOffsetIgnoringBottomDecoration =
792                 mPopupWindow.getMaxAvailableHeight(upperAnchorView, maxOffset, IGNORE_BOTTOM_DECOR);
793         assertTrue(maxAvailableHeightWithMaxOffsetIgnoringBottomDecoration > 0);
794         assertTrue(maxAvailableHeightWithMaxOffsetIgnoringBottomDecoration
795                 <= availableAboveTopAnchor + maxOffset);
796 
797         final int maxAvailableHeightWithHalfOffsetIgnoringBottomDecoration =
798                 mPopupWindow.getMaxAvailableHeight(
799                         upperAnchorView,
800                         maxOffset / 2,
801                         IGNORE_BOTTOM_DECOR);
802         assertTrue(maxAvailableHeightWithHalfOffsetIgnoringBottomDecoration > 0);
803         assertTrue(maxAvailableHeightWithHalfOffsetIgnoringBottomDecoration
804                 <= Math.max(
805                         availableAboveTopAnchor + maxOffset / 2,
806                         availableBelowTopAnchor + bottomDecorationHeight - maxOffset / 2));
807 
808         final int maxAvailableHeightWithOffsetIgnoringBottomDecoration =
809                 mPopupWindow.getMaxAvailableHeight(upperAnchorView, 0, IGNORE_BOTTOM_DECOR);
810         assertTrue(maxAvailableHeightWithOffsetIgnoringBottomDecoration > 0);
811         assertTrue(maxAvailableHeightWithOffsetIgnoringBottomDecoration
812                 <= availableBelowTopAnchor + bottomDecorationHeight);
813 
814         final View lowerAnchorView = mActivity.findViewById(R.id.anchor_lower);
815         final int availableAboveLowerAnchor = getLoc(lowerAnchorView).y - visibleDisplayFrame.top;
816         final int maxAvailableHeightLowerAnchor =
817                 mPopupWindow.getMaxAvailableHeight(lowerAnchorView);
818         assertTrue(maxAvailableHeightLowerAnchor > 0);
819         assertTrue(maxAvailableHeightLowerAnchor <= availableAboveLowerAnchor);
820 
821         final View middleAnchorView = mActivity.findViewById(R.id.anchor_middle_left);
822         final int availableAboveMiddleAnchor = getLoc(middleAnchorView).y - visibleDisplayFrame.top;
823         final int availableBelowMiddleAnchor =
824                 visibleDisplayFrame.bottom - getViewBottom(middleAnchorView);
825         final int maxAvailableHeightMiddleAnchor =
826                 mPopupWindow.getMaxAvailableHeight(middleAnchorView);
827         assertTrue(maxAvailableHeightMiddleAnchor > 0);
828         assertTrue(maxAvailableHeightMiddleAnchor
829                 <= Math.max(availableAboveMiddleAnchor, availableBelowMiddleAnchor));
830     }
831 
832     @Test
testGetMaxAvailableHeight_topAnchor()833     public void testGetMaxAvailableHeight_topAnchor() {
834         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
835         final View anchorView = mActivity.findViewById(R.id.anchor_upper);
836 
837         final int expected = getVisibleDisplayFrame(anchorView).bottom - getViewBottom(anchorView);
838         final int actual = mPopupWindow.getMaxAvailableHeight(anchorView);
839 
840         assertEquals(expected, actual);
841     }
842 
843     @Test
testGetMaxAvailableHeight_topAnchor_ignoringBottomDecoration()844     public void testGetMaxAvailableHeight_topAnchor_ignoringBottomDecoration() {
845         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
846         final View anchorView = mActivity.findViewById(R.id.anchor_upper);
847 
848         final int expected = getDisplayFrame(anchorView).bottom - getViewBottom(anchorView);
849         final int actual = mPopupWindow.getMaxAvailableHeight(anchorView, 0, IGNORE_BOTTOM_DECOR);
850 
851         assertEquals(expected, actual);
852     }
853 
854     @Test
testGetMaxAvailableHeight_topAnchor_offset2()855     public void testGetMaxAvailableHeight_topAnchor_offset2() {
856         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
857         final View anchorView = mActivity.findViewById(R.id.anchor_upper);
858 
859         final int expected =
860                 getVisibleDisplayFrame(anchorView).bottom - getViewBottom(anchorView) - 2;
861         final int actual = mPopupWindow.getMaxAvailableHeight(anchorView, 2);
862 
863         assertEquals(expected, actual);
864     }
865 
866     @Test
testGetMaxAvailableHeight_topAnchor_offset2_ignoringBottomDecoration()867     public void testGetMaxAvailableHeight_topAnchor_offset2_ignoringBottomDecoration() {
868         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
869         final View anchorView = mActivity.findViewById(R.id.anchor_upper);
870 
871         final int expected = getDisplayFrame(anchorView).bottom - getViewBottom(anchorView) - 2;
872         final int actual = mPopupWindow.getMaxAvailableHeight(anchorView, 2, IGNORE_BOTTOM_DECOR);
873 
874         assertEquals(expected, actual);
875     }
876 
877     @Test
testGetMaxAvailableHeight_topAnchor_largeOffset()878     public void testGetMaxAvailableHeight_topAnchor_largeOffset() {
879         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
880         final View anchorView = mActivity.findViewById(R.id.anchor_upper);
881         final Rect visibleDisplayFrame = getVisibleDisplayFrame(anchorView);
882         final int maxOffset = visibleDisplayFrame.bottom - getViewBottom(anchorView);
883         final int offset = maxOffset / 2;
884 
885         final int distanceToTop = getLoc(anchorView).y - visibleDisplayFrame.top + offset;
886         final int distanceToBottom =
887                 visibleDisplayFrame.bottom - getViewBottom(anchorView) - offset;
888 
889         final int expected = Math.max(distanceToTop, distanceToBottom);
890         final int actual = mPopupWindow.getMaxAvailableHeight(anchorView, offset);
891 
892         assertEquals(expected, actual);
893     }
894 
895     @Test
testGetMaxAvailableHeight_topAnchor_largeOffset_ignoringBottomDecoration()896     public void testGetMaxAvailableHeight_topAnchor_largeOffset_ignoringBottomDecoration() {
897         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
898         final View anchorView = mActivity.findViewById(R.id.anchor_upper);
899         final Rect visibleDisplayFrame = getVisibleDisplayFrame(anchorView);
900         final Rect displayFrame = getDisplayFrame(anchorView);
901 
902         final int maxOffset = visibleDisplayFrame.bottom - getViewBottom(anchorView);
903         final int offset = maxOffset / 2;
904 
905         final int distanceToTop = getLoc(anchorView).y - visibleDisplayFrame.top + offset;
906         final int distanceToBottom = displayFrame.bottom - getViewBottom(anchorView) - offset;
907 
908         final int expected = Math.max(distanceToTop, distanceToBottom);
909         final int actual =
910                 mPopupWindow.getMaxAvailableHeight(anchorView, offset, IGNORE_BOTTOM_DECOR);
911 
912         assertEquals(expected, actual);
913     }
914 
915     @Test
testGetMaxAvailableHeight_topAnchor_maxOffset()916     public void testGetMaxAvailableHeight_topAnchor_maxOffset() {
917         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
918         final View anchorView = mActivity.findViewById(R.id.anchor_upper);
919         final Rect visibleDisplayFrame = getVisibleDisplayFrame(anchorView);
920         final int offset = visibleDisplayFrame.bottom - getViewBottom(anchorView);
921 
922         final int expected = getLoc(anchorView).y - visibleDisplayFrame.top + offset;
923         final int actual = mPopupWindow.getMaxAvailableHeight(anchorView, offset);
924 
925         assertEquals(expected, actual);
926     }
927 
928     @Test
testGetMaxAvailableHeight_topAnchor_maxOffset_ignoringBottomDecoration()929     public void testGetMaxAvailableHeight_topAnchor_maxOffset_ignoringBottomDecoration() {
930         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
931         final View anchorView = mActivity.findViewById(R.id.anchor_upper);
932         final Rect visibleDisplayFrame = getVisibleDisplayFrame(anchorView);
933         final int offset = visibleDisplayFrame.bottom - getViewBottom(anchorView);
934 
935         final int expected = getLoc(anchorView).y - visibleDisplayFrame.top + offset;
936         final int actual =
937                 mPopupWindow.getMaxAvailableHeight(anchorView, offset, IGNORE_BOTTOM_DECOR);
938 
939         assertEquals(expected, actual);
940     }
941 
942     @Test
testGetMaxAvailableHeight_topAnchor_negativeOffset()943     public void testGetMaxAvailableHeight_topAnchor_negativeOffset() {
944         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
945         final View anchorView = mActivity.findViewById(R.id.anchor_upper);
946 
947         final int expected =
948                 getVisibleDisplayFrame(anchorView).bottom - getViewBottom(anchorView) + 1;
949         final int actual = mPopupWindow.getMaxAvailableHeight(anchorView, -1);
950 
951         assertEquals(expected, actual);
952     }
953 
954     // TODO(b/136178425): A negative offset can return a size that is larger than the display.
955     @Test
testGetMaxAvailableHeight_topAnchor_negativeOffset_ignoringBottomDecoration()956     public void testGetMaxAvailableHeight_topAnchor_negativeOffset_ignoringBottomDecoration() {
957         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
958         final View anchorView = mActivity.findViewById(R.id.anchor_upper);
959 
960         final int expected =
961                 getDisplayFrame(anchorView).bottom - getViewBottom(anchorView) + 1;
962         final int actual = mPopupWindow.getMaxAvailableHeight(anchorView, -1, IGNORE_BOTTOM_DECOR);
963 
964         assertEquals(expected, actual);
965     }
966 
967     @Test
testGetMaxAvailableHeight_middleAnchor()968     public void testGetMaxAvailableHeight_middleAnchor() {
969         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
970         final View anchorView = mActivity.findViewById(R.id.anchor_middle_left);
971         final Rect visibleDisplayFrame = getVisibleDisplayFrame(anchorView);
972 
973         final int distanceToTop = getLoc(anchorView).y - visibleDisplayFrame.top;
974         final int distanceToBottom = visibleDisplayFrame.bottom - getViewBottom(anchorView);
975 
976         final int expected = Math.max(distanceToTop, distanceToBottom);
977         final int actual = mPopupWindow.getMaxAvailableHeight(anchorView);
978 
979         assertEquals(expected, actual);
980     }
981 
982     @Test
testGetMaxAvailableHeight_middleAnchor_ignoreBottomDecoration()983     public void testGetMaxAvailableHeight_middleAnchor_ignoreBottomDecoration() {
984         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
985         final View anchorView = mActivity.findViewById(R.id.anchor_middle_left);
986         final Rect visibleDisplayFrame = getVisibleDisplayFrame(anchorView);
987         final Rect displayFrame = getDisplayFrame(anchorView);
988 
989 
990         final int distanceToTop = getLoc(anchorView).y - visibleDisplayFrame.top;
991         final int distanceToBottom = displayFrame.bottom - getViewBottom(anchorView);
992 
993         final int expected = Math.max(distanceToTop, distanceToBottom);
994         final int actual = mPopupWindow.getMaxAvailableHeight(anchorView, 0, IGNORE_BOTTOM_DECOR);
995 
996         assertEquals(expected, actual);
997     }
998 
999     @Test
testGetMaxAvailableHeight_bottomAnchor()1000     public void testGetMaxAvailableHeight_bottomAnchor() {
1001         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
1002         final View anchorView = mActivity.findViewById(R.id.anchor_lower);
1003 
1004         final int expected = getLoc(anchorView).y - getVisibleDisplayFrame(anchorView).top;
1005         final int actual = mPopupWindow.getMaxAvailableHeight(anchorView);
1006 
1007         assertEquals(expected, actual);
1008     }
1009 
1010     @Test
testGetMaxAvailableHeight_bottomAnchor_ignoreBottomDecoration()1011     public void testGetMaxAvailableHeight_bottomAnchor_ignoreBottomDecoration() {
1012         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
1013         final View anchorView = mActivity.findViewById(R.id.anchor_lower);
1014 
1015         final int expected = getLoc(anchorView).y - getVisibleDisplayFrame(anchorView).top;
1016         final int actual = mPopupWindow.getMaxAvailableHeight(anchorView, 0, IGNORE_BOTTOM_DECOR);
1017 
1018         assertEquals(expected, actual);
1019     }
1020 
getLoc(View view)1021     private Point getLoc(View view) {
1022         final int[] anchorPosition = new int[2];
1023         view.getLocationOnScreen(anchorPosition);
1024         return new Point(anchorPosition[0], anchorPosition[1]);
1025     }
1026 
getViewBottom(View view)1027     private int getViewBottom(View view) {
1028         return getLoc(view).y + view.getHeight();
1029     }
1030 
getVisibleDisplayFrame(View view)1031     private Rect getVisibleDisplayFrame(View view) {
1032         final Rect visibleDisplayFrame = new Rect();
1033         view.getWindowVisibleDisplayFrame(visibleDisplayFrame);
1034         return visibleDisplayFrame;
1035     }
1036 
getDisplayFrame(View view)1037     private Rect getDisplayFrame(View view) {
1038         final Rect displayFrame = new Rect();
1039         view.getWindowDisplayFrame(displayFrame);
1040         return displayFrame;
1041     }
1042 
1043     @UiThreadTest
1044     @Test
testDismiss()1045     public void testDismiss() {
1046         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
1047         assertFalse(mPopupWindow.isShowing());
1048         View anchorView = mActivity.findViewById(R.id.anchor_upper);
1049         mPopupWindow.showAsDropDown(anchorView);
1050 
1051         mPopupWindow.dismiss();
1052         assertFalse(mPopupWindow.isShowing());
1053 
1054         mPopupWindow.dismiss();
1055         assertFalse(mPopupWindow.isShowing());
1056     }
1057 
1058     @Test
testSetOnDismissListener()1059     public void testSetOnDismissListener() throws Throwable {
1060         mActivityRule.runOnUiThread(() -> mTextView = new TextView(mActivity));
1061         mInstrumentation.waitForIdleSync();
1062         mPopupWindow = new PopupWindow(mTextView);
1063         mPopupWindow.setOnDismissListener(null);
1064 
1065         OnDismissListener onDismissListener = mock(OnDismissListener.class);
1066         mPopupWindow.setOnDismissListener(onDismissListener);
1067         showPopup();
1068         dismissPopup();
1069         verify(onDismissListener, times(1)).onDismiss();
1070 
1071         showPopup();
1072         dismissPopup();
1073         verify(onDismissListener, times(2)).onDismiss();
1074 
1075         mPopupWindow.setOnDismissListener(null);
1076         showPopup();
1077         dismissPopup();
1078         verify(onDismissListener, times(2)).onDismiss();
1079     }
1080 
1081     @Test
testUpdate()1082     public void testUpdate() throws Throwable {
1083         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
1084         mPopupWindow.setBackgroundDrawable(null);
1085         showPopup();
1086 
1087         mPopupWindow.setIgnoreCheekPress();
1088         mPopupWindow.setFocusable(true);
1089         mPopupWindow.setTouchable(false);
1090         mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
1091         mPopupWindow.setClippingEnabled(false);
1092         mPopupWindow.setOutsideTouchable(true);
1093 
1094         WindowManager.LayoutParams p = (WindowManager.LayoutParams)
1095                 mPopupWindow.getContentView().getRootView().getLayoutParams();
1096 
1097         assertEquals(0, WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES & p.flags);
1098         assertEquals(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
1099                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE & p.flags);
1100         assertEquals(0, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE & p.flags);
1101         assertEquals(0, WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH & p.flags);
1102         assertEquals(0, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS & p.flags);
1103         assertEquals(0, WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM & p.flags);
1104 
1105         mActivityRule.runOnUiThread(mPopupWindow::update);
1106         mInstrumentation.waitForIdleSync();
1107 
1108         assertEquals(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES,
1109                 WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES & p.flags);
1110         assertEquals(0, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE & p.flags);
1111         assertEquals(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
1112                 WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE & p.flags);
1113         assertEquals(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
1114                 WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH & p.flags);
1115         assertEquals(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
1116                 WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS & p.flags);
1117         assertEquals(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
1118                 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM & p.flags);
1119     }
1120 
1121     @Test
testEnterExitInterruption()1122     public void testEnterExitInterruption() throws Throwable {
1123         final View anchorView = mActivity.findViewById(R.id.anchor_upper);
1124         verifyEnterExitTransition(
1125                 () -> mPopupWindow.showAsDropDown(anchorView, 0, 0), true);
1126     }
1127 
1128     @Test
testEnterExitTransitionAsDropDown()1129     public void testEnterExitTransitionAsDropDown() throws Throwable {
1130         final View anchorView = mActivity.findViewById(R.id.anchor_upper);
1131         verifyEnterExitTransition(
1132                 () -> mPopupWindow.showAsDropDown(anchorView, 0, 0), false);
1133     }
1134 
1135     @Test
testEnterExitTransitionAtLocation()1136     public void testEnterExitTransitionAtLocation() throws Throwable {
1137         final View anchorView = mActivity.findViewById(R.id.anchor_upper);
1138         verifyEnterExitTransition(
1139                 () -> mPopupWindow.showAtLocation(anchorView, Gravity.BOTTOM, 0, 0), false);
1140     }
1141 
1142     @Test
testEnterExitTransitionAsDropDownWithCustomBounds()1143     public void testEnterExitTransitionAsDropDownWithCustomBounds() throws Throwable {
1144         final View anchorView = mActivity.findViewById(R.id.anchor_upper);
1145         final Rect epicenter = new Rect(20, 50, 22, 80);
1146         verifyTransitionEpicenterChange(
1147                 () -> mPopupWindow.showAsDropDown(anchorView, 0, 0), epicenter);
1148     }
1149 
verifyTransitionEpicenterChange(Runnable showRunnable, Rect epicenterBounds)1150     private void verifyTransitionEpicenterChange(Runnable showRunnable, Rect epicenterBounds)
1151             throws Throwable {
1152         TransitionListener enterListener = mock(TransitionListener.class);
1153         Transition enterTransition = new BaseTransition();
1154         enterTransition.addListener(enterListener);
1155 
1156         TransitionListener exitListener = mock(TransitionListener.class);
1157         Transition exitTransition = new BaseTransition();
1158         exitTransition.addListener(exitListener);
1159 
1160         OnDismissListener dismissListener = mock(OnDismissListener.class);
1161 
1162         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
1163         mPopupWindow.setEnterTransition(enterTransition);
1164         mPopupWindow.setExitTransition(exitTransition);
1165         mPopupWindow.setOnDismissListener(dismissListener);
1166 
1167         ArgumentCaptor<Transition> captor = ArgumentCaptor.forClass(Transition.class);
1168 
1169         mActivityRule.runOnUiThread(showRunnable);
1170         mInstrumentation.waitForIdleSync();
1171 
1172         verify(enterListener, times(1)).onTransitionStart(captor.capture());
1173         final Rect oldEpicenterStart = new Rect(captor.getValue().getEpicenter());
1174 
1175         mActivityRule.runOnUiThread(mPopupWindow::dismiss);
1176         mInstrumentation.waitForIdleSync();
1177 
1178         verify(exitListener, times(1)).onTransitionStart(captor.capture());
1179         final Rect oldEpicenterExit = new Rect(captor.getValue().getEpicenter());
1180 
1181         mPopupWindow.setEpicenterBounds(epicenterBounds);
1182         mActivityRule.runOnUiThread(showRunnable);
1183         mInstrumentation.waitForIdleSync();
1184 
1185         verify(enterListener, times(2)).onTransitionStart(captor.capture());
1186         final Rect newEpicenterStart = new Rect(captor.getValue().getEpicenter());
1187 
1188         mActivityRule.runOnUiThread(mPopupWindow::dismiss);
1189         mInstrumentation.waitForIdleSync();
1190 
1191         verify(exitListener, times(2)).onTransitionStart(captor.capture());
1192 
1193         final Rect newEpicenterExit = new Rect(captor.getValue().getEpicenter());
1194 
1195         verifyEpicenters(oldEpicenterStart, newEpicenterStart, epicenterBounds);
1196         verifyEpicenters(oldEpicenterExit, newEpicenterExit, epicenterBounds);
1197 
1198     }
1199 
verifyEpicenters(Rect actualOld, Rect actualNew, Rect passed)1200     private void verifyEpicenters(Rect actualOld, Rect actualNew, Rect passed) {
1201         Rect oldCopy = new Rect(actualOld);
1202         int left = oldCopy.left;
1203         int top = oldCopy.top;
1204         oldCopy.set(passed);
1205         oldCopy.offset(left, top);
1206 
1207         assertEquals(oldCopy, actualNew);
1208     }
1209 
verifyEnterExitTransition(Runnable showRunnable, boolean showAgain)1210     private void verifyEnterExitTransition(Runnable showRunnable, boolean showAgain)
1211             throws Throwable {
1212         TransitionListener enterListener = mock(TransitionListener.class);
1213         Transition enterTransition = new BaseTransition();
1214         enterTransition.addListener(enterListener);
1215 
1216         TransitionListener exitListener = mock(TransitionListener.class);
1217         Transition exitTransition = new BaseTransition();
1218         exitTransition.addListener(exitListener);
1219 
1220         OnDismissListener dismissListener = mock(OnDismissListener.class);
1221 
1222         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
1223         mPopupWindow.setEnterTransition(enterTransition);
1224         mPopupWindow.setExitTransition(exitTransition);
1225         mPopupWindow.setOnDismissListener(dismissListener);
1226 
1227         mActivityRule.runOnUiThread(showRunnable);
1228         mInstrumentation.waitForIdleSync();
1229         verify(enterListener, times(1)).onTransitionStart(any(Transition.class));
1230         verify(exitListener, never()).onTransitionStart(any(Transition.class));
1231         verify(dismissListener, never()).onDismiss();
1232 
1233         mActivityRule.runOnUiThread(mPopupWindow::dismiss);
1234 
1235         int times;
1236         if (showAgain) {
1237             // Interrupt dismiss by calling show again, then actually dismiss.
1238             mActivityRule.runOnUiThread(showRunnable);
1239             mInstrumentation.waitForIdleSync();
1240             mActivityRule.runOnUiThread(mPopupWindow::dismiss);
1241 
1242             times = 2;
1243         } else {
1244             times = 1;
1245         }
1246 
1247         mInstrumentation.waitForIdleSync();
1248         verify(enterListener, times(times)).onTransitionStart(any(Transition.class));
1249         verify(exitListener, times(times)).onTransitionStart(any(Transition.class));
1250         verify(dismissListener, times(times)).onDismiss();
1251     }
1252 
1253     @Test
testUpdatePositionAndDimension()1254     public void testUpdatePositionAndDimension() throws Throwable {
1255         int[] fstXY = new int[2];
1256         int[] sndXY = new int[2];
1257         int[] viewInWindowXY = new int[2];
1258         Rect containingRect = new Rect();
1259         final Point popupPos = new Point();
1260 
1261         mActivityRule.runOnUiThread(() -> {
1262             mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
1263             // Do not attach within the decor; we will be measuring location
1264             // with regard to screen coordinates.
1265             mPopupWindow.setAttachedInDecor(false);
1266         });
1267 
1268         mInstrumentation.waitForIdleSync();
1269         // Do not update if it is not shown
1270         assertFalse(mPopupWindow.isShowing());
1271         assertFalse(mPopupWindow.isAttachedInDecor());
1272         assertEquals(WINDOW_SIZE_DP, mPopupWindow.getWidth());
1273         assertEquals(WINDOW_SIZE_DP, mPopupWindow.getHeight());
1274 
1275         showPopup();
1276         mPopupWindow.getContentView().getLocationInWindow(viewInWindowXY);
1277         final View containerView = mActivity.findViewById(R.id.main_container);
1278         containerView.getWindowDisplayFrame(containingRect);
1279 
1280         // update if it is not shown
1281         mActivityRule.runOnUiThread(() -> mPopupWindow.update(80, 80));
1282 
1283         mInstrumentation.waitForIdleSync();
1284         assertTrue(mPopupWindow.isShowing());
1285         assertEquals(80, mPopupWindow.getWidth());
1286         assertEquals(80, mPopupWindow.getHeight());
1287 
1288         final WindowInsets windowInsets = containerView.getRootWindowInsets();
1289         popupPos.set(windowInsets.getStableInsetLeft() + 20, windowInsets.getStableInsetTop() + 50);
1290 
1291         // update if it is not shown
1292         mActivityRule.runOnUiThread(() -> mPopupWindow.update(popupPos.x, popupPos.y, 50, 50));
1293 
1294         mInstrumentation.waitForIdleSync();
1295         assertTrue(mPopupWindow.isShowing());
1296         assertEquals(50, mPopupWindow.getWidth());
1297         assertEquals(50, mPopupWindow.getHeight());
1298 
1299         mPopupWindow.getContentView().getLocationOnScreen(fstXY);
1300         assertEquals(containingRect.left + popupPos.x + viewInWindowXY[0], fstXY[0]);
1301         assertEquals(containingRect.top + popupPos.y + viewInWindowXY[1], fstXY[1]);
1302 
1303         popupPos.set(windowInsets.getStableInsetLeft() + 4, windowInsets.getStableInsetTop());
1304 
1305         // ignore if width or height is -1
1306         mActivityRule.runOnUiThread(
1307                 () -> mPopupWindow.update(popupPos.x, popupPos.y, -1, -1, true));
1308         mInstrumentation.waitForIdleSync();
1309 
1310         assertTrue(mPopupWindow.isShowing());
1311         assertEquals(50, mPopupWindow.getWidth());
1312         assertEquals(50, mPopupWindow.getHeight());
1313 
1314         mPopupWindow.getContentView().getLocationOnScreen(sndXY);
1315         assertEquals(containingRect.left + popupPos.x + viewInWindowXY[0], sndXY[0]);
1316         assertEquals(containingRect.top + popupPos.y + viewInWindowXY[1], sndXY[1]);
1317 
1318         dismissPopup();
1319     }
1320 
1321     @Test
testUpdateDimensionAndAlignAnchorView()1322     public void testUpdateDimensionAndAlignAnchorView() throws Throwable {
1323         mActivityRule.runOnUiThread(
1324                 () -> mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP,
1325                         CONTENT_SIZE_DP)));
1326         mInstrumentation.waitForIdleSync();
1327 
1328         final View anchorView = mActivity.findViewById(R.id.anchor_upper);
1329         mPopupWindow.update(anchorView, 50, 50);
1330         // Do not update if it is not shown
1331         assertFalse(mPopupWindow.isShowing());
1332         assertEquals(WINDOW_SIZE_DP, mPopupWindow.getWidth());
1333         assertEquals(WINDOW_SIZE_DP, mPopupWindow.getHeight());
1334 
1335         mActivityRule.runOnUiThread(() -> mPopupWindow.showAsDropDown(anchorView));
1336         mInstrumentation.waitForIdleSync();
1337         // update if it is shown
1338         mActivityRule.runOnUiThread(() -> mPopupWindow.update(anchorView, 50, 50));
1339         mInstrumentation.waitForIdleSync();
1340         assertTrue(mPopupWindow.isShowing());
1341         assertEquals(50, mPopupWindow.getWidth());
1342         assertEquals(50, mPopupWindow.getHeight());
1343 
1344         // ignore if width or height is -1
1345         mActivityRule.runOnUiThread(() -> mPopupWindow.update(anchorView, -1, -1));
1346         mInstrumentation.waitForIdleSync();
1347         assertTrue(mPopupWindow.isShowing());
1348         assertEquals(50, mPopupWindow.getWidth());
1349         assertEquals(50, mPopupWindow.getHeight());
1350 
1351         mActivityRule.runOnUiThread(mPopupWindow::dismiss);
1352         mInstrumentation.waitForIdleSync();
1353     }
1354 
1355     @Test
testUpdateDimensionAndAlignAnchorViewWithOffsets()1356     public void testUpdateDimensionAndAlignAnchorViewWithOffsets() throws Throwable {
1357         int[] anchorXY = new int[2];
1358         int[] viewInWindowOff = new int[2];
1359         int[] viewXY = new int[2];
1360 
1361         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
1362         final View anchorView = mActivity.findViewById(R.id.anchor_upper);
1363         // Do not update if it is not shown
1364         assertFalse(mPopupWindow.isShowing());
1365         assertEquals(WINDOW_SIZE_DP, mPopupWindow.getWidth());
1366         assertEquals(WINDOW_SIZE_DP, mPopupWindow.getHeight());
1367 
1368         showPopup();
1369         anchorView.getLocationOnScreen(anchorXY);
1370         mPopupWindow.getContentView().getLocationInWindow(viewInWindowOff);
1371 
1372         // update if it is not shown
1373         mActivityRule.runOnUiThread(() -> mPopupWindow.update(anchorView, 20, 50, 50, 50));
1374 
1375         mInstrumentation.waitForIdleSync();
1376 
1377         assertTrue(mPopupWindow.isShowing());
1378         assertEquals(50, mPopupWindow.getWidth());
1379         assertEquals(50, mPopupWindow.getHeight());
1380 
1381         mPopupWindow.getContentView().getLocationOnScreen(viewXY);
1382 
1383         // The popup should appear below and to right with an offset.
1384         assertEquals(anchorXY[0] + 20 + viewInWindowOff[0], viewXY[0]);
1385         assertEquals(anchorXY[1] + anchorView.getHeight() + 50 + viewInWindowOff[1], viewXY[1]);
1386 
1387         // ignore width and height but change location
1388         mActivityRule.runOnUiThread(() -> mPopupWindow.update(anchorView, 10, 50, -1, -1));
1389         mInstrumentation.waitForIdleSync();
1390 
1391         assertTrue(mPopupWindow.isShowing());
1392         assertEquals(50, mPopupWindow.getWidth());
1393         assertEquals(50, mPopupWindow.getHeight());
1394 
1395         mPopupWindow.getContentView().getLocationOnScreen(viewXY);
1396 
1397         // The popup should appear below and to right with an offset.
1398         assertEquals(anchorXY[0] + 10 + viewInWindowOff[0], viewXY[0]);
1399         assertEquals(anchorXY[1] + anchorView.getHeight() + 50 + viewInWindowOff[1], viewXY[1]);
1400 
1401         final View anotherView = mActivity.findViewById(R.id.anchor_middle_left);
1402         mActivityRule.runOnUiThread(() -> mPopupWindow.update(anotherView, 0, 0, 60, 60));
1403         mInstrumentation.waitForIdleSync();
1404 
1405         assertTrue(mPopupWindow.isShowing());
1406         assertEquals(60, mPopupWindow.getWidth());
1407         assertEquals(60, mPopupWindow.getHeight());
1408 
1409         int[] newXY = new int[2];
1410         anotherView.getLocationOnScreen(newXY);
1411         mPopupWindow.getContentView().getLocationOnScreen(viewXY);
1412 
1413         // The popup should appear below and to the right.
1414         assertEquals(newXY[0] + viewInWindowOff[0], viewXY[0]);
1415         assertEquals(newXY[1] + anotherView.getHeight() + viewInWindowOff[1], viewXY[1]);
1416 
1417         dismissPopup();
1418     }
1419 
1420     @Test
testAccessInputMethodMode()1421     public void testAccessInputMethodMode() {
1422         mPopupWindow = new PopupWindow(mActivity);
1423         assertEquals(0, mPopupWindow.getInputMethodMode());
1424 
1425         mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_FROM_FOCUSABLE);
1426         assertEquals(PopupWindow.INPUT_METHOD_FROM_FOCUSABLE, mPopupWindow.getInputMethodMode());
1427 
1428         mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
1429         assertEquals(PopupWindow.INPUT_METHOD_NEEDED, mPopupWindow.getInputMethodMode());
1430 
1431         mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
1432         assertEquals(PopupWindow.INPUT_METHOD_NOT_NEEDED, mPopupWindow.getInputMethodMode());
1433 
1434         mPopupWindow.setInputMethodMode(-1);
1435         assertEquals(-1, mPopupWindow.getInputMethodMode());
1436     }
1437 
1438     @Test
testAccessClippingEnabled()1439     public void testAccessClippingEnabled() {
1440         mPopupWindow = new PopupWindow(mActivity);
1441         assertTrue(mPopupWindow.isClippingEnabled());
1442 
1443         mPopupWindow.setClippingEnabled(false);
1444         assertFalse(mPopupWindow.isClippingEnabled());
1445     }
1446 
1447     @Test
testAccessIsClippedToScreen()1448     public void testAccessIsClippedToScreen() {
1449         mPopupWindow = new PopupWindow(mActivity);
1450         assertFalse(mPopupWindow.isClippedToScreen());
1451 
1452         mPopupWindow.setIsClippedToScreen(true);
1453         assertTrue(mPopupWindow.isClippedToScreen());
1454     }
1455 
1456     @Test
testAccessIsLaidOutInScreen()1457     public void testAccessIsLaidOutInScreen() {
1458         mPopupWindow = new PopupWindow(mActivity);
1459         assertFalse(mPopupWindow.isLaidOutInScreen());
1460 
1461         mPopupWindow.setIsLaidOutInScreen(true);
1462         assertTrue(mPopupWindow.isLaidOutInScreen());
1463     }
1464 
1465     @Test
testAccessTouchModal()1466     public void testAccessTouchModal() {
1467         mPopupWindow = new PopupWindow(mActivity);
1468         assertTrue(mPopupWindow.isTouchModal());
1469 
1470         mPopupWindow.setTouchModal(false);
1471         assertFalse(mPopupWindow.isTouchModal());
1472     }
1473 
1474     @Test
testAccessEpicenterBounds()1475     public void testAccessEpicenterBounds() {
1476         mPopupWindow = new PopupWindow(mActivity);
1477         assertNull(mPopupWindow.getEpicenterBounds());
1478 
1479         final Rect epicenter = new Rect(5, 10, 15, 20);
1480 
1481         mPopupWindow.setEpicenterBounds(epicenter);
1482         assertEquals(mPopupWindow.getEpicenterBounds(), epicenter);
1483 
1484         mPopupWindow.setEpicenterBounds(null);
1485         assertNull(mPopupWindow.getEpicenterBounds());
1486     }
1487 
1488     @Test
testAccessOutsideTouchable()1489     public void testAccessOutsideTouchable() {
1490         mPopupWindow = new PopupWindow(mActivity);
1491         assertFalse(mPopupWindow.isOutsideTouchable());
1492 
1493         mPopupWindow.setOutsideTouchable(true);
1494         assertTrue(mPopupWindow.isOutsideTouchable());
1495     }
1496 
1497     @Test
testAccessTouchable()1498     public void testAccessTouchable() {
1499         mPopupWindow = new PopupWindow(mActivity);
1500         assertTrue(mPopupWindow.isTouchable());
1501 
1502         mPopupWindow.setTouchable(false);
1503         assertFalse(mPopupWindow.isTouchable());
1504     }
1505 
1506     @Test
testIsAboveAnchor()1507     public void testIsAboveAnchor() throws Throwable {
1508         mActivityRule.runOnUiThread(() -> mPopupWindow = createPopupWindow(createPopupContent(
1509                 CONTENT_SIZE_DP, CONTENT_SIZE_DP)));
1510         mInstrumentation.waitForIdleSync();
1511         final View upperAnchor = mActivity.findViewById(R.id.anchor_upper);
1512 
1513         mActivityRule.runOnUiThread(() -> mPopupWindow.showAsDropDown(upperAnchor));
1514         mInstrumentation.waitForIdleSync();
1515         assertFalse(mPopupWindow.isAboveAnchor());
1516         dismissPopup();
1517 
1518         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
1519         final View lowerAnchor = mActivity.findViewById(R.id.anchor_lower);
1520 
1521         mActivityRule.runOnUiThread(() -> mPopupWindow.showAsDropDown(lowerAnchor, 0, 0));
1522         mInstrumentation.waitForIdleSync();
1523         assertTrue(mPopupWindow.isAboveAnchor());
1524         dismissPopup();
1525     }
1526 
1527     @Test
testSetTouchInterceptor()1528     public void testSetTouchInterceptor() throws Throwable {
1529         final CountDownLatch latch = new CountDownLatch(1);
1530         mActivityRule.runOnUiThread(() -> mTextView = new TextView(mActivity));
1531         mActivityRule.runOnUiThread(() -> mTextView.setText("Testing"));
1532         ViewTreeObserver observer = mTextView.getViewTreeObserver();
1533         observer.addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
1534             @Override
1535             public void onWindowFocusChanged(boolean hasFocus) {
1536                 if (hasFocus) {
1537                     ViewTreeObserver currentObserver = mTextView.getViewTreeObserver();
1538                     currentObserver.removeOnWindowFocusChangeListener(this);
1539                     latch.countDown();
1540                 }
1541             }
1542         });
1543         mPopupWindow = new PopupWindow(mTextView, LayoutParams.WRAP_CONTENT,
1544                 LayoutParams.WRAP_CONTENT, true /* focusable */);
1545 
1546         OnTouchListener onTouchListener = mock(OnTouchListener.class);
1547         when(onTouchListener.onTouch(any(View.class), any(MotionEvent.class))).thenReturn(true);
1548 
1549         mPopupWindow.setTouchInterceptor(onTouchListener);
1550         mPopupWindow.setOutsideTouchable(true);
1551         Drawable drawable = new ColorDrawable();
1552         mPopupWindow.setBackgroundDrawable(drawable);
1553         mPopupWindow.setAnimationStyle(0);
1554         showPopup();
1555         mInstrumentation.waitForIdleSync();
1556 
1557         latch.await(2000, TimeUnit.MILLISECONDS);
1558         // Extra delay to allow input system to get fully set up (b/113686346)
1559         SystemClock.sleep(500);
1560         int[] xy = new int[2];
1561         mPopupWindow.getContentView().getLocationOnScreen(xy);
1562         final int viewWidth = mPopupWindow.getContentView().getWidth();
1563         final int viewHeight = mPopupWindow.getContentView().getHeight();
1564         final float x = xy[0] + (viewWidth / 2.0f);
1565         float y = xy[1] + (viewHeight / 2.0f);
1566 
1567         long downTime = SystemClock.uptimeMillis();
1568         long eventTime = SystemClock.uptimeMillis();
1569         MotionEvent event = MotionEvent.obtain(downTime, eventTime,
1570                 MotionEvent.ACTION_DOWN, x, y, 0);
1571         mInstrumentation.sendPointerSync(event);
1572         verify(onTouchListener, times(1)).onTouch(any(View.class), any(MotionEvent.class));
1573 
1574         downTime = SystemClock.uptimeMillis();
1575         eventTime = SystemClock.uptimeMillis();
1576         event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
1577         mInstrumentation.sendPointerSync(event);
1578         verify(onTouchListener, times(2)).onTouch(any(View.class), any(MotionEvent.class));
1579 
1580         mPopupWindow.setTouchInterceptor(null);
1581         downTime = SystemClock.uptimeMillis();
1582         eventTime = SystemClock.uptimeMillis();
1583         event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0);
1584         mInstrumentation.sendPointerSync(event);
1585         verify(onTouchListener, times(2)).onTouch(any(View.class), any(MotionEvent.class));
1586     }
1587 
1588     @Test
testSetWindowLayoutMode()1589     public void testSetWindowLayoutMode() throws Throwable {
1590         mActivityRule.runOnUiThread(() -> mTextView = new TextView(mActivity));
1591         mInstrumentation.waitForIdleSync();
1592         mPopupWindow = new PopupWindow(mTextView);
1593         showPopup();
1594 
1595         ViewGroup.LayoutParams p = mPopupWindow.getContentView().getRootView().getLayoutParams();
1596         assertEquals(0, p.width);
1597         assertEquals(0, p.height);
1598 
1599         mPopupWindow.setWindowLayoutMode(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
1600         mActivityRule.runOnUiThread(() -> mPopupWindow.update(20, 50, 50, 50));
1601 
1602         assertEquals(LayoutParams.WRAP_CONTENT, p.width);
1603         assertEquals(LayoutParams.MATCH_PARENT, p.height);
1604     }
1605 
1606     @Test
testAccessElevation()1607     public void testAccessElevation() throws Throwable {
1608         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
1609         mActivityRule.runOnUiThread(() -> mPopupWindow.setElevation(2.0f));
1610 
1611         showPopup();
1612         assertEquals(2.0f, mPopupWindow.getElevation(), 0.0f);
1613 
1614         dismissPopup();
1615         mActivityRule.runOnUiThread(() -> mPopupWindow.setElevation(4.0f));
1616         showPopup();
1617         assertEquals(4.0f, mPopupWindow.getElevation(), 0.0f);
1618 
1619         dismissPopup();
1620         mActivityRule.runOnUiThread(() -> mPopupWindow.setElevation(10.0f));
1621         showPopup();
1622         assertEquals(10.0f, mPopupWindow.getElevation(), 0.0f);
1623     }
1624 
1625     @Test
testAccessSoftInputMode()1626     public void testAccessSoftInputMode() throws Throwable {
1627         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
1628         mActivityRule.runOnUiThread(
1629                 () -> mPopupWindow.setSoftInputMode(
1630                         WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE));
1631 
1632         showPopup();
1633         assertEquals(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE,
1634                 mPopupWindow.getSoftInputMode());
1635 
1636         dismissPopup();
1637         mActivityRule.runOnUiThread(
1638                 () -> mPopupWindow.setSoftInputMode(
1639                         WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN));
1640         showPopup();
1641         assertEquals(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN,
1642                 mPopupWindow.getSoftInputMode());
1643     }
1644 
1645     @Test
testAccessSplitTouchEnabled()1646     public void testAccessSplitTouchEnabled() throws Throwable {
1647         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
1648         mActivityRule.runOnUiThread(() -> mPopupWindow.setSplitTouchEnabled(true));
1649 
1650         showPopup();
1651         assertTrue(mPopupWindow.isSplitTouchEnabled());
1652 
1653         dismissPopup();
1654         mActivityRule.runOnUiThread(() -> mPopupWindow.setSplitTouchEnabled(false));
1655         showPopup();
1656         assertFalse(mPopupWindow.isSplitTouchEnabled());
1657 
1658         dismissPopup();
1659         mActivityRule.runOnUiThread(() -> mPopupWindow.setSplitTouchEnabled(true));
1660         showPopup();
1661         assertTrue(mPopupWindow.isSplitTouchEnabled());
1662     }
1663 
1664     @Test
testVerticallyClippedBeforeAdjusted()1665     public void testVerticallyClippedBeforeAdjusted() throws Throwable {
1666         View parentWindowView = mActivity.getWindow().getDecorView();
1667         int parentWidth = parentWindowView.getMeasuredWidth();
1668         int parentHeight = parentWindowView.getMeasuredHeight();
1669 
1670         // We make a popup which is too large to fit within the parent window.
1671         // After showing it, we verify that it is shrunk to fit the window,
1672         // rather than adjusted up.
1673         mPopupWindow = createPopupWindow(createPopupContent(parentWidth*2, parentHeight*2));
1674         mPopupWindow.setWidth(WindowManager.LayoutParams.WRAP_CONTENT);
1675         mPopupWindow.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
1676 
1677         showPopup(R.id.anchor_middle);
1678 
1679         View popupRoot = mPopupWindow.getContentView();
1680         int measuredWidth = popupRoot.getMeasuredWidth();
1681         int measuredHeight = popupRoot.getMeasuredHeight();
1682         View anchor = mActivity.findViewById(R.id.anchor_middle);
1683 
1684         // The popup should occupy all available vertical space, except the system bars.
1685         int[] anchorLocationInWindowXY = new int[2];
1686         anchor.getLocationInWindow(anchorLocationInWindowXY);
1687         assertEquals(measuredHeight,
1688                 parentHeight - (anchorLocationInWindowXY[1] + anchor.getHeight())
1689                         - parentWindowView.getRootWindowInsets().getSystemWindowInsetBottom());
1690 
1691         // The popup should be vertically aligned to the anchor's bottom edge.
1692         int[] anchorLocationOnScreenXY = new int[2];
1693         anchor.getLocationOnScreen(anchorLocationOnScreenXY);
1694         int[] popupLocationOnScreenXY = new int[2];
1695         popupRoot.getLocationOnScreen(popupLocationOnScreenXY);
1696         assertEquals(anchorLocationOnScreenXY[1] + anchor.getHeight(), popupLocationOnScreenXY[1]);
1697     }
1698 
1699     @Test
testClipToScreenClipsToInsets()1700     public void testClipToScreenClipsToInsets() throws Throwable {
1701         int[] orientationValues = {ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
1702                 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT};
1703         int currentOrientation = mActivity.getResources().getConfiguration().orientation;
1704         if (currentOrientation == Configuration.ORIENTATION_LANDSCAPE) {
1705             orientationValues[0] = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
1706             orientationValues[1] = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
1707         }
1708 
1709         for (int i = 0; i < 2; i++) {
1710             final int orientation = orientationValues[i];
1711             if (orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
1712                     && !hasDeviceFeature(PackageManager.FEATURE_SCREEN_PORTRAIT)) {
1713                 // skip test for devices not supporting portrait orientation
1714                 continue;
1715             }
1716             mActivity.runOnUiThread(() ->
1717                     mActivity.setRequestedOrientation(orientation));
1718             mActivity.waitForConfigurationChanged();
1719             // Wait for main thread to be idle to make sure layout and draw have been performed
1720             // before continuing.
1721             mInstrumentation.waitForIdleSync();
1722 
1723             View parentWindowView = mActivity.getWindow().getDecorView();
1724             int parentWidth = parentWindowView.getMeasuredWidth();
1725             int parentHeight = parentWindowView.getMeasuredHeight();
1726 
1727             mPopupWindow = createPopupWindow(createPopupContent(parentWidth*2, parentHeight*2));
1728             mPopupWindow.setWidth(WindowManager.LayoutParams.MATCH_PARENT);
1729             mPopupWindow.setHeight(WindowManager.LayoutParams.MATCH_PARENT);
1730             mPopupWindow.setIsClippedToScreen(true);
1731 
1732             showPopup(R.id.anchor_upper_left);
1733 
1734             View popupRoot = mPopupWindow.getContentView().getRootView();
1735             int measuredWidth  = popupRoot.getMeasuredWidth();
1736             int measuredHeight = popupRoot.getMeasuredHeight();
1737 
1738             // The visible frame will not include the insets.
1739             Rect visibleFrame = new Rect();
1740             parentWindowView.getWindowVisibleDisplayFrame(visibleFrame);
1741 
1742             assertEquals(measuredWidth, visibleFrame.width());
1743             assertEquals(measuredHeight, visibleFrame.height());
1744         }
1745     }
1746 
1747     @Test
testPositionAfterParentScroll()1748     public void testPositionAfterParentScroll() throws Throwable {
1749         View.OnScrollChangeListener scrollChangeListener = mock(
1750                 View.OnScrollChangeListener.class);
1751 
1752         mActivityRule.runOnUiThread(() -> {
1753             mActivity.setContentView(R.layout.popup_window_scrollable);
1754 
1755             View anchor = mActivity.findViewById(R.id.anchor_upper);
1756             PopupWindow window = createPopupWindow();
1757             window.showAsDropDown(anchor);
1758         });
1759 
1760         mActivityRule.runOnUiThread(() -> {
1761             View parent = mActivity.findViewById(R.id.main_container);
1762             parent.scrollBy(0, 500);
1763             parent.setOnScrollChangeListener(scrollChangeListener);
1764         });
1765 
1766         verify(scrollChangeListener, never()).onScrollChange(
1767                 any(View.class), anyInt(), anyInt(), anyInt(), anyInt());
1768     }
1769 
1770     @Test
testPositionAfterAnchorRemoval()1771     public void testPositionAfterAnchorRemoval() throws Throwable {
1772         mPopupWindow = createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
1773         showPopup(R.id.anchor_middle);
1774 
1775         final ViewGroup container = (ViewGroup) mActivity.findViewById(R.id.main_container);
1776         final View anchor = mActivity.findViewById(R.id.anchor_middle);
1777         final LayoutParams anchorLayoutParams = anchor.getLayoutParams();
1778 
1779         final int[] originalLocation = new int[2];
1780         mPopupWindow.getContentView().getLocationOnScreen(originalLocation);
1781 
1782         final int deltaX = 30;
1783         final int deltaY = 20;
1784 
1785         // Scroll the container, the popup should move along with the anchor.
1786         WidgetTestUtils.runOnMainAndLayoutSync(
1787                 mActivityRule,
1788                 mPopupWindow.getContentView().getRootView(),
1789                 () -> container.scrollBy(deltaX, deltaY),
1790                 false  /* force layout */);
1791         // Since the first layout might have been caused by the original scroll event (and not by
1792         // the anchor change), we need to wait until all traversals are done.
1793         mInstrumentation.waitForIdleSync();
1794         assertPopupLocation(originalLocation, deltaX, deltaY);
1795 
1796         // Detach the anchor, the popup should stay in the same location.
1797         WidgetTestUtils.runOnMainAndLayoutSync(
1798                 mActivityRule,
1799                 mActivity.getWindow().getDecorView(),
1800                 () -> container.removeView(anchor),
1801                 false  /* force layout */);
1802         assertPopupLocation(originalLocation, deltaX, deltaY);
1803 
1804         // Scroll the container while the anchor is detached, the popup should not move.
1805         WidgetTestUtils.runOnMainAndLayoutSync(
1806                 mActivityRule,
1807                 mActivity.getWindow().getDecorView(),
1808                 () -> container.scrollBy(deltaX, deltaY),
1809                 true  /* force layout */);
1810         mInstrumentation.waitForIdleSync();
1811         assertPopupLocation(originalLocation, deltaX, deltaY);
1812 
1813         // Re-attach the anchor, the popup should snap back to the new anchor location.
1814         WidgetTestUtils.runOnMainAndLayoutSync(
1815                 mActivityRule,
1816                 mPopupWindow.getContentView().getRootView(),
1817                 () -> container.addView(anchor, anchorLayoutParams),
1818                 false  /* force layout */);
1819         assertPopupLocation(originalLocation, deltaX * 2, deltaY * 2);
1820     }
1821 
1822     @Test
testAnchorInPopup()1823     public void testAnchorInPopup() throws Throwable {
1824         DisplayMetrics displayMetrics = mActivity.getResources().getDisplayMetrics();
1825         float dpWidth = displayMetrics.widthPixels / displayMetrics.density;
1826         float dpHeight = displayMetrics.heightPixels / displayMetrics.density;
1827         final int minDisplaySize = 320;
1828         if (dpWidth < minDisplaySize || dpHeight < minDisplaySize) {
1829             // On smaller screens the popups that this test is creating
1830             // are not guaranteed to be properly aligned to their anchors.
1831             return;
1832         }
1833 
1834         mPopupWindow = createPopupWindow(
1835                 mActivity.getLayoutInflater().inflate(R.layout.popup_window, null));
1836 
1837         final PopupWindow subPopup =
1838                 createPopupWindow(createPopupContent(CONTENT_SIZE_DP, CONTENT_SIZE_DP));
1839 
1840         // Check alignment without overlapping the anchor.
1841         assertFalse(subPopup.getOverlapAnchor());
1842 
1843         verifySubPopupPosition(subPopup, R.id.anchor_upper_left, R.id.anchor_lower_right,
1844                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM);
1845         verifySubPopupPosition(subPopup, R.id.anchor_middle_left, R.id.anchor_lower_right,
1846                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM);
1847         verifySubPopupPosition(subPopup, R.id.anchor_lower_left, R.id.anchor_lower_right,
1848                 LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP);
1849 
1850         verifySubPopupPosition(subPopup, R.id.anchor_upper, R.id.anchor_lower_right,
1851                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM);
1852         verifySubPopupPosition(subPopup, R.id.anchor_middle, R.id.anchor_lower_right,
1853                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, BOTTOM);
1854         verifySubPopupPosition(subPopup, R.id.anchor_lower, R.id.anchor_lower_right,
1855                 LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP);
1856 
1857         verifySubPopupPosition(subPopup, R.id.anchor_upper_right, R.id.anchor_lower_right,
1858                 RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, BOTTOM);
1859         verifySubPopupPosition(subPopup, R.id.anchor_middle_right, R.id.anchor_lower_right,
1860                 RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, BOTTOM);
1861         verifySubPopupPosition(subPopup, R.id.anchor_lower_right, R.id.anchor_lower_right,
1862                 RIGHT, EQUAL_TO, RIGHT, BOTTOM, EQUAL_TO, TOP);
1863 
1864         // Check alignment while overlapping the anchor.
1865         subPopup.setOverlapAnchor(true);
1866 
1867         final int anchorHeight = mActivity.findViewById(R.id.anchor_lower_right).getHeight();
1868         // To simplify the math assert that all three lower anchors are the same height.
1869         assertEquals(anchorHeight, mActivity.findViewById(R.id.anchor_lower_left).getHeight());
1870         assertEquals(anchorHeight, mActivity.findViewById(R.id.anchor_lower).getHeight());
1871 
1872         final int verticalSpaceBelowAnchor = anchorHeight * 2;
1873         // Ensure that the subpopup is flipped vertically.
1874         subPopup.setHeight(verticalSpaceBelowAnchor + 1);
1875 
1876         verifySubPopupPosition(subPopup, R.id.anchor_upper_left, R.id.anchor_lower_right,
1877                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP);
1878         verifySubPopupPosition(subPopup, R.id.anchor_middle_left, R.id.anchor_lower_right,
1879                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP);
1880         verifySubPopupPosition(subPopup, R.id.anchor_lower_left, R.id.anchor_lower_right,
1881                 LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP);
1882 
1883         verifySubPopupPosition(subPopup, R.id.anchor_upper, R.id.anchor_lower_right,
1884                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP);
1885         verifySubPopupPosition(subPopup, R.id.anchor_middle, R.id.anchor_lower_right,
1886                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP);
1887         verifySubPopupPosition(subPopup, R.id.anchor_lower, R.id.anchor_lower_right,
1888                 LEFT, EQUAL_TO, LEFT, BOTTOM, EQUAL_TO, TOP);
1889 
1890         verifySubPopupPosition(subPopup, R.id.anchor_upper_right, R.id.anchor_lower_right,
1891                 RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, TOP);
1892         verifySubPopupPosition(subPopup, R.id.anchor_middle_right, R.id.anchor_lower_right,
1893                 RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, TOP);
1894         verifySubPopupPosition(subPopup, R.id.anchor_lower_right, R.id.anchor_lower_right,
1895                 RIGHT, EQUAL_TO, RIGHT, BOTTOM, EQUAL_TO, TOP);
1896 
1897         // Re-test for the bottom anchor row ensuring that the subpopup not flipped vertically.
1898         subPopup.setHeight(verticalSpaceBelowAnchor - 1);
1899 
1900         verifySubPopupPosition(subPopup, R.id.anchor_lower_left, R.id.anchor_lower_right,
1901                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP);
1902         verifySubPopupPosition(subPopup, R.id.anchor_lower, R.id.anchor_lower_right,
1903                 LEFT, EQUAL_TO, LEFT, TOP, EQUAL_TO, TOP);
1904         verifySubPopupPosition(subPopup, R.id.anchor_lower_right, R.id.anchor_lower_right,
1905                 RIGHT, EQUAL_TO, RIGHT, TOP, EQUAL_TO, TOP);
1906 
1907         // Check that scrolling scrolls the sub popup along with the main popup.
1908         showPopup(R.id.anchor_middle);
1909 
1910         mActivityRule.runOnUiThread(() -> subPopup.showAsDropDown(
1911                 mPopupWindow.getContentView().findViewById(R.id.anchor_middle)));
1912         mInstrumentation.waitForIdleSync();
1913 
1914         final int[] popupLocation = new int[2];
1915         mPopupWindow.getContentView().getLocationOnScreen(popupLocation);
1916         final int[] subPopupLocation = new int[2];
1917         subPopup.getContentView().getLocationOnScreen(subPopupLocation);
1918 
1919         final int deltaX = 20;
1920         final int deltaY = 30;
1921 
1922         final ViewGroup container = (ViewGroup) mActivity.findViewById(R.id.main_container);
1923         WidgetTestUtils.runOnMainAndLayoutSync(
1924                 mActivityRule,
1925                 subPopup.getContentView().getRootView(),
1926                 () -> container.scrollBy(deltaX, deltaY),
1927                 false  /* force layout */);
1928 
1929         // Since the first layout might have been caused by the original scroll event (and not by
1930         // the anchor change), we need to wait until all traversals are done.
1931         mInstrumentation.waitForIdleSync();
1932 
1933         final int[] newPopupLocation = new int[2];
1934         mPopupWindow.getContentView().getLocationOnScreen(newPopupLocation);
1935         assertEquals(popupLocation[0] - deltaX, newPopupLocation[0]);
1936         assertEquals(popupLocation[1] - deltaY, newPopupLocation[1]);
1937 
1938         final int[] newSubPopupLocation = new int[2];
1939         subPopup.getContentView().getLocationOnScreen(newSubPopupLocation);
1940         assertEquals(subPopupLocation[0] - deltaX, newSubPopupLocation[0]);
1941         assertEquals(subPopupLocation[1] - deltaY, newSubPopupLocation[1]);
1942     }
1943 
verifySubPopupPosition(PopupWindow subPopup, int mainAnchorId, int subAnchorId, int contentEdgeX, int operatorX, int anchorEdgeX, int contentEdgeY, int operatorY, int anchorEdgeY)1944     private void verifySubPopupPosition(PopupWindow subPopup, int mainAnchorId, int subAnchorId,
1945             int contentEdgeX, int operatorX, int anchorEdgeX,
1946             int contentEdgeY, int operatorY, int anchorEdgeY) throws Throwable {
1947         showPopup(mainAnchorId);
1948         verifyPosition(subPopup, mPopupWindow.getContentView().findViewById(subAnchorId),
1949                 contentEdgeX, operatorX, anchorEdgeX, contentEdgeY, operatorY, anchorEdgeY);
1950         dismissPopup();
1951     }
1952 
assertPopupLocation(int[] originalLocation, int deltaX, int deltaY)1953     private void assertPopupLocation(int[] originalLocation, int deltaX, int deltaY) {
1954         final int[] actualLocation = new int[2];
1955         mPopupWindow.getContentView().getLocationOnScreen(actualLocation);
1956         assertEquals(originalLocation[0] - deltaX, actualLocation[0]);
1957         assertEquals(originalLocation[1] - deltaY, actualLocation[1]);
1958     }
1959 
1960     private static class BaseTransition extends Transition {
1961         @Override
captureStartValues(TransitionValues transitionValues)1962         public void captureStartValues(TransitionValues transitionValues) {}
1963 
1964         @Override
captureEndValues(TransitionValues transitionValues)1965         public void captureEndValues(TransitionValues transitionValues) {}
1966     }
1967 
createPopupContent(int width, int height)1968     private View createPopupContent(int width, int height) {
1969         final View popupView = new View(mActivity);
1970         popupView.setLayoutParams(new ViewGroup.LayoutParams(width, height));
1971         popupView.setBackgroundColor(Color.MAGENTA);
1972 
1973         return popupView;
1974     }
1975 
createPopupWindow()1976     private PopupWindow createPopupWindow() {
1977         PopupWindow window = new PopupWindow(mActivity);
1978         window.setWidth(WINDOW_SIZE_DP);
1979         window.setHeight(WINDOW_SIZE_DP);
1980         window.setBackgroundDrawable(new ColorDrawable(Color.YELLOW));
1981         return window;
1982     }
1983 
createPopupWindow(View content)1984     private PopupWindow createPopupWindow(View content) {
1985         PopupWindow window = createPopupWindow();
1986         window.setContentView(content);
1987         return window;
1988     }
1989 
hasDeviceFeature(final String requiredFeature)1990     private boolean hasDeviceFeature(final String requiredFeature) {
1991         return mContext.getPackageManager().hasSystemFeature(requiredFeature);
1992     }
1993 
showPopup(int resourceId)1994     private void showPopup(int resourceId) throws Throwable {
1995         mActivityRule.runOnUiThread(() -> {
1996             if (mPopupWindow == null || mPopupWindow.isShowing()) {
1997                 return;
1998             }
1999             View anchor = mActivity.findViewById(resourceId);
2000             mPopupWindow.showAsDropDown(anchor);
2001             assertTrue(mPopupWindow.isShowing());
2002         });
2003         mInstrumentation.waitForIdleSync();
2004     }
2005 
showPopup()2006     private void showPopup() throws Throwable {
2007         showPopup(R.id.anchor_upper_left);
2008     }
2009 
dismissPopup()2010     private void dismissPopup() throws Throwable {
2011         mActivityRule.runOnUiThread(() -> {
2012             if (mPopupWindow == null || !mPopupWindow.isShowing()) {
2013                 return;
2014             }
2015             mPopupWindow.dismiss();
2016         });
2017         mInstrumentation.waitForIdleSync();
2018     }
2019 }
2020