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