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