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